diff --git a/.changeset/mighty-clouds-help.md b/.changeset/mighty-clouds-help.md new file mode 100644 index 0000000000000..68601bdb30989 --- /dev/null +++ b/.changeset/mighty-clouds-help.md @@ -0,0 +1,30 @@ +--- +"@refinedev/core": minor +--- + +feat(core): ability to pass global app title and icon + +Added ability to pass global app name and icon values through `` component's `options` prop. + +Now `` component accepts `options.title` prop that can be used to set app icon and app name globally. By default these values will be accessible through `useRefineOptions` hook and will be used in `` and `` components of the UI packages. + +```tsx +import { Refine } from "@refinedev/core"; + +const MyIcon = () => {/* ... */}; + +const App = () => { + return ( + , + text: "Refine App", + }, + }} + > + {/* ... */} + + ); +}; +``` diff --git a/.changeset/real-turtles-love.md b/.changeset/real-turtles-love.md new file mode 100644 index 0000000000000..a4fb06e4978f5 --- /dev/null +++ b/.changeset/real-turtles-love.md @@ -0,0 +1,5 @@ +--- +"@refinedev/ui-tests": patch +--- + +chore(ui-tests): add test case for globally passed app title and app icon to title tests diff --git a/.changeset/smart-ads-attack.md b/.changeset/smart-ads-attack.md new file mode 100644 index 0000000000000..eb9397d13e9d5 --- /dev/null +++ b/.changeset/smart-ads-attack.md @@ -0,0 +1,7 @@ +--- +"@refinedev/ui-types": patch +--- + +chore(ThemedTitleProps): update icon and text tsdoc descriptions + +Updated TSDoc descriptions of the `icon` and `text` props in the `RefineLayoutThemedTitleProps` interface to provide default values and how they are used in the component. diff --git a/.changeset/three-items-breathe.md b/.changeset/three-items-breathe.md new file mode 100644 index 0000000000000..3005a341743f7 --- /dev/null +++ b/.changeset/three-items-breathe.md @@ -0,0 +1,33 @@ +--- +"@refinedev/chakra-ui": minor +"@refinedev/mantine": minor +"@refinedev/antd": minor +"@refinedev/mui": minor +--- + +feat: use global values by default for app title and app icon + +Now `` component accepts `options.title` prop that can be used to set app icon and app name globally. For `` and `` components, these values will be used by default. While users can use `options.title` to pass global values for app icon and app name, option to override through `` component is still available for users to override these values in specific use cases. + +```tsx +import { Refine } from "@refinedev/core"; + +const MyIcon = () => {/* ... */}; + +const App = () => { + return ( + , + text: "Refine App", + }, + }} + > + {/* ... */} + + ); +}; +``` + +Then, `` and `` components will display `` and `"Refine App"` as app icon and app name respectively. diff --git a/documentation/docs/core/refine-component/index.md b/documentation/docs/core/refine-component/index.md index 089f447fe04ca..7a37f16647486 100644 --- a/documentation/docs/core/refine-component/index.md +++ b/documentation/docs/core/refine-component/index.md @@ -634,6 +634,84 @@ With `@refinedev/core`'s `v4.35.0`, Refine introduced new query and mutation key By default, Refine uses the legacy keys for backward compatibility and in the future versions it will switch to using the new query keys. You can easily switch to using new keys by setting `useNewQueryKeys` to `true`. +### title + +Refine's predefined layout and auth components displays a title for the app, which consists of the app name and an icon. These values can be customized globally by passing `options.title` to the `` component. + +`title` is an object that can have the following properties: + +- `icon`: A React Node to be used as the app icon. By default, it's Refine logo. +- `text`: A React Node to be used as the app name. By default, it's `"Refine Project"`. + +```tsx title="App.tsx" +const App = () => ( + , + text: "Custom App Name", + }, + // highlight-end + }} + /> +); +``` + +If you wish to use separate values for your `` and `` components, you can `Title` prop to override the default title component (which is the `` component from the respective package). + +```tsx +import { Refine } from "@refinedev/core"; +// ThemedTitleV2 accepts `text` and `icon` props with same types as `options.title` +// This component is used in both AuthPage and ThemedLayoutV2 components. +import { ThemedLayoutV2, AuthPage, ThemedTitleV2 } from "@refinedev/antd"; + +const App = () => { + return ( + , + }, + // highlight-end + }} + > + {/* ... */} + ( + } + {...props} + /> + )} + // highlight-end + > + {/* ... */} + + {/* ... */} + } + /> + } + // highlight-end + /> + + ); +}; +``` + ## onLiveEvent Callback to handle all live events. diff --git a/examples/with-nextjs/src/app/blog-posts/layout.tsx b/examples/with-nextjs/src/app/blog-posts/layout.tsx index b9fe2bfff8338..90ce9a6e45094 100644 --- a/examples/with-nextjs/src/app/blog-posts/layout.tsx +++ b/examples/with-nextjs/src/app/blog-posts/layout.tsx @@ -1,5 +1,6 @@ -import { ThemedLayout } from "@components/themed-layout"; import { authProviderServer } from "@providers/auth-provider"; +import { ThemedLayoutV2 } from "@refinedev/antd"; +import { Header } from "@components/header"; import { redirect } from "next/navigation"; import React from "react"; @@ -10,7 +11,7 @@ export default async function Layout({ children }: React.PropsWithChildren) { return redirect(data?.redirectTo || "/login"); } - return {children}; + return {children}; } async function getData() { diff --git a/examples/with-nextjs/src/app/categories/layout.tsx b/examples/with-nextjs/src/app/categories/layout.tsx index b9fe2bfff8338..90ce9a6e45094 100644 --- a/examples/with-nextjs/src/app/categories/layout.tsx +++ b/examples/with-nextjs/src/app/categories/layout.tsx @@ -1,5 +1,6 @@ -import { ThemedLayout } from "@components/themed-layout"; import { authProviderServer } from "@providers/auth-provider"; +import { ThemedLayoutV2 } from "@refinedev/antd"; +import { Header } from "@components/header"; import { redirect } from "next/navigation"; import React from "react"; @@ -10,7 +11,7 @@ export default async function Layout({ children }: React.PropsWithChildren) { return redirect(data?.redirectTo || "/login"); } - return {children}; + return {children}; } async function getData() { diff --git a/examples/with-nextjs/src/components/header/index.tsx b/examples/with-nextjs/src/components/header/index.tsx index ebd1b9705c339..a06972d15cb79 100644 --- a/examples/with-nextjs/src/components/header/index.tsx +++ b/examples/with-nextjs/src/components/header/index.tsx @@ -23,7 +23,7 @@ type IUser = { }; export const Header: React.FC = ({ - sticky, + sticky = true, }) => { const { token } = useToken(); const { data: user } = useGetIdentity(); diff --git a/examples/with-nextjs/src/components/themed-layout/index.tsx b/examples/with-nextjs/src/components/themed-layout/index.tsx deleted file mode 100644 index 8d09b7eeba84c..0000000000000 --- a/examples/with-nextjs/src/components/themed-layout/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { Header } from "@components/header"; -import { ThemedLayoutV2 } from "@refinedev/antd"; -import React from "react"; - -export const ThemedLayout = ({ children }: React.PropsWithChildren) => { - return ( -
}>{children} - ); -}; diff --git a/packages/antd/src/components/themedLayoutV2/title/index.spec.tsx b/packages/antd/src/components/themedLayoutV2/title/index.spec.tsx index 72252e1da909e..5ac3d0c86c616 100644 --- a/packages/antd/src/components/themedLayoutV2/title/index.spec.tsx +++ b/packages/antd/src/components/themedLayoutV2/title/index.spec.tsx @@ -1,8 +1,11 @@ import React from "react"; import { render } from "@testing-library/react"; import { ThemedTitleV2 } from "."; +import { layoutTitleTests } from "@refinedev/ui-tests"; describe("Themed Title", () => { + layoutTitleTests.bind(this)(ThemedTitleV2); + test("should render default text", () => { const { getByText } = render(); expect(getByText("Refine Project")).toBeInTheDocument(); diff --git a/packages/antd/src/components/themedLayoutV2/title/index.tsx b/packages/antd/src/components/themedLayoutV2/title/index.tsx index 0f1da52613988..ef3611006d239 100644 --- a/packages/antd/src/components/themedLayoutV2/title/index.tsx +++ b/packages/antd/src/components/themedLayoutV2/title/index.tsx @@ -1,38 +1,26 @@ import React from "react"; -import { useRouterContext, useRouterType, useLink } from "@refinedev/core"; +import { + useRouterContext, + useRouterType, + useLink, + useRefineOptions, +} from "@refinedev/core"; import { Typography, theme, Space } from "antd"; import type { RefineLayoutThemedTitleProps } from "../types"; -const defaultText = "Refine Project"; - -const defaultIcon = ( - - - - -); - export const ThemedTitleV2: React.FC = ({ collapsed, - icon = defaultIcon, - text = defaultText, + icon: iconFromProps, + text: textFromProps, wrapperStyles, }) => { + const { + title: { icon: defaultIcon, text: defaultText }, + } = useRefineOptions(); + const icon = + typeof iconFromProps === "undefined" ? defaultIcon : iconFromProps; + const text = + typeof textFromProps === "undefined" ? defaultText : textFromProps; const { token } = theme.useToken(); const routerType = useRouterType(); const Link = useLink(); diff --git a/packages/chakra-ui/src/components/layout/title/index.spec.ts b/packages/chakra-ui/src/components/layout/title/index.spec.ts deleted file mode 100644 index 50533d24136cd..0000000000000 --- a/packages/chakra-ui/src/components/layout/title/index.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { layoutTitleTests } from "@refinedev/ui-tests"; -import { Title } from "./index"; - -describe("Layout", () => { - layoutTitleTests.bind(this)(Title); -}); diff --git a/packages/chakra-ui/src/components/themedLayout/title/index.spec.ts b/packages/chakra-ui/src/components/themedLayout/title/index.spec.ts deleted file mode 100644 index 10124adb14375..0000000000000 --- a/packages/chakra-ui/src/components/themedLayout/title/index.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { layoutTitleTests } from "@refinedev/ui-tests"; -import { ThemedTitle } from "./index"; - -describe("Layout", () => { - layoutTitleTests.bind(this)(ThemedTitle); -}); diff --git a/packages/chakra-ui/src/components/themedLayoutV2/title/index.tsx b/packages/chakra-ui/src/components/themedLayoutV2/title/index.tsx index 04d3fc20dcfd0..551ae9bdc415f 100644 --- a/packages/chakra-ui/src/components/themedLayoutV2/title/index.tsx +++ b/packages/chakra-ui/src/components/themedLayoutV2/title/index.tsx @@ -1,38 +1,26 @@ import React from "react"; -import { useRouterContext, useRouterType, useLink } from "@refinedev/core"; +import { + useRouterContext, + useRouterType, + useLink, + useRefineOptions, +} from "@refinedev/core"; import { Link as ChakraLink, Icon, HStack, Heading } from "@chakra-ui/react"; import type { RefineLayoutThemedTitleProps } from "../types"; -const defaultText = "Refine Project"; - -const defaultIcon = ( - - - - -); - export const ThemedTitleV2: React.FC = ({ collapsed, - icon = defaultIcon, - text = defaultText, + icon: iconFromProps, + text: textFromProps, wrapperStyles, }) => { + const { + title: { icon: defaultIcon, text: defaultText }, + } = useRefineOptions(); + const icon = + typeof iconFromProps === "undefined" ? defaultIcon : iconFromProps; + const text = + typeof textFromProps === "undefined" ? defaultText : textFromProps; const routerType = useRouterType(); const Link = useLink(); const { Link: LegacyLink } = useRouterContext(); diff --git a/packages/core/src/contexts/refine/index.tsx b/packages/core/src/contexts/refine/index.tsx index aa76518259ef1..33b338575799f 100644 --- a/packages/core/src/contexts/refine/index.tsx +++ b/packages/core/src/contexts/refine/index.tsx @@ -13,6 +13,32 @@ import type { import { LoginPage as DefaultLoginPage } from "@components/pages"; +const defaultTitle: IRefineContextOptions["title"] = { + icon: ( + + ), + text: "Refine Project", +}; + export const defaultRefineOptions: IRefineContextOptions = { mutationMode: "pessimistic", syncWithLocation: false, @@ -33,6 +59,7 @@ export const defaultRefineOptions: IRefineContextOptions = { singular: pluralize.singular, }, disableServerSideValidation: false, + title: defaultTitle, }; export const RefineContext = React.createContext({ diff --git a/packages/core/src/contexts/refine/types.ts b/packages/core/src/contexts/refine/types.ts index f11542b565276..a06dc808f175e 100644 --- a/packages/core/src/contexts/refine/types.ts +++ b/packages/core/src/contexts/refine/types.ts @@ -96,6 +96,14 @@ export interface IRefineOptions { */ projectId?: string; useNewQueryKeys?: boolean; + /** + * Icon and name for the app title. These values are used as default values in the and components. + * By default, `icon` is the Refine logo and `text` is "Refine Project". + */ + title?: { + icon?: React.ReactNode; + text?: React.ReactNode; + }; } export interface IRefineContextOptions { @@ -115,6 +123,10 @@ export interface IRefineContextOptions { disableServerSideValidation: boolean; projectId?: string; useNewQueryKeys?: boolean; + title: { + icon?: React.ReactNode; + text?: React.ReactNode; + }; } export interface IRefineContext { diff --git a/packages/core/src/definitions/helpers/handleRefineOptions/index.spec.ts b/packages/core/src/definitions/helpers/handleRefineOptions/index.spec.ts index 8f03e2653b0e2..162a5762500dc 100644 --- a/packages/core/src/definitions/helpers/handleRefineOptions/index.spec.ts +++ b/packages/core/src/definitions/helpers/handleRefineOptions/index.spec.ts @@ -61,6 +61,10 @@ describe("handleRefineOptions", () => { singular: expect.any(Function), }, disableServerSideValidation: false, + title: expect.objectContaining({ + icon: expect.any(Object), + text: "Refine Project", + }), }); expect(disableTelemetryWithDefault).toBe(true); expect(reactQueryWithDefaults).toEqual({ @@ -127,6 +131,10 @@ describe("handleRefineOptions", () => { singular: expect.any(Function), }, disableServerSideValidation: false, + title: expect.objectContaining({ + icon: expect.any(Object), + text: "Refine Project", + }), }); expect(disableTelemetryWithDefault).toBe(true); expect(reactQueryWithDefaults).toEqual({ @@ -177,6 +185,10 @@ describe("handleRefineOptions", () => { singular: expect.any(Function), }, disableServerSideValidation: false, + title: expect.objectContaining({ + icon: expect.any(Object), + text: "Refine Project", + }), }); expect(disableTelemetryWithDefault).toBe(true); expect(reactQueryWithDefaults).toEqual({ @@ -232,4 +244,49 @@ describe("handleRefineOptions", () => { expect(optionsWithDefaults.projectId).toEqual("test"); }); + + it("it should return title", () => { + const options: IRefineOptions = { + title: { + icon: "My Icon", + text: "My Project", + }, + }; + + const { optionsWithDefaults } = handleRefineOptions({ options }); + + expect(optionsWithDefaults.title).toEqual( + expect.objectContaining({ icon: "My Icon", text: "My Project" }), + ); + }); + + it("it should return modified title partially", () => { + const options: IRefineOptions = { + title: { + icon: undefined, + text: "My Project", + }, + }; + + const { optionsWithDefaults } = handleRefineOptions({ options }); + + expect(optionsWithDefaults.title).toEqual( + expect.objectContaining({ icon: expect.any(Object), text: "My Project" }), + ); + }); + + it("it should accept null values for title", () => { + const options: IRefineOptions = { + title: { + icon: null, + text: "My Project", + }, + }; + + const { optionsWithDefaults } = handleRefineOptions({ options }); + + expect(optionsWithDefaults.title).toEqual( + expect.objectContaining({ icon: null, text: "My Project" }), + ); + }); }); diff --git a/packages/core/src/definitions/helpers/handleRefineOptions/index.ts b/packages/core/src/definitions/helpers/handleRefineOptions/index.ts index 2c6ade8e2359d..1aff8041a5d2f 100644 --- a/packages/core/src/definitions/helpers/handleRefineOptions/index.ts +++ b/packages/core/src/definitions/helpers/handleRefineOptions/index.ts @@ -87,6 +87,16 @@ export const handleRefineOptions = ({ defaultRefineOptions.disableServerSideValidation, projectId: options?.projectId, useNewQueryKeys: options?.useNewQueryKeys, + title: { + icon: + typeof options?.title?.icon === "undefined" + ? defaultRefineOptions.title.icon + : options?.title?.icon, + text: + typeof options?.title?.text === "undefined" + ? defaultRefineOptions.title.text + : options?.title?.text, + }, }; const disableTelemetryWithDefault = diff --git a/packages/mantine/src/components/layout/title/index.spec.ts b/packages/mantine/src/components/layout/title/index.spec.ts deleted file mode 100644 index 50533d24136cd..0000000000000 --- a/packages/mantine/src/components/layout/title/index.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { layoutTitleTests } from "@refinedev/ui-tests"; -import { Title } from "./index"; - -describe("Layout", () => { - layoutTitleTests.bind(this)(Title); -}); diff --git a/packages/mantine/src/components/themedLayout/title/index.spec.ts b/packages/mantine/src/components/themedLayout/title/index.spec.ts deleted file mode 100644 index ca15a3f490c1f..0000000000000 --- a/packages/mantine/src/components/themedLayout/title/index.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { layoutTitleTests } from "@refinedev/ui-tests"; -import { ThemedTitle } from "./index"; - -describe("ThemedTitleTitle", () => { - layoutTitleTests.bind(this)(ThemedTitle); -}); diff --git a/packages/mantine/src/components/themedLayoutV2/title/index.tsx b/packages/mantine/src/components/themedLayoutV2/title/index.tsx index 68b33cfb7a6b0..2f62224b5c21f 100644 --- a/packages/mantine/src/components/themedLayoutV2/title/index.tsx +++ b/packages/mantine/src/components/themedLayoutV2/title/index.tsx @@ -1,38 +1,26 @@ import React from "react"; -import { useRouterContext, useRouterType, useLink } from "@refinedev/core"; +import { + useRouterContext, + useRouterType, + useLink, + useRefineOptions, +} from "@refinedev/core"; import { Center, Text, useMantineTheme } from "@mantine/core"; import type { RefineLayoutThemedTitleProps } from "../types"; -const defaultText = "Refine Project"; - -const defaultIcon = ( - - - - -); - export const ThemedTitleV2: React.FC = ({ collapsed, - icon = defaultIcon, - text = defaultText, + icon: iconFromProps, + text: textFromProps, wrapperStyles = {}, }) => { + const { + title: { icon: defaultIcon, text: defaultText }, + } = useRefineOptions(); + const icon = + typeof iconFromProps === "undefined" ? defaultIcon : iconFromProps; + const text = + typeof textFromProps === "undefined" ? defaultText : textFromProps; const theme = useMantineTheme(); const routerType = useRouterType(); const Link = useLink(); diff --git a/packages/mui/src/components/themedLayoutV2/title/index.spec.tsx b/packages/mui/src/components/themedLayoutV2/title/index.spec.tsx new file mode 100644 index 0000000000000..c1bf24e85a054 --- /dev/null +++ b/packages/mui/src/components/themedLayoutV2/title/index.spec.tsx @@ -0,0 +1,6 @@ +import { layoutTitleTests } from "@refinedev/ui-tests"; +import { ThemedTitleV2 } from "./index"; + +describe("ThemedTitleV2", () => { + layoutTitleTests.bind(this)(ThemedTitleV2); +}); diff --git a/packages/mui/src/components/themedLayoutV2/title/index.tsx b/packages/mui/src/components/themedLayoutV2/title/index.tsx index c4c2e1c70808a..17cc8a9230c96 100644 --- a/packages/mui/src/components/themedLayoutV2/title/index.tsx +++ b/packages/mui/src/components/themedLayoutV2/title/index.tsx @@ -1,5 +1,10 @@ import React from "react"; -import { useRouterContext, useLink, useRouterType } from "@refinedev/core"; +import { + useRouterContext, + useLink, + useRouterType, + useRefineOptions, +} from "@refinedev/core"; import MuiLink from "@mui/material/Link"; import SvgIcon from "@mui/material/SvgIcon"; @@ -7,36 +12,19 @@ import Typography from "@mui/material/Typography"; import type { RefineLayoutThemedTitleProps } from "../types"; -const defaultText = "Refine Project"; - -const defaultIcon = ( - - - - -); - export const ThemedTitleV2: React.FC = ({ collapsed, wrapperStyles, - icon = defaultIcon, - text = defaultText, + icon: iconFromProps, + text: textFromProps, }) => { + const { + title: { icon: defaultIcon, text: defaultText }, + } = useRefineOptions(); + const icon = + typeof iconFromProps === "undefined" ? defaultIcon : iconFromProps; + const text = + typeof textFromProps === "undefined" ? defaultText : textFromProps; const routerType = useRouterType(); const Link = useLink(); const { Link: LegacyLink } = useRouterContext(); diff --git a/packages/ui-tests/src/test/index.tsx b/packages/ui-tests/src/test/index.tsx index 725c12ff90f00..7dc8c0b28ca2f 100644 --- a/packages/ui-tests/src/test/index.tsx +++ b/packages/ui-tests/src/test/index.tsx @@ -13,6 +13,7 @@ import type { IResourceItem, RouterBindings, IRouterContext, + IRefineOptions, } from "@refinedev/core"; /* interface ITestWrapperProps { @@ -42,6 +43,7 @@ export interface ITestWrapperProps { routerProvider?: RouterBindings; routerInitialEntries?: string[]; DashboardPage?: React.FC; + options?: IRefineOptions; } export const TestWrapper: (props: ITestWrapperProps) => React.FC = ({ @@ -56,6 +58,7 @@ export const TestWrapper: (props: ITestWrapperProps) => React.FC = ({ i18nProvider, routerProvider, legacyRouterProvider, + options, }) => { // Previously, MemoryRouter was used in this wrapper. However, the // recommendation by react-router developers (see @@ -75,6 +78,7 @@ export const TestWrapper: (props: ITestWrapperProps) => React.FC = ({ component", () => { + const { getByTestId } = render(, { + wrapper: TestWrapper({ + options: { + title: { + text:
My Company
, + icon:
, + }, + }, + }), + }); + + expect(getByTestId("my-company-name")).toBeInTheDocument(); + expect(getByTestId("my-company-logo")).toBeInTheDocument(); + }); }); }; diff --git a/packages/ui-types/src/types/layout.tsx b/packages/ui-types/src/types/layout.tsx index fcbe27ec026b4..54d5dbe26337f 100644 --- a/packages/ui-types/src/types/layout.tsx +++ b/packages/ui-types/src/types/layout.tsx @@ -57,7 +57,20 @@ export type RefineThemedLayoutHeaderProps = RefineLayoutHeaderProps & { }; export type RefineLayoutThemedTitleProps = RefineLayoutTitleProps & { + /** + * + * Icon element to be displayed in the title. + * + * Default: Refine Icon + * + * ![Refine](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZGF0YS10ZXN0aWQ9InJlZmluZS1sb2dvIiBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTEzLjguNGE0IDQgMCAwIDAtMy42IDBsLTggNEE0IDQgMCAwIDAgMCA4djhhNCA0IDAgMCAwIDIuMiAzLjZsOCA0YTQgNCAwIDAgMCAzLjYgMGw4LTRBNCA0IDAgMCAwIDI0IDE2VjhhNCA0IDAgMCAwLTIuMi0zLjZsLTgtNHpNOCA4YTQgNCAwIDEgMSA4IDB2OGE0IDQgMCAwIDEtOCAwVjh6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xNCA4YTIgMiAwIDEgMS00IDAgMiAyIDAgMCAxIDQgMHoiLz48L3N2Zz4=) + */ icon?: React.ReactNode; + /** + * Text to be displayed in the title. + * + * @default "Refine Project" + */ text?: React.ReactNode; wrapperStyles?: React.CSSProperties; };