Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DashboardLayout] Add homeUrl to branding and appTitle slot #4477

Merged
merged 13 commits into from
Dec 8, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function DashboardLayoutBranding(props) {
branding={{
logo: <img src="https://mui.com/static/logo.png" alt="MUI logo" />,
title: 'MUI',
homeUrl: '/toolpad/core/introduction',
}}
router={router}
theme={demoTheme}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default function DashboardLayoutBranding(props: DemoProps) {
branding={{
logo: <img src="https://mui.com/static/logo.png" alt="MUI logo" />,
title: 'MUI',
homeUrl: '/toolpad/core/introduction',
}}
router={router}
theme={demoTheme}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
branding={{
logo: <img src="https://mui.com/static/logo.png" alt="MUI logo" />,
title: 'MUI',
homeUrl: '/toolpad/core/introduction',
}}
router={router}
theme={demoTheme}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Chip from '@mui/material/Chip';
import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import CloudCircleIcon from '@mui/icons-material/CloudCircle';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import SearchIcon from '@mui/icons-material/Search';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout';
Expand Down Expand Up @@ -119,6 +122,19 @@ SidebarFooter.propTypes = {
mini: PropTypes.bool.isRequired,
};

function CustomAppTitle() {
return (
<Stack direction="row" alignItems="center" spacing={2}>
<CloudCircleIcon fontSize="large" color="primary" />
<Typography variant="h6">My App</Typography>
<Chip size="small" label="BETA" color="info" />
<Tooltip title="Connected to production">
<CheckCircleIcon color="success" fontSize="small" />
</Tooltip>
</Stack>
);
}

function DashboardLayoutSlots(props) {
const { window } = props;

Expand All @@ -136,6 +152,7 @@ function DashboardLayoutSlots(props) {
>
<DashboardLayout
slots={{
appTitle: CustomAppTitle,
toolbarActions: ToolbarActionsSearch,
sidebarFooter: SidebarFooter,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import * as React from 'react';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Chip from '@mui/material/Chip';
import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import CloudCircleIcon from '@mui/icons-material/CloudCircle';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import SearchIcon from '@mui/icons-material/Search';
import { AppProvider, type Navigation } from '@toolpad/core/AppProvider';
import {
Expand Down Expand Up @@ -114,6 +117,19 @@ function SidebarFooter({ mini }: SidebarFooterProps) {
);
}

function CustomAppTitle() {
return (
<Stack direction="row" alignItems="center" spacing={2}>
<CloudCircleIcon fontSize="large" color="primary" />
<Typography variant="h6">My App</Typography>
<Chip size="small" label="BETA" color="info" />
<Tooltip title="Connected to production">
<CheckCircleIcon color="success" fontSize="small" />
</Tooltip>
</Stack>
);
}

interface DemoProps {
/**
* Injected by the documentation to work in an iframe.
Expand All @@ -139,6 +155,7 @@ export default function DashboardLayoutSlots(props: DemoProps) {
>
<DashboardLayout
slots={{
appTitle: CustomAppTitle,
toolbarActions: ToolbarActionsSearch,
sidebarFooter: SidebarFooter,
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<DashboardLayout
slots={{
appTitle: CustomAppTitle,
toolbarActions: ToolbarActionsSearch,
sidebarFooter: SidebarFooter,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ If application [themes](https://mui.com/toolpad/core/react-app-provider/#theming

Some elements of the `DashboardLayout` can be configured to match your personalized brand.

This can be done via the `branding` prop in the [AppProvider](https://mui.com/toolpad/core/react-app-provider/), which allows for setting a custom `logo` image or `title` text in the page header.
This can be done via the `branding` prop in the [AppProvider](https://mui.com/toolpad/core/react-app-provider/), which allows for setting a custom `logo` image, a custom `title` text in the page header, and a custom `homeUrl` which the branding component leads to on click.

{{"demo": "DashboardLayoutBranding.js", "height": 400, "iframe": true}}

:::info
You may also override the default branding by passing in your own component to the `appTitle` slot, as shown in the [Slots section](#slots).
:::

## Navigation

The `navigation` prop in the [AppProvider](https://mui.com/toolpad/core/react-app-provider/) allows for setting any type of navigation structure in the `DashboardLayout` sidebar by including different navigation elements as building blocks in any order.
Expand Down Expand Up @@ -145,6 +149,8 @@ The use of an `iframe` may cause some spacing issues in the following demo.
Certain areas of the layout can be replaced with custom components by using the `slots` and `slotProps` props.
Some possibly useful slots:

- `appTitle`: allows you to customize the app title section in the layout header.

- `toolbarActions`: allows you to add new items to the toolbar in the header, such as a search bar or button. The default `ThemeSwitcher` component can be imported and used if you wish to do so, as shown in the example below.

- `sidebarFooter`: allows you to add footer content in the sidebar.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useDemoRouter } from '@toolpad/core/internal';
import { PageContainer, PageContainerToolbar } from '@toolpad/core/PageContainer';
import { AppProvider } from '@toolpad/core/AppProvider';
Expand All @@ -19,12 +20,14 @@ const NAVIGATION = [
];

// preview-start
function PageToolbar() {
function PageToolbar({ status }) {
return (
<PageContainerToolbar>
<p>Current status: {status}</p>
<Button startIcon={<FileDownloadIcon />} color="inherit">
Export
</Button>

<DateRangePicker
sx={{ width: 220 }}
defaultValue={[dayjs(), dayjs().add(14, 'day')]}
Expand All @@ -37,16 +40,25 @@ function PageToolbar() {
}
// preview-end

PageToolbar.propTypes = {
status: PropTypes.string.isRequired,
};

export default function ActionsPageContainer() {
const router = useDemoRouter();
const status = 'supesh';

const PageToolbarComponent = React.useCallback(
() => <PageToolbar status={status} />,
[status],
);
const theme = useTheme();

return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<AppProvider navigation={NAVIGATION} router={router} theme={theme}>
<Paper sx={{ width: '100%' }}>
<PageContainer slots={{ toolbar: PageToolbar }}>
<PageContainer slots={{ toolbar: PageToolbarComponent }}>
<PageContent />
</PageContainer>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ const NAVIGATION = [
];

// preview-start
function PageToolbar() {
function PageToolbar({ status }: { status: string }) {
return (
<PageContainerToolbar>
<p>Current status: {status}</p>
<Button startIcon={<FileDownloadIcon />} color="inherit">
Export
</Button>

<DateRangePicker
sx={{ width: 220 }}
defaultValue={[dayjs(), dayjs().add(14, 'day')]}
Expand All @@ -39,14 +41,19 @@ function PageToolbar() {

export default function ActionsPageContainer() {
const router = useDemoRouter();
const status = 'supesh';

const PageToolbarComponent = React.useCallback(
() => <PageToolbar status={status} />,
[status],
);
const theme = useTheme();

return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<AppProvider navigation={NAVIGATION} router={router} theme={theme}>
<Paper sx={{ width: '100%' }}>
<PageContainer slots={{ toolbar: PageToolbar }}>
<PageContainer slots={{ toolbar: PageToolbarComponent }}>
<PageContent />
</PageContainer>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
function PageToolbar() {
function PageToolbar({ status }: { status: string }) {
return (
<PageContainerToolbar>
<p>Current status: {status}</p>
<Button startIcon={<FileDownloadIcon />} color="inherit">
Export
</Button>

<DateRangePicker
sx={{ width: 220 }}
defaultValue={[dayjs(), dayjs().add(14, 'day')]}
Expand Down
5 changes: 4 additions & 1 deletion docs/pages/toolpad/core/api/app-provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"default": "null"
},
"branding": {
"type": { "name": "shape", "description": "{ logo?: node, title?: string }" },
"type": {
"name": "shape",
"description": "{ homeUrl?: string, logo?: node, title?: string }"
},
"default": "null"
},
"navigation": {
Expand Down
15 changes: 12 additions & 3 deletions docs/pages/toolpad/core/api/dashboard-layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"props": {
"children": { "type": { "name": "node" }, "required": true },
"branding": {
"type": { "name": "shape", "description": "{ logo?: node, title?: string }" },
"type": {
"name": "shape",
"description": "{ homeUrl?: string, logo?: node, title?: string }"
},
"default": "null"
},
"defaultSidebarCollapsed": { "type": { "name": "bool" }, "default": "false" },
Expand All @@ -15,14 +18,14 @@
"slotProps": {
"type": {
"name": "shape",
"description": "{ sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
"description": "{ appTitle?: { branding?: { homeUrl?: string, logo?: node, title?: string } }, sidebarFooter?: { mini: bool }, toolbarAccount?: { localeText?: { iconButtonAriaLabel?: string, signInLabel?: string, signOutLabel?: string }, slotProps?: { popover?: object, popoverContent?: object, preview?: object, signInButton?: object, signOutButton?: object }, slots?: { popover?: elementType, popoverContent?: elementType, preview?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
"description": "{ sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }"
"description": "{ appTitle?: elementType, sidebarFooter?: elementType, toolbarAccount?: elementType, toolbarActions?: elementType }"
},
"default": "{}",
"additionalInfo": { "slotsApi": true }
Expand All @@ -41,6 +44,12 @@
"import { DashboardLayout } from '@toolpad/core';"
],
"slots": [
{
"name": "appTitle",
"description": "The component used for the app title section in the layout header.",
"default": "Link",
"class": null
},
{
"name": "toolbarActions",
"description": "The toolbar actions component used in the layout header.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"classDescriptions": {},
"slotDescriptions": {
"appTitle": "The component used for the app title section in the layout header.",
"sidebarFooter": "Optional footer component used in the layout sidebar.",
"toolbarAccount": "The toolbar account component used in the layout header.",
"toolbarActions": "The toolbar actions component used in the layout header."
Expand Down
2 changes: 2 additions & 0 deletions packages/toolpad-core/src/AppProvider/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Router {
export interface Branding {
title?: string;
logo?: React.ReactNode;
homeUrl?: string;
}

export interface NavigationPageItem {
Expand Down Expand Up @@ -193,6 +194,7 @@ AppProvider.propTypes /* remove-proptypes */ = {
* @default null
*/
branding: PropTypes.shape({
homeUrl: PropTypes.string,
logo: PropTypes.node,
title: PropTypes.string,
}),
Expand Down
47 changes: 47 additions & 0 deletions packages/toolpad-core/src/DashboardLayout/AppTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import { styled, useTheme } from '@mui/material';
import { Link } from '../shared/Link';
import { ToolpadLogo } from './ToolpadLogo';
import { type Branding } from '../AppProvider';
import { useApplicationTitle } from '../shared/branding';

const LogoContainer = styled('div')({
position: 'relative',
height: 40,
'& img': {
maxHeight: 40,
},
});

export interface AppTitleProps {
branding?: Branding;
}

/**
* @ignore - internal component.
*/
export function AppTitle(props: AppTitleProps) {
const theme = useTheme();
const defaultTitle = useApplicationTitle();
const title = props?.branding?.title ?? defaultTitle;
return (
<Link href={props?.branding?.homeUrl ?? '/'} style={{ textDecoration: 'none' }}>
<Stack direction="row" alignItems="center">
<LogoContainer>{props?.branding?.logo ?? <ToolpadLogo size={40} />}</LogoContainer>
<Typography
variant="h6"
sx={{
color: (theme.vars ?? theme).palette.primary.main,
fontWeight: '700',
ml: 1,
whiteSpace: 'nowrap',
}}
>
{title}
</Typography>
</Stack>
</Link>
);
}
Loading
Loading