Skip to content

Commit

Permalink
feat(core): allow passing global app name and app icon (#5993)
Browse files Browse the repository at this point in the history
  • Loading branch information
aliemir authored and BatuhanW committed Jun 4, 2024
1 parent 6584d6f commit 00eb9f3
Show file tree
Hide file tree
Showing 26 changed files with 368 additions and 148 deletions.
30 changes: 30 additions & 0 deletions .changeset/mighty-clouds-help.md
Original file line number Diff line number Diff line change
@@ -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 `<Refine />` component's `options` prop.

Now `<Refine />` 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 `<ThemedLayoutV2 />` and `<AuthPage />` components of the UI packages.

```tsx
import { Refine } from "@refinedev/core";

const MyIcon = () => <svg>{/* ... */}</svg>;

const App = () => {
return (
<Refine
options={{
title: {
icon: <MyIcon />,
text: "Refine App",
},
}}
>
{/* ... */}
</Refine>
);
};
```
5 changes: 5 additions & 0 deletions .changeset/real-turtles-love.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions .changeset/smart-ads-attack.md
Original file line number Diff line number Diff line change
@@ -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.
33 changes: 33 additions & 0 deletions .changeset/three-items-breathe.md
Original file line number Diff line number Diff line change
@@ -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 `<Refine />` component accepts `options.title` prop that can be used to set app icon and app name globally. For `<ThemedLayoutV2 />` and `<AuthPage />` 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 `<ThemedTitleV2 />` component is still available for users to override these values in specific use cases.

```tsx
import { Refine } from "@refinedev/core";

const MyIcon = () => <svg>{/* ... */}</svg>;

const App = () => {
return (
<Refine
options={{
title: {
icon: <MyIcon />,
text: "Refine App",
},
}}
>
{/* ... */}
</Refine>
);
};
```

Then, `<ThemedLayoutV2 />` and `<AuthPage />` components will display `<MyIcon />` and `"Refine App"` as app icon and app name respectively.
78 changes: 78 additions & 0 deletions documentation/docs/core/refine-component/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Refine>` 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 = () => (
<Refine
options={{
// highlight-start
title: {
icon: <CustomIcon />,
text: "Custom App Name",
},
// highlight-end
}}
/>
);
```

If you wish to use separate values for your `<AuthPage />` and `<ThemedLayoutV2 />` components, you can `Title` prop to override the default title component (which is the `<ThemedTitleV2 />` 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 (
<Refine
options={{
// highlight-start
title: {
text: "My App",
icon: <IconA />,
},
// highlight-end
}}
>
{/* ... */}
<ThemedLayoutV2
// highlight-start
Title={(props) => (
<ThemedTitleV2
// These values will override the global title values
text="A Different Value"
icon={<IconB />}
{...props}
/>
)}
// highlight-end
>
{/* ... */}
</ThemedLayoutV2>
{/* ... */}
<AuthPage
type="login"
// highlight-start
title={
<ThemedTitleV2
collapsed={false}
// These values will override the global title values
text="A Different Value"
icon={<IconC />}
/>
}
// highlight-end
/>
</Refine>
);
};
```

## onLiveEvent

Callback to handle all live events.
Expand Down
5 changes: 3 additions & 2 deletions examples/with-nextjs/src/app/blog-posts/layout.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -10,7 +11,7 @@ export default async function Layout({ children }: React.PropsWithChildren) {
return redirect(data?.redirectTo || "/login");
}

return <ThemedLayout>{children}</ThemedLayout>;
return <ThemedLayoutV2 Header={Header}>{children}</ThemedLayoutV2>;
}

async function getData() {
Expand Down
5 changes: 3 additions & 2 deletions examples/with-nextjs/src/app/categories/layout.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -10,7 +11,7 @@ export default async function Layout({ children }: React.PropsWithChildren) {
return redirect(data?.redirectTo || "/login");
}

return <ThemedLayout>{children}</ThemedLayout>;
return <ThemedLayoutV2 Header={Header}>{children}</ThemedLayoutV2>;
}

async function getData() {
Expand Down
2 changes: 1 addition & 1 deletion examples/with-nextjs/src/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type IUser = {
};

export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
sticky,
sticky = true,
}) => {
const { token } = useToken();
const { data: user } = useGetIdentity<IUser>();
Expand Down
11 changes: 0 additions & 11 deletions examples/with-nextjs/src/components/themed-layout/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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(<ThemedTitleV2 collapsed={false} />);
expect(getByText("Refine Project")).toBeInTheDocument();
Expand Down
42 changes: 15 additions & 27 deletions packages/antd/src/components/themedLayoutV2/title/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="refine-logo"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.7889 0.422291C12.6627 -0.140764 11.3373 -0.140764 10.2111 0.422291L2.21115 4.42229C0.85601 5.09986 0 6.48491 0 8V16C0 17.5151 0.85601 18.9001 2.21115 19.5777L10.2111 23.5777C11.3373 24.1408 12.6627 24.1408 13.7889 23.5777L21.7889 19.5777C23.144 18.9001 24 17.5151 24 16V8C24 6.48491 23.144 5.09986 21.7889 4.42229L13.7889 0.422291ZM8 8C8 5.79086 9.79086 4 12 4C14.2091 4 16 5.79086 16 8V16C16 18.2091 14.2091 20 12 20C9.79086 20 8 18.2091 8 16V8Z"
fill="currentColor"
/>
<path
d="M14 8C14 9.10457 13.1046 10 12 10C10.8954 10 10 9.10457 10 8C10 6.89543 10.8954 6 12 6C13.1046 6 14 6.89543 14 8Z"
fill="currentColor"
/>
</svg>
);

export const ThemedTitleV2: React.FC<RefineLayoutThemedTitleProps> = ({
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();
Expand Down
6 changes: 0 additions & 6 deletions packages/chakra-ui/src/components/layout/title/index.spec.ts

This file was deleted.

This file was deleted.

42 changes: 15 additions & 27 deletions packages/chakra-ui/src/components/themedLayoutV2/title/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="refine-logo"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.7889 0.422291C12.6627 -0.140764 11.3373 -0.140764 10.2111 0.422291L2.21115 4.42229C0.85601 5.09986 0 6.48491 0 8V16C0 17.5151 0.85601 18.9001 2.21115 19.5777L10.2111 23.5777C11.3373 24.1408 12.6627 24.1408 13.7889 23.5777L21.7889 19.5777C23.144 18.9001 24 17.5151 24 16V8C24 6.48491 23.144 5.09986 21.7889 4.42229L13.7889 0.422291ZM8 8C8 5.79086 9.79086 4 12 4C14.2091 4 16 5.79086 16 8V16C16 18.2091 14.2091 20 12 20C9.79086 20 8 18.2091 8 16V8Z"
fill="currentColor"
/>
<path
d="M14 8C14 9.10457 13.1046 10 12 10C10.8954 10 10 9.10457 10 8C10 6.89543 10.8954 6 12 6C13.1046 6 14 6.89543 14 8Z"
fill="currentColor"
/>
</svg>
);

export const ThemedTitleV2: React.FC<RefineLayoutThemedTitleProps> = ({
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();
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/contexts/refine/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ import type {

import { LoginPage as DefaultLoginPage } from "@components/pages";

const defaultTitle: IRefineContextOptions["title"] = {
icon: (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
data-testid="refine-logo"
id="refine-default-logo"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.7889 0.422291C12.6627 -0.140764 11.3373 -0.140764 10.2111 0.422291L2.21115 4.42229C0.85601 5.09986 0 6.48491 0 8V16C0 17.5151 0.85601 18.9001 2.21115 19.5777L10.2111 23.5777C11.3373 24.1408 12.6627 24.1408 13.7889 23.5777L21.7889 19.5777C23.144 18.9001 24 17.5151 24 16V8C24 6.48491 23.144 5.09986 21.7889 4.42229L13.7889 0.422291ZM8 8C8 5.79086 9.79086 4 12 4C14.2091 4 16 5.79086 16 8V16C16 18.2091 14.2091 20 12 20C9.79086 20 8 18.2091 8 16V8Z"
fill="currentColor"
/>
<path
d="M14 8C14 9.10457 13.1046 10 12 10C10.8954 10 10 9.10457 10 8C10 6.89543 10.8954 6 12 6C13.1046 6 14 6.89543 14 8Z"
fill="currentColor"
/>
</svg>
),
text: "Refine Project",
};

export const defaultRefineOptions: IRefineContextOptions = {
mutationMode: "pessimistic",
syncWithLocation: false,
Expand All @@ -33,6 +59,7 @@ export const defaultRefineOptions: IRefineContextOptions = {
singular: pluralize.singular,
},
disableServerSideValidation: false,
title: defaultTitle,
};

export const RefineContext = React.createContext<IRefineContext>({
Expand Down
Loading

0 comments on commit 00eb9f3

Please sign in to comment.