From d082ce09ff10fd8b3bf2070b163f1608c67d028b Mon Sep 17 00:00:00 2001 From: Alexandre Teyar <11601622+aress31@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:49:45 +0900 Subject: [PATCH 1/5] feat(mui): enhanced customisation in ThemedLayoutV2 (#6502) (resolves #6498) --- .changeset/seven-ads-punch.md | 5 +++ .../components/themed-layout/index.md | 30 ++++++++++++++++++ .../src/components/themedLayoutV2/index.tsx | 31 +++++++++++++++++-- 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .changeset/seven-ads-punch.md diff --git a/.changeset/seven-ads-punch.md b/.changeset/seven-ads-punch.md new file mode 100644 index 000000000000..f908e7a97946 --- /dev/null +++ b/.changeset/seven-ads-punch.md @@ -0,0 +1,5 @@ +--- +"@refinedev/mui": minor +--- + +Introduced containerBoxProps and childrenBoxProps props to ThemedLayoutV2 to allow for greater control over the layout - including styling. diff --git a/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md b/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md index 26b8b87da66d..4a59505bdbfc 100644 --- a/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md +++ b/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md @@ -203,6 +203,36 @@ type SiderRenderFunction = (props: { }) => React.ReactNode; ``` +### childrenBoxProps + +This prop is used to customize the styles and attributes of the inner Box that contains the main layout content. It accepts all properties of BoxProps, including the `sx` property for inline styling. + +```tsx + + {/* ... */} + +``` + +### containerBoxProps + +This prop is used to customize the styles and attributes of the outer container Box wrapping the layout. It accepts all properties of BoxProps, including the `sx` property for inline styling + +```tsx + + {/* ... */} + +``` + ### initialSiderCollapsed This prop is used to set the initial collapsed state of the [``][themed-sider] component. If it is `true`, It will be collapsed by default. diff --git a/packages/mui/src/components/themedLayoutV2/index.tsx b/packages/mui/src/components/themedLayoutV2/index.tsx index df25e644ee2b..5265a02ba81e 100644 --- a/packages/mui/src/components/themedLayoutV2/index.tsx +++ b/packages/mui/src/components/themedLayoutV2/index.tsx @@ -1,13 +1,28 @@ import React from "react"; - import Box from "@mui/material/Box"; import { ThemedLayoutContextProvider } from "@contexts"; import { ThemedSiderV2 as DefaultSider } from "./sider"; import { ThemedHeaderV2 as DefaultHeader } from "./header"; + +import type { BoxProps } from "@mui/material"; import type { RefineThemedLayoutV2Props } from "./types"; -export const ThemedLayoutV2: React.FC = ({ +interface ExtendedRefineThemedLayoutV2Props extends RefineThemedLayoutV2Props { + /** + * Additional properties for the children box. + * This type includes all properties of BoxProps, including 'sx'. + */ + childrenBoxProps?: BoxProps; + + /** + * Additional properties for the container box. + * This type includes all properties of BoxProps, including 'sx'. + */ + containerBoxProps?: BoxProps; +} + +export const ThemedLayoutV2: React.FC = ({ Sider, Header, Title, @@ -15,13 +30,21 @@ export const ThemedLayoutV2: React.FC = ({ OffLayoutArea, children, initialSiderCollapsed, + childrenBoxProps = {}, + containerBoxProps = {}, }) => { const SiderToRender = Sider ?? DefaultSider; const HeaderToRender = Header ?? DefaultHeader; + const { sx: childrenSx, ...restChildrenProps } = childrenBoxProps; + const { sx: containerSx, ...restContainerProps } = containerBoxProps; + return ( - + = ({ p: { xs: 1, md: 2, lg: 3 }, flexGrow: 1, bgcolor: (theme) => theme.palette.background.default, + ...childrenSx, }} + {...restChildrenProps} > {children} From 1d2613381c50f438270d6a3e486595d54496ef92 Mon Sep 17 00:00:00 2001 From: Omkar Bansod Date: Mon, 9 Dec 2024 16:00:12 +0530 Subject: [PATCH 2/5] feat: add onSiderCollapsed to RefineThemedLayoutV2Props (#6527) (resolves #6508) Co-authored-by: Alican Erdurmaz Co-authored-by: Batuhan Wilhelm --- .changeset/warm-plums-hammer.md | 9 ++++++ .../components/themed-layout/index.md | 28 +++++++++++++++++++ .../components/themed-layout/index.md | 27 ++++++++++++++++++ .../mantine/components/themed-layout/index.md | 27 ++++++++++++++++++ .../components/themed-layout/index.md | 27 ++++++++++++++++++ .../src/components/themedLayoutV2/index.tsx | 6 +++- .../IThemedLayoutContext.ts | 1 + .../contexts/themedLayoutContext/index.tsx | 12 ++++++-- .../src/components/themedLayoutV2/index.tsx | 6 +++- .../IThemedLayoutContext.ts | 1 + .../contexts/themedLayoutContext/index.tsx | 12 ++++++-- .../src/components/themedLayoutV2/index.tsx | 6 +++- .../IThemedLayoutContext.ts | 1 + .../contexts/themedLayoutContext/index.tsx | 12 ++++++-- .../components/themedLayoutV2/index.spec.tsx | 6 ++++ .../src/components/themedLayoutV2/index.tsx | 6 +++- .../IThemedLayoutContext.ts | 1 + .../contexts/themedLayoutContext/index.tsx | 13 +++++++-- packages/ui-tests/src/tests/layout/layout.tsx | 6 ++-- packages/ui-types/src/types/layout.tsx | 5 ++++ 20 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 .changeset/warm-plums-hammer.md create mode 100644 packages/mui/src/components/themedLayoutV2/index.spec.tsx diff --git a/.changeset/warm-plums-hammer.md b/.changeset/warm-plums-hammer.md new file mode 100644 index 000000000000..5f4ddb87dbea --- /dev/null +++ b/.changeset/warm-plums-hammer.md @@ -0,0 +1,9 @@ +--- +"@refinedev/chakra-ui": minor +"@refinedev/ui-types": minor +"@refinedev/mantine": minor +"@refinedev/antd": minor +"@refinedev/mui": minor +--- + +Enhanced the ThemedSideV2 component with new functionality to support dynamic onSiderCollapsed handling. This allows better customization of sider collapse/expand events and improved responsiveness for mobile and desktop views. Added additional type definitions and ensured compatibility across all layout contexts. resolves #6508 diff --git a/documentation/docs/ui-integrations/ant-design/components/themed-layout/index.md b/documentation/docs/ui-integrations/ant-design/components/themed-layout/index.md index 2662f50d3471..1988c4a74848 100644 --- a/documentation/docs/ui-integrations/ant-design/components/themed-layout/index.md +++ b/documentation/docs/ui-integrations/ant-design/components/themed-layout/index.md @@ -210,6 +210,7 @@ const App: React.FC = () => { | `meta` | `Record` | Meta data to use when creating routes for the menu items | | `fixed` | `boolean` | Whether the sider is fixed or not | | `activeItemDisabled` | `boolean` | Whether clicking on an active sider item should reload the page | +| `onSiderCollapsed` | `(collapsed: boolean) => void` | Callback function invoked when the sider collapses or expands | ```tsx type SiderRenderFunction = (props: { @@ -236,6 +237,33 @@ This prop is used to set the initial collapsed state of the [``][ ``` +### `onSiderCollapsed` + +Will be triggered when the [``][themed-sider] component's `collapsed` state changes. + +Can be used to persist collapsed state on the localstorage. Then you can use localStorage item to decide if sider should be collapsed initially or not. + +Here's an example of how to use the `onSiderCollapsed` prop: + +```tsx +const MyLayout = () => { + const onSiderCollapse = (collapsed: boolean) => { + localStorage.setItem("siderCollapsed", collapsed); + }; + + const initialSiderCollapsed = Boolean(localStorage.getItem("siderCollapsed")); + + return ( + + {/* ... */} + + ); +}; +``` + ### Header In ``, the header section is rendered using the [``][themed-header] component by default. It uses [`useGetIdentity`](/docs/authentication/hooks/use-get-identity) hook to display the user's name and avatar on the right side of the header. However, if desired, it's possible to replace the default [``][themed-header] component by passing a custom component to the `Header` prop. diff --git a/documentation/docs/ui-integrations/chakra-ui/components/themed-layout/index.md b/documentation/docs/ui-integrations/chakra-ui/components/themed-layout/index.md index 9a87062237c3..b135d4a96485 100644 --- a/documentation/docs/ui-integrations/chakra-ui/components/themed-layout/index.md +++ b/documentation/docs/ui-integrations/chakra-ui/components/themed-layout/index.md @@ -219,6 +219,33 @@ This prop is used to set the initial collapsed state of the [``][ ``` +### `onSiderCollapsed` + +Will be triggered when the [``][themed-sider] component's `collapsed` state changes. + +Can be used to persist collapsed state on the localstorage. Then you can use localStorage item to decide if sider should be collapsed initially or not. + +Here's an example of how to use the `onSiderCollapsed` prop: + +```tsx +const MyLayout = () => { + const onSiderCollapse = (collapsed: boolean) => { + localStorage.setItem("siderCollapsed", collapsed); + }; + + const initialSiderCollapsed = Boolean(localStorage.getItem("siderCollapsed")); + + return ( + + {/* ... */} + + ); +}; +``` + ### Header In ``, the header section is rendered using the [``][themed-header] component by default. It uses the [`useGetIdentity`](/docs/authentication/hooks/use-get-identity) hook to display the user's name and avatar on the right side of the header. However, if desired, it's possible to replace the default [``][themed-header] component by passing a custom component to the `Header` prop. diff --git a/documentation/docs/ui-integrations/mantine/components/themed-layout/index.md b/documentation/docs/ui-integrations/mantine/components/themed-layout/index.md index dda11113d60b..01f28dcf0db7 100644 --- a/documentation/docs/ui-integrations/mantine/components/themed-layout/index.md +++ b/documentation/docs/ui-integrations/mantine/components/themed-layout/index.md @@ -221,6 +221,33 @@ This prop is used to set the initial collapsed state of the [``][ ``` +### `onSiderCollapsed` + +Will be triggered when the [``][themed-sider] component's `collapsed` state changes. + +Can be used to persist collapsed state on the localstorage. Then you can use localStorage item to decide if sider should be collapsed initially or not. + +Here's an example of how to use the `onSiderCollapsed` prop: + +```tsx +const MyLayout = () => { + const onSiderCollapse = (collapsed: boolean) => { + localStorage.setItem("siderCollapsed", collapsed); + }; + + const initialSiderCollapsed = Boolean(localStorage.getItem("siderCollapsed")); + + return ( + + {/* ... */} + + ); +}; +``` + ### Header In ``, the header section is rendered using the [``][themed-header] component by default. It uses the [`useGetIdentity`](/docs/authentication/hooks/use-get-identity) hook to display the user's name and avatar on the right side of the header. However, if desired, it's possible to replace the default [``][themed-header] component by passing a custom component to the `Header` prop. diff --git a/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md b/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md index 4a59505bdbfc..8a86b95d51dd 100644 --- a/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md +++ b/documentation/docs/ui-integrations/material-ui/components/themed-layout/index.md @@ -246,6 +246,33 @@ This prop is used to set the initial collapsed state of the [``][ ``` +### `onSiderCollapsed` + +Will be triggered when the [``][themed-sider] component's `collapsed` state changes. + +Can be used to persist collapsed state on the localstorage. Then you can use localStorage item to decide if sider should be collapsed initially or not. + +Here's an example of how to use the `onSiderCollapsed` prop: + +```tsx +const MyLayout = () => { + const onSiderCollapse = (collapsed: boolean) => { + localStorage.setItem("siderCollapsed", collapsed); + }; + + const initialSiderCollapsed = Boolean(localStorage.getItem("siderCollapsed")); + + return ( + + {/* ... */} + + ); +}; +``` + ### Header In ``, the header section is rendered using the [``][themed-header] component by default. It uses [`useGetIdentity`](/docs/authentication/hooks/use-get-identity) hook to display the user's name and avatar on the right side of the header. However, if desired, it's possible to replace the default [``][themed-header] component by passing a custom component to the `Header` prop. diff --git a/packages/antd/src/components/themedLayoutV2/index.tsx b/packages/antd/src/components/themedLayoutV2/index.tsx index 0692bd0ca5c1..01acbb5f7520 100644 --- a/packages/antd/src/components/themedLayoutV2/index.tsx +++ b/packages/antd/src/components/themedLayoutV2/index.tsx @@ -14,6 +14,7 @@ export const ThemedLayoutV2: React.FC = ({ Footer, OffLayoutArea, initialSiderCollapsed, + onSiderCollapsed, }) => { const breakpoint = Grid.useBreakpoint(); const SiderToRender = Sider ?? DefaultSider; @@ -22,7 +23,10 @@ export const ThemedLayoutV2: React.FC = ({ const hasSider = !!SiderToRender({ Title }); return ( - + diff --git a/packages/antd/src/contexts/themedLayoutContext/IThemedLayoutContext.ts b/packages/antd/src/contexts/themedLayoutContext/IThemedLayoutContext.ts index 6134b4b3f010..5e9fef9614e6 100644 --- a/packages/antd/src/contexts/themedLayoutContext/IThemedLayoutContext.ts +++ b/packages/antd/src/contexts/themedLayoutContext/IThemedLayoutContext.ts @@ -3,4 +3,5 @@ export interface IThemedLayoutContext { setSiderCollapsed: (visible: boolean) => void; mobileSiderOpen: boolean; setMobileSiderOpen: (visible: boolean) => void; + onSiderCollapsed?: (collapsed: boolean) => void; } diff --git a/packages/antd/src/contexts/themedLayoutContext/index.tsx b/packages/antd/src/contexts/themedLayoutContext/index.tsx index cedb65c6a300..264991bbab90 100644 --- a/packages/antd/src/contexts/themedLayoutContext/index.tsx +++ b/packages/antd/src/contexts/themedLayoutContext/index.tsx @@ -12,12 +12,20 @@ export const ThemedLayoutContext = React.createContext({ export const ThemedLayoutContextProvider: React.FC<{ children: ReactNode; initialSiderCollapsed?: boolean; -}> = ({ children, initialSiderCollapsed }) => { - const [siderCollapsed, setSiderCollapsed] = useState( + onSiderCollapsed?: (collapsed: boolean) => void; +}> = ({ children, initialSiderCollapsed, onSiderCollapsed }) => { + const [siderCollapsed, setSiderCollapsedState] = useState( initialSiderCollapsed ?? false, ); const [mobileSiderOpen, setMobileSiderOpen] = useState(false); + const setSiderCollapsed = (collapsed: boolean) => { + setSiderCollapsedState(collapsed); + if (onSiderCollapsed) { + onSiderCollapsed(collapsed); + } + }; + return ( = ({ OffLayoutArea, children, initialSiderCollapsed, + onSiderCollapsed, }) => { const SiderToRender = Sider ?? DefaultSider; const HeaderToRender = Header ?? DefaultHeader; return ( - + void; mobileSiderOpen: boolean; setMobileSiderOpen: (visible: boolean) => void; + onSiderCollapsed?: (collapsed: boolean) => void; } diff --git a/packages/chakra-ui/src/contexts/themedLayoutContext/index.tsx b/packages/chakra-ui/src/contexts/themedLayoutContext/index.tsx index cedb65c6a300..264991bbab90 100644 --- a/packages/chakra-ui/src/contexts/themedLayoutContext/index.tsx +++ b/packages/chakra-ui/src/contexts/themedLayoutContext/index.tsx @@ -12,12 +12,20 @@ export const ThemedLayoutContext = React.createContext({ export const ThemedLayoutContextProvider: React.FC<{ children: ReactNode; initialSiderCollapsed?: boolean; -}> = ({ children, initialSiderCollapsed }) => { - const [siderCollapsed, setSiderCollapsed] = useState( + onSiderCollapsed?: (collapsed: boolean) => void; +}> = ({ children, initialSiderCollapsed, onSiderCollapsed }) => { + const [siderCollapsed, setSiderCollapsedState] = useState( initialSiderCollapsed ?? false, ); const [mobileSiderOpen, setMobileSiderOpen] = useState(false); + const setSiderCollapsed = (collapsed: boolean) => { + setSiderCollapsedState(collapsed); + if (onSiderCollapsed) { + onSiderCollapsed(collapsed); + } + }; + return ( = ({ OffLayoutArea, initialSiderCollapsed, children, + onSiderCollapsed, }) => { const SiderToRender = Sider ?? DefaultSider; const HeaderToRender = Header ?? DefaultHeader; return ( - + void; mobileSiderOpen: boolean; setMobileSiderOpen: (visible: boolean) => void; + onSiderCollapsed?: (collapsed: boolean) => void; } diff --git a/packages/mantine/src/contexts/themedLayoutContext/index.tsx b/packages/mantine/src/contexts/themedLayoutContext/index.tsx index cedb65c6a300..264991bbab90 100644 --- a/packages/mantine/src/contexts/themedLayoutContext/index.tsx +++ b/packages/mantine/src/contexts/themedLayoutContext/index.tsx @@ -12,12 +12,20 @@ export const ThemedLayoutContext = React.createContext({ export const ThemedLayoutContextProvider: React.FC<{ children: ReactNode; initialSiderCollapsed?: boolean; -}> = ({ children, initialSiderCollapsed }) => { - const [siderCollapsed, setSiderCollapsed] = useState( + onSiderCollapsed?: (collapsed: boolean) => void; +}> = ({ children, initialSiderCollapsed, onSiderCollapsed }) => { + const [siderCollapsed, setSiderCollapsedState] = useState( initialSiderCollapsed ?? false, ); const [mobileSiderOpen, setMobileSiderOpen] = useState(false); + const setSiderCollapsed = (collapsed: boolean) => { + setSiderCollapsedState(collapsed); + if (onSiderCollapsed) { + onSiderCollapsed(collapsed); + } + }; + return ( { + layoutLayoutTests.bind(this)(ThemedLayoutV2); +}); diff --git a/packages/mui/src/components/themedLayoutV2/index.tsx b/packages/mui/src/components/themedLayoutV2/index.tsx index 5265a02ba81e..5a72c8e3237f 100644 --- a/packages/mui/src/components/themedLayoutV2/index.tsx +++ b/packages/mui/src/components/themedLayoutV2/index.tsx @@ -30,6 +30,7 @@ export const ThemedLayoutV2: React.FC = ({ OffLayoutArea, children, initialSiderCollapsed, + onSiderCollapsed, childrenBoxProps = {}, containerBoxProps = {}, }) => { @@ -40,7 +41,10 @@ export const ThemedLayoutV2: React.FC = ({ const { sx: containerSx, ...restContainerProps } = containerBoxProps; return ( - + void; mobileSiderOpen: boolean; setMobileSiderOpen: (visible: boolean) => void; + onSiderCollapsed?: (collapsed: boolean) => void; } diff --git a/packages/mui/src/contexts/themedLayoutContext/index.tsx b/packages/mui/src/contexts/themedLayoutContext/index.tsx index cedb65c6a300..e8e78c063e09 100644 --- a/packages/mui/src/contexts/themedLayoutContext/index.tsx +++ b/packages/mui/src/contexts/themedLayoutContext/index.tsx @@ -12,12 +12,20 @@ export const ThemedLayoutContext = React.createContext({ export const ThemedLayoutContextProvider: React.FC<{ children: ReactNode; initialSiderCollapsed?: boolean; -}> = ({ children, initialSiderCollapsed }) => { - const [siderCollapsed, setSiderCollapsed] = useState( + onSiderCollapsed?: (collapsed: boolean) => void; +}> = ({ children, initialSiderCollapsed, onSiderCollapsed }) => { + const [siderCollapsed, setSiderCollapsedState] = useState( initialSiderCollapsed ?? false, ); const [mobileSiderOpen, setMobileSiderOpen] = useState(false); + const setSiderCollapsed = (collapsed: boolean) => { + setSiderCollapsedState(collapsed); + if (onSiderCollapsed) { + onSiderCollapsed(collapsed); + } + }; + return ( {children} diff --git a/packages/ui-tests/src/tests/layout/layout.tsx b/packages/ui-tests/src/tests/layout/layout.tsx index 972002f0ac29..c1a37f25bb52 100644 --- a/packages/ui-tests/src/tests/layout/layout.tsx +++ b/packages/ui-tests/src/tests/layout/layout.tsx @@ -1,10 +1,10 @@ import React from "react"; -import type { RefineLayoutLayoutProps } from "@refinedev/ui-types"; +import type { RefineThemedLayoutV2Props } from "@refinedev/ui-types"; -import { act, render, TestWrapper } from "@test"; +import { render, TestWrapper } from "@test"; export const layoutLayoutTests = ( - LayoutElement: React.ComponentType, + LayoutElement: React.ComponentType, ): void => { describe("[@refinedev/ui-tests] Common Tests / Layout Element", () => { it("Layout renders sider, header, footer, title, offLayoutArea if given props", async () => { diff --git a/packages/ui-types/src/types/layout.tsx b/packages/ui-types/src/types/layout.tsx index 54d5dbe26337..2ca58bfab9f7 100644 --- a/packages/ui-types/src/types/layout.tsx +++ b/packages/ui-types/src/types/layout.tsx @@ -80,6 +80,11 @@ export type RefineThemedLayoutV2Props = { * Whether the sider is collapsed or not by default. */ initialSiderCollapsed?: boolean; + + /** + * Callback function triggered when the sider's collapsed state changes. + */ + onSiderCollapsed?: (collapsed: boolean) => void; } & RefineLayoutLayoutProps; export type RefineThemedLayoutV2SiderProps = RefineLayoutSiderProps & { activeItemDisabled?: boolean; From 6b8016a9bbb7422255711bf67f8247eb44db78a9 Mon Sep 17 00:00:00 2001 From: Alican Erdurmaz Date: Mon, 9 Dec 2024 13:31:06 +0300 Subject: [PATCH 3/5] feat(nextjs-router): add Next.js 15 support (#6551) (resolves #6430) Co-authored-by: Batuhan Wilhelm --- .changeset/tidy-sloths-juggle.md | 22 + .syncpackrc | 5 + examples/core-use-select/package.json | 2 + examples/with-nextjs-headless/.eslintrc.json | 3 + examples/with-nextjs-headless/.gitignore | 36 + examples/with-nextjs-headless/.npmrc | 2 + examples/with-nextjs-headless/README.MD | 43 + examples/with-nextjs-headless/next.config.mjs | 8 + examples/with-nextjs-headless/package.json | 41 + .../with-nextjs-headless/public/favicon.ico | Bin 0 -> 7406 bytes .../public/locales/de/common.json | 172 +++ .../public/locales/en/common.json | 162 +++ .../src/app/_refine_context.tsx | 58 + .../src/app/blog-posts/create/page.tsx | 78 ++ .../src/app/blog-posts/edit/[id]/page.tsx | 127 ++ .../src/app/blog-posts/page.tsx | 158 +++ .../src/app/blog-posts/show/[id]/page.tsx | 68 ++ .../src/app/forgot-password/page.tsx | 5 + .../with-nextjs-headless/src/app/icon.ico | Bin 0 -> 7406 bytes .../with-nextjs-headless/src/app/layout.tsx | 39 + .../src/app/login/page.tsx | 5 + .../src/app/not-found.tsx | 14 + .../with-nextjs-headless/src/app/page.tsx | 13 + .../src/app/register/page.tsx | 5 + .../src/app/update-password/page.tsx | 45 + .../src/components/auth-page/index.tsx | 16 + .../src/components/layout/header.tsx | 22 + .../src/components/select-language/index.tsx | 36 + .../with-nextjs-headless/src/i18n/config.ts | 2 + .../with-nextjs-headless/src/i18n/index.ts | 17 + .../with-nextjs-headless/src/i18n/request.ts | 14 + .../auth-provider/auth-provider.server.ts | 23 + .../providers/auth-provider/auth-provider.ts | 155 +++ .../src/providers/auth-provider/index.ts | 2 + .../src/providers/data-provider/index.ts | 7 + .../src/providers/devtools/index.tsx | 16 + .../with-nextjs-headless/src/types/index.tsx | 15 + examples/with-nextjs-headless/tsconfig.json | 28 + packages/nextjs-router/package.json | 10 +- pnpm-lock.yaml | 1017 +++++++++++++++-- pnpm-workspace.yaml | 1 + 41 files changed, 2418 insertions(+), 74 deletions(-) create mode 100644 .changeset/tidy-sloths-juggle.md create mode 100644 examples/with-nextjs-headless/.eslintrc.json create mode 100644 examples/with-nextjs-headless/.gitignore create mode 100644 examples/with-nextjs-headless/.npmrc create mode 100644 examples/with-nextjs-headless/README.MD create mode 100644 examples/with-nextjs-headless/next.config.mjs create mode 100644 examples/with-nextjs-headless/package.json create mode 100644 examples/with-nextjs-headless/public/favicon.ico create mode 100644 examples/with-nextjs-headless/public/locales/de/common.json create mode 100644 examples/with-nextjs-headless/public/locales/en/common.json create mode 100644 examples/with-nextjs-headless/src/app/_refine_context.tsx create mode 100644 examples/with-nextjs-headless/src/app/blog-posts/create/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/blog-posts/edit/[id]/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/blog-posts/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/blog-posts/show/[id]/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/forgot-password/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/icon.ico create mode 100644 examples/with-nextjs-headless/src/app/layout.tsx create mode 100644 examples/with-nextjs-headless/src/app/login/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/not-found.tsx create mode 100644 examples/with-nextjs-headless/src/app/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/register/page.tsx create mode 100644 examples/with-nextjs-headless/src/app/update-password/page.tsx create mode 100644 examples/with-nextjs-headless/src/components/auth-page/index.tsx create mode 100644 examples/with-nextjs-headless/src/components/layout/header.tsx create mode 100644 examples/with-nextjs-headless/src/components/select-language/index.tsx create mode 100644 examples/with-nextjs-headless/src/i18n/config.ts create mode 100644 examples/with-nextjs-headless/src/i18n/index.ts create mode 100644 examples/with-nextjs-headless/src/i18n/request.ts create mode 100644 examples/with-nextjs-headless/src/providers/auth-provider/auth-provider.server.ts create mode 100644 examples/with-nextjs-headless/src/providers/auth-provider/auth-provider.ts create mode 100644 examples/with-nextjs-headless/src/providers/auth-provider/index.ts create mode 100644 examples/with-nextjs-headless/src/providers/data-provider/index.ts create mode 100644 examples/with-nextjs-headless/src/providers/devtools/index.tsx create mode 100644 examples/with-nextjs-headless/src/types/index.tsx create mode 100644 examples/with-nextjs-headless/tsconfig.json diff --git a/.changeset/tidy-sloths-juggle.md b/.changeset/tidy-sloths-juggle.md new file mode 100644 index 000000000000..e9dec2aa3eca --- /dev/null +++ b/.changeset/tidy-sloths-juggle.md @@ -0,0 +1,22 @@ +--- +"@refinedev/nextjs-router": minor +--- + +**Feature:** Added support for Next.js 15. #6430 + +To create a new example project with Next.js 15, run the following command: + +```bash +npm create refine-app@latest -- --example with-nextjs-headless +``` + +You can find the source code in the [examples/with-nextjs-headless](https://github.com/refinedev/refine/tree/main/examples/with-nextjs-headless) directory. + +> 🚨 While `@refinedev/core` and `@refinedev/nextjs-router` do not introduce breaking changes, upgrading to Next.js 15 requires your project to be compatible with React 19. Please refer to the migration guides below: +> +> - [Next.js 15 Upgrade Guide](https://nextjs.org/docs/app/building-your-application/upgrading/version-15) +> - [React 19 Upgrade Guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) +> +> 🚨 Additionally, if you're using `@refinedev/antd`, `@refinedev/chakra-ui`, `@refinedev/mantine`, or `@refinedev/mui`, make sure to check their compatibility with React 19. + +For known issues, migration guidance, and more details, please refer to issue [#6430](https://github.com/refinedev/refine/issues/6430). diff --git a/.syncpackrc b/.syncpackrc index 74dc2c7acd74..16a085924bf8 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -26,6 +26,11 @@ "dependencyTypes": ["dev"], "packages": ["@refinedev/appwrite", "@refinedev/graphql"], "isIgnored": true + }, + { + "dependencies": ["next", "react", "react-dom", "@types/react", "@types/react-dom"], + "packages": ["with-nextjs-headless"], + "isIgnored": true } ] } diff --git a/examples/core-use-select/package.json b/examples/core-use-select/package.json index e9b8c1b33764..df629e7473c4 100644 --- a/examples/core-use-select/package.json +++ b/examples/core-use-select/package.json @@ -26,6 +26,8 @@ "@refinedev/core": "^4.56.0", "@refinedev/react-router-v6": "^4.6.0", "@refinedev/simple-rest": "^5.0.8", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-router-dom": "^6.8.1" }, "devDependencies": { diff --git a/examples/with-nextjs-headless/.eslintrc.json b/examples/with-nextjs-headless/.eslintrc.json new file mode 100644 index 000000000000..37224185490e --- /dev/null +++ b/examples/with-nextjs-headless/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/examples/with-nextjs-headless/.gitignore b/examples/with-nextjs-headless/.gitignore new file mode 100644 index 000000000000..0563835d3494 --- /dev/null +++ b/examples/with-nextjs-headless/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/examples/with-nextjs-headless/.npmrc b/examples/with-nextjs-headless/.npmrc new file mode 100644 index 000000000000..5aec3916e1d2 --- /dev/null +++ b/examples/with-nextjs-headless/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps=true +strict-peer-dependencies=false diff --git a/examples/with-nextjs-headless/README.MD b/examples/with-nextjs-headless/README.MD new file mode 100644 index 000000000000..2f6833cc1b90 --- /dev/null +++ b/examples/with-nextjs-headless/README.MD @@ -0,0 +1,43 @@ +
+ + refine logo + + +
+
+ + +
+ +
+
+ +
Build your React-based CRUD applications, without constraints.
An open source, headless web application framework developed with flexibility in mind. + +
+
+ +[![Discord](https://img.shields.io/discord/837692625737613362.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/refine) +[![Twitter Follow](https://img.shields.io/twitter/follow/refine_dev?style=social)](https://twitter.com/refine_dev) + +refine - 100% open source React framework to build web apps 3x faster | Product Hunt + +
+ +## Try this example on your local + +```bash +npm create refine-app@latest -- --example with-nextjs-headless +``` + +## Try this example on CodeSandbox + +
+ +[![Open with-nextjs-headless example from refine](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/embed/github/refinedev/refine/tree/main/examples/with-nextjs-headless?view=preview&theme=dark&codemirror=1) diff --git a/examples/with-nextjs-headless/next.config.mjs b/examples/with-nextjs-headless/next.config.mjs new file mode 100644 index 000000000000..3e657d2b6f0b --- /dev/null +++ b/examples/with-nextjs-headless/next.config.mjs @@ -0,0 +1,8 @@ +import createNextIntlPlugin from "next-intl/plugin"; + +const withNextIntl = createNextIntlPlugin(); + +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default withNextIntl(nextConfig); diff --git a/examples/with-nextjs-headless/package.json b/examples/with-nextjs-headless/package.json new file mode 100644 index 000000000000..a23bf6c37710 --- /dev/null +++ b/examples/with-nextjs-headless/package.json @@ -0,0 +1,41 @@ +{ + "name": "with-nextjs-headless", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "refine build", + "dev": "cross-env NODE_OPTIONS=--max_old_space_size=4096 refine dev", + "lint": "eslint '**/*.{js,jsx,ts,tsx}'", + "start": "refine start" + }, + "dependencies": { + "@refinedev/cli": "^2.16.39", + "@refinedev/core": "^4.56.0", + "@refinedev/devtools": "^1.2.9", + "@refinedev/kbar": "^1.3.12", + "@refinedev/nextjs-router": "^6.1.0", + "@refinedev/react-hook-form": "^4.9.1", + "@refinedev/react-table": "^5.6.13", + "@refinedev/simple-rest": "^5.0.8", + "@tanstack/react-table": "^8.2.6", + "cross-env": "^7.0.3", + "js-cookie": "^3.0.1", + "next": "^15.0.4", + "next-intl": "^3.25.3", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/js-cookie": "^3.0.2", + "@types/node": "^18.16.2", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "cross-env": "^7.0.3", + "eslint": "^8.24.0", + "eslint-config-next": "15.0.3", + "typescript": "^5.4.2" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/with-nextjs-headless/public/favicon.ico b/examples/with-nextjs-headless/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3df394d64c7624f7b64e3071c79ef5047aa1bbb GIT binary patch literal 7406 zcmeHLS#J|p6#nci9y=kE>nu)eC$Swn35ny_1_B9$Re(w>Dt2`TUIILH;e}0IKv0#k zb(L6Kc2L^~5C~9MO50z+(mq0xim(+bLJ$#=p!eQcG8d1Npe_{QBi)&~=ljl?GsiR6 z=Ul*oG(@AIX9)*$fO-HvpE{R-OH`MetIQW<03W-6D$1cIm`ew<<6>7%{Af|GKUlgH zcRM=~4g~OfdprKPc@r(ikE5%r3sWae!sqMPqr&6C#q;M82?nun-aLGN-~fK0c*PvT7ARTel8Dw;LDEokR27x%h7Xek@zO829eo!^@K= zMm^}+?zg+f>~e?B^{T*05WZ{u`J3tkvI7GG`JgsJ$Pl7Y^*%gSOUILqbxQsSG@S86eMqxTKL*>qyBpuu#*3bxtl7)kFHX(AmtS zUkicVuSEj8PYY9aQj4T)Q;T%inO>DfsWLOOGE|_=>Di8SL#_gi2FRIhQ6O)XQQMfI z8x%Def$Ulhs1*#FN>zQX)kydFH98?)V@OWxPz4!oq&p+MbV5P`G(v&O1!dI8cL-2e zfwBapAsj+10u)pDg3f7lPly?2rqis&$9B=^6zuI)A-HJew#$yAo{i0bC^LQ zW}+LzKTf-Ep!^2K>*yqW@qNxDq)_3z3GoZ@#Ha%*;`fqINN+si_hM7{d4iKXsvOLo zcM!dh1ZcRzmlMDDF%1puX)VG!W6f3gWSFp?#CzA1Y@_?8Q7WB~62VVg{e=Ajy)mHE z2@%&rPhPa9#u4!tyo>80wS2Pmmfb3;Mbdv6Q;Vc*(|h#EPO#{gEm#bQ%`9A@`m(a( zf&O1Ji>D{gU&Z(TdSce6`sf%-hu){A(}^Z(pL*l^b+BIbL$zQ1>C74Q^z`6odVX{E zY~H`_+P)pbYHF}@`EvYn_AG|i*5b&aL%ctIW!f~}qef|O%KFn8Q>Ngx88hH7EX0m2 zThMy>GTvXZMDI^unlJ%94<4Yrs0eS&nuTw-ZPWYJgM0V#UiGaxa}c6^D(g?{!(psk zu>vPgoWSJKqj`@yA`-#T!-w(e^y#>K=@J@f6MyW;5#E2kK656%*}65=gKEEX>YU0z z9~qGUK?zseB*|7CPU12je}Q@qllJXTq=Ae zvRvY-P^bAtJW%L$P+SRI?}Tc>6l#}7NPz;&vQ|$KE7QbOcVw7`>kV~`X$mzwN5gpq z%d~||qroJi;d%h%S;GTag9EahG=8^(5J%c$xFJ8hSI(5h{KT{b`Q#|wcqpxx#rzg) z_)u9ccT`o%a-F8Ju>64)&|Ex9g5%m;`F-W6{(jgqWI4n4*!v;Nz3*3ED8%Y=CcW<$ z^V;Y8$!}|9B);E9ALb}!gsaQ^{^Z{mzs-{#`dAwN6NuF*bc>zWDteuw)+RgvL!B}z z9_O_Q+f#pM!2-U2{@$WR`d<0EHEVb+GNP`I?~!+Rck>$L`n79(Z+z3*wY=7NySW)R zZ`?rBkRkYJ*)p(tqqe*p?=D=3wX0X-Q##nb_x=0#;VUS>j?J4fxU`hl8P~2}#c^6| zuo|P<=i?!!4yg>JGVt$aKr$1XJIn8;HHRWLlIEb|E6iu}5_`zuu;i3k9FCBEkR%mT zC{326fRkQiN>T { + const t = useTranslations(); + + const i18nProvider: I18nProvider = { + translate: (key: string, options?: Record) => + t(key, options), + getLocale: useLocale, + changeLocale: setUserLocale, + }; + + return ( + Loading...}> + + + + + {children} + + + + + + ); +}; diff --git a/examples/with-nextjs-headless/src/app/blog-posts/create/page.tsx b/examples/with-nextjs-headless/src/app/blog-posts/create/page.tsx new file mode 100644 index 000000000000..7bb014ffbf99 --- /dev/null +++ b/examples/with-nextjs-headless/src/app/blog-posts/create/page.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useForm } from "@refinedev/react-hook-form"; +import { Link, useSelect, useTranslate } from "@refinedev/core"; +import type { BlogPost, Category } from "@types"; + +export default function BlogPostCreate() { + const translate = useTranslate(); + + const { + refineCore: { onFinish }, + handleSubmit, + register, + formState: { errors }, + } = useForm(); + + const { options } = useSelect({ + resource: "categories", + }); + + return ( +
+

{translate("blog_posts.titles.create")}

+
+
+ +
+ + {errors.title && {errors.title.message?.toString()}} +
+ +
+ +
+ +
+ + {errors.category && This field is required} +
+ +
+
+ +
+