Skip to content

Commit

Permalink
Allow theme switcher override with slots (#4340)
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira authored Nov 7, 2024
1 parent f1bc2bd commit caf962e
Show file tree
Hide file tree
Showing 18 changed files with 476 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import IconButton from '@mui/material/IconButton';
import Popover from '@mui/material/Popover';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { createTheme, useColorScheme } from '@mui/material/styles';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import SettingsIcon from '@mui/icons-material/Settings';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { useDemoRouter } from '@toolpad/core/internal';

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 CustomThemeSwitcher() {
const { setMode } = useColorScheme();

const handleThemeChange = React.useCallback(
(event) => {
setMode(event.target.value);
},
[setMode],
);

const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const [menuAnchorEl, setMenuAnchorEl] = React.useState(null);

const toggleMenu = React.useCallback(
(event) => {
setMenuAnchorEl(isMenuOpen ? null : event.currentTarget);
setIsMenuOpen((previousIsMenuOpen) => !previousIsMenuOpen);
},
[isMenuOpen],
);

return (
<React.Fragment>
<Tooltip title="Settings" enterDelay={1000}>
<div>
<IconButton type="button" aria-label="settings" onClick={toggleMenu}>
<SettingsIcon />
</IconButton>
</div>
</Tooltip>
<Popover
open={isMenuOpen}
anchorEl={menuAnchorEl}
onClose={toggleMenu}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
disableAutoFocus
>
<Box sx={{ p: 2 }}>
<FormControl>
<FormLabel id="custom-theme-switcher-label">Theme</FormLabel>
<RadioGroup
aria-labelledby="custom-theme-switcher-label"
defaultValue="system"
name="custom-theme-switcher"
onChange={handleThemeChange}
>
<FormControlLabel value="light" control={<Radio />} label="Light" />
<FormControlLabel value="system" control={<Radio />} label="System" />
<FormControlLabel value="dark" control={<Radio />} label="Dark" />
</RadioGroup>
</FormControl>
</Box>
</Popover>
</React.Fragment>
);
}

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

const router = useDemoRouter('/dashboard');

// 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: CustomThemeSwitcher,
}}
>
<DemoPageContent pathname={router.pathname} />
</DashboardLayout>
</AppProvider>
);
}

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

export default DashboardLayoutCustomThemeSwitcher;
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import IconButton from '@mui/material/IconButton';
import Popover from '@mui/material/Popover';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { createTheme, useColorScheme } from '@mui/material/styles';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import SettingsIcon from '@mui/icons-material/Settings';
import { AppProvider, type Navigation } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { useDemoRouter } from '@toolpad/core/internal';

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 CustomThemeSwitcher() {
const { setMode } = useColorScheme();

const handleThemeChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setMode(event.target.value as 'light' | 'dark' | 'system');
},
[setMode],
);

const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const [menuAnchorEl, setMenuAnchorEl] = React.useState<HTMLElement | null>(null);

const toggleMenu = React.useCallback(
(event: React.MouseEvent<HTMLElement>) => {
setMenuAnchorEl(isMenuOpen ? null : event.currentTarget);
setIsMenuOpen((previousIsMenuOpen) => !previousIsMenuOpen);
},
[isMenuOpen],
);

return (
<React.Fragment>
<Tooltip title="Settings" enterDelay={1000}>
<div>
<IconButton type="button" aria-label="settings" onClick={toggleMenu}>
<SettingsIcon />
</IconButton>
</div>
</Tooltip>
<Popover
open={isMenuOpen}
anchorEl={menuAnchorEl}
onClose={toggleMenu}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
disableAutoFocus
>
<Box sx={{ p: 2 }}>
<FormControl>
<FormLabel id="custom-theme-switcher-label">Theme</FormLabel>
<RadioGroup
aria-labelledby="custom-theme-switcher-label"
defaultValue="system"
name="custom-theme-switcher"
onChange={handleThemeChange}
>
<FormControlLabel value="light" control={<Radio />} label="Light" />
<FormControlLabel value="system" control={<Radio />} label="System" />
<FormControlLabel value="dark" control={<Radio />} label="Dark" />
</RadioGroup>
</FormControl>
</Box>
</Popover>
</React.Fragment>
);
}

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 DashboardLayoutCustomThemeSwitcher(props: DemoProps) {
const { window } = props;

const router = useDemoRouter('/dashboard');

// 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: CustomThemeSwitcher,
}}
>
<DemoPageContent pathname={router.pathname} />
</DashboardLayout>
</AppProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<DashboardLayout
slots={{
toolbarActions: CustomThemeSwitcher,
}}
>
<DemoPageContent pathname={router.pathname} />
</DashboardLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
Expand All @@ -10,7 +11,7 @@ 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';
import { DashboardLayout, ThemeSwitcher } from '@toolpad/core/DashboardLayout';
import { useDemoRouter } from '@toolpad/core/internal';

const NAVIGATION = [
Expand Down Expand Up @@ -66,9 +67,9 @@ DemoPageContent.propTypes = {
pathname: PropTypes.string.isRequired,
};

function Search() {
function ToolbarActionsSearch() {
return (
<React.Fragment>
<Stack direction="row">
<Tooltip title="Search" enterDelay={1000}>
<div>
<IconButton
Expand Down Expand Up @@ -98,7 +99,8 @@ function Search() {
}}
sx={{ display: { xs: 'none', md: 'inline-block' }, mr: 1 }}
/>
</React.Fragment>
<ThemeSwitcher />
</Stack>
);
}

Expand Down Expand Up @@ -133,7 +135,10 @@ function DashboardLayoutSlots(props) {
window={demoWindow}
>
<DashboardLayout
slots={{ toolbarActions: Search, sidebarFooter: SidebarFooter }}
slots={{
toolbarActions: ToolbarActionsSearch,
sidebarFooter: SidebarFooter,
}}
>
<DemoPageContent pathname={router.pathname} />
</DashboardLayout>
Expand Down
Loading

0 comments on commit caf962e

Please sign in to comment.