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

[core] Add toolbarActions and toolbarAccount slots to DashboardLayout #3984

Merged
merged 13 commits into from
Aug 29, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import SearchIcon from '@mui/icons-material/Search';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';

const NAVIGATION = [
{
kind: 'header',
title: 'Main items',
},
{
segment: 'dashboard',
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
];

const demoTheme = createTheme({
cssVariables: {
colorSchemeSelector: 'data-toolpad-color-scheme',
},
colorSchemes: { light: true, dark: true },
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 600,
lg: 1200,
xl: 1536,
},
},
});

function DemoPageContent({ pathname }) {
return (
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<Typography>Dashboard content for {pathname}</Typography>
</Box>
);
}

DemoPageContent.propTypes = {
pathname: PropTypes.string.isRequired,
};

function SearchBar() {
return (
<TextField
id="search"
label="Search"
variant="outlined"
size="small"
slotProps={{
input: {
endAdornment: (
<IconButton type="button" aria-label="search" size="small">
<SearchIcon />
</IconButton>
),
sx: { pr: 0.5 },
},
}}
sx={{ mr: 1 }}
/>
);
}

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

const [pathname, setPathname] = React.useState('/dashboard');

const router = React.useMemo(() => {
return {
pathname,
searchParams: new URLSearchParams(),
navigate: (path) => setPathname(String(path)),
};
}, [pathname]);

// Remove this const when copying and pasting into your project.
const demoWindow = window !== undefined ? window() : undefined;

return (
<AppProvider
navigation={NAVIGATION}
router={router}
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout slots={{ toolbarActions: SearchBar }}>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
);
}

DashboardLayoutSlots.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* Remove this when copying and pasting into your project.
*/
window: PropTypes.func,
};

export default DashboardLayoutSlots;
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { createTheme } from '@mui/material/styles';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import SearchIcon from '@mui/icons-material/Search';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';

const NAVIGATION: Navigation = [
{
kind: 'header',
title: 'Main items',
},
{
segment: 'dashboard',
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
];

const demoTheme = createTheme({
cssVariables: {
colorSchemeSelector: 'data-toolpad-color-scheme',
},
colorSchemes: { light: true, dark: true },
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 600,
lg: 1200,
xl: 1536,
},
},
});

function DemoPageContent({ pathname }: { pathname: string }) {
return (
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<Typography>Dashboard content for {pathname}</Typography>
</Box>
);
}

function SearchBar() {
return (
<TextField
id="search"
label="Search"
variant="outlined"
size="small"
slotProps={{
input: {
endAdornment: (
<IconButton type="button" aria-label="search" size="small">
<SearchIcon />
</IconButton>
),
sx: { pr: 0.5 },
},
}}
sx={{ mr: 1 }}
/>
);
}

interface DemoProps {
/**
* Injected by the documentation to work in an iframe.
* Remove this when copying and pasting into your project.
*/
window?: () => Window;
}

export default function DashboardLayoutSlots(props: DemoProps) {
const { window } = props;

const [pathname, setPathname] = React.useState('/dashboard');

const router = React.useMemo<Router>(() => {
return {
pathname,
searchParams: new URLSearchParams(),
navigate: (path) => setPathname(String(path)),
};
}, [pathname]);

// Remove this const when copying and pasting into your project.
const demoWindow = window !== undefined ? window() : undefined;

return (
<AppProvider
navigation={NAVIGATION}
router={router}
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout slots={{ toolbarActions: SearchBar }}>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
</AppProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<DashboardLayout slots={{ toolbarActions: SearchBar }}>
<DemoPageContent pathname={pathname} />
</DashboardLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ The use of an `iframe` may cause some spacing issues in the following demo.
:::

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

## Customization

Some areas of the layout can be replaced with custom components by using the `slots` and `slotProps` props.
For example, this allows you to add new items to the toolbar in the header, such as a search bar.

{{"demo": "DashboardLayoutSlots.js", "height": 400, "iframe": true}}
33 changes: 32 additions & 1 deletion docs/pages/toolpad/core/api/dashboard-layout.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
{
"props": { "children": { "type": { "name": "node" }, "required": true } },
"props": {
"children": { "type": { "name": "node" }, "required": true },
"slotProps": {
"type": {
"name": "shape",
"description": "{ toolbarAccount?: { signInLabel?: string, signOutLabel?: string, slotProps?: { avatar?: object, iconButton?: object, signInButton?: object, signOutButton?: object } }, toolbarActions?: object }"
},
"default": "{}"
},
"slots": {
"type": {
"name": "shape",
"description": "{ toolbarAccount?: elementType, toolbarActions?: elementType }"
},
"default": "{}",
"additionalInfo": { "slotsApi": true }
}
},
"name": "DashboardLayout",
"imports": [
"import { DashboardLayout } from '@toolpad/core/DashboardLayout';",
"import { DashboardLayout } from '@toolpad/core';"
],
"slots": [
{
"name": "toolbarActions",
"description": "The toolbar account component used in the layout header.",
"default": "ToolbarActions",
"class": null
},
{
"name": "toolbarAccount",
"description": "The toolbar account component used in the layout header.",
"default": "Account",
"class": null
}
],
"classes": [],
"spread": true,
"themeDefaultProps": null,
Expand Down
32 changes: 32 additions & 0 deletions docs/pages/toolpad/core/api/sign-in-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@
"import { SignInPage } from '@toolpad/core/SignInPage';",
"import { SignInPage } from '@toolpad/core';"
],
"slots": [
{
"name": "emailField",
"description": "The custom email field component used in the credentials form.",
"default": "TextField",
"class": null
},
{
"name": "passwordField",
"description": "The custom password field component used in the credentials form.",
"default": "TextField",
"class": null
},
{
"name": "submitButton",
"description": "The custom submit button component used in the credentials form.",
"default": "LoadingButton",
"class": null
},
{
"name": "forgotPasswordLink",
"description": "The custom forgot password link component used in the credentials form.",
"default": "Link",
"class": null
},
{
"name": "signUpLink",
"description": "The custom sign up link component used in the credentials form.",
"default": "Link",
"class": null
}
],
"classes": [],
"spread": true,
"themeDefaultProps": false,
Expand Down
12 changes: 10 additions & 2 deletions docs/translations/api-docs/dashboard-layout/dashboard-layout.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"componentDescription": "",
"propDescriptions": { "children": { "description": "The content of the dashboard." } },
"classDescriptions": {}
"propDescriptions": {
"children": { "description": "The content of the dashboard." },
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." }
},
"classDescriptions": {},
"slotDescriptions": {
"toolbarAccount": "The toolbar account component used in the layout header.",
"toolbarActions": "The toolbar account component used in the layout header."
}
}
9 changes: 8 additions & 1 deletion docs/translations/api-docs/sign-in-page/sign-in-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@
"slotProps": { "description": "The props used for each slot inside." },
"slots": { "description": "The components used for each slot inside." }
},
"classDescriptions": {}
"classDescriptions": {},
"slotDescriptions": {
"emailField": "The custom email field component used in the credentials form.",
"forgotPasswordLink": "The custom forgot password link component used in the credentials form.",
"passwordField": "The custom password field component used in the credentials form.",
"signUpLink": "The custom sign up link component used in the credentials form.",
"submitButton": "The custom submit button component used in the credentials form."
}
}
4 changes: 2 additions & 2 deletions packages/toolpad-core/src/AppProvider/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ export interface Authentication {
signOut: () => void;
}

export const SessionContext = React.createContext<Session | null>(null);

export const AuthenticationContext = React.createContext<Authentication | null>(null);

export const SessionContext = React.createContext<Session | null>(null);

export type AppTheme = Theme | { light: Theme; dark: Theme };

export interface AppProviderProps {
Expand Down
Loading
Loading