From 38ecc1757c5bc6902a4402f270ac7f1cd6ad4b3d Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Wed, 2 Oct 2024 13:39:47 +0200 Subject: [PATCH 01/43] fix: Improve docs and make API clearer --- ...lots.js => AccountSlotsAccountSwitcher.js} | 6 +- ...ts.tsx => AccountSlotsAccountSwitcher.tsx} | 6 +- ...> AccountSlotsAccountSwitcher.tsx.preview} | 2 +- .../components/account/AccountSlotsInfo.js | 102 +++++++++++++++++ .../components/account/AccountSlotsInfo.tsx | 107 ++++++++++++++++++ .../account/AccountSlotsInfo.tsx.preview | 9 ++ .../core/components/account/CustomMenu.js | 100 +++++++++++++--- .../core/components/account/CustomMenu.tsx | 100 +++++++++++++--- .../core/components/account/account.md | 14 ++- docs/pages/toolpad/core/api/account.json | 4 +- .../toolpad/core/api/dashboard-layout.json | 2 +- .../api-docs/account/account.json | 2 +- packages/toolpad-core/src/Account/Account.tsx | 6 +- .../src/DashboardLayout/DashboardLayout.tsx | 2 +- 14 files changed, 413 insertions(+), 49 deletions(-) rename docs/data/toolpad/core/components/account/{AccountSlots.js => AccountSlotsAccountSwitcher.js} (86%) rename docs/data/toolpad/core/components/account/{AccountSlots.tsx => AccountSlotsAccountSwitcher.tsx} (86%) rename docs/data/toolpad/core/components/account/{AccountSlots.tsx.preview => AccountSlotsAccountSwitcher.tsx.preview} (85%) create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.js create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview diff --git a/docs/data/toolpad/core/components/account/AccountSlots.js b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js similarity index 86% rename from docs/data/toolpad/core/components/account/AccountSlots.js rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js index 31051b0adab..97b3aa14b99 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.js +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; -import CustomMenuItems from './CustomMenu'; +import CustomMenu from './CustomMenu'; const demoSession = { user: { @@ -10,7 +10,7 @@ const demoSession = { }, }; -export default function AccountSlots() { +export default function AccountSlotsAccountSwitcher() { const [session, setSession] = React.useState(demoSession); const authentication = React.useMemo(() => { return { @@ -28,7 +28,7 @@ export default function AccountSlots() { diff --git a/docs/data/toolpad/core/components/account/AccountSlots.tsx b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx similarity index 86% rename from docs/data/toolpad/core/components/account/AccountSlots.tsx rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx index 43f9af3a30f..ea03da06080 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx @@ -5,7 +5,7 @@ import { SessionContext, Session, } from '@toolpad/core'; -import CustomMenuItems from './CustomMenu'; +import CustomMenu from './CustomMenu'; const demoSession = { user: { @@ -15,7 +15,7 @@ const demoSession = { }, }; -export default function AccountSlots() { +export default function AccountSlotsAccountSwitcher() { const [session, setSession] = React.useState(demoSession); const authentication = React.useMemo(() => { return { @@ -33,7 +33,7 @@ export default function AccountSlots() { diff --git a/docs/data/toolpad/core/components/account/AccountSlots.tsx.preview b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview similarity index 85% rename from docs/data/toolpad/core/components/account/AccountSlots.tsx.preview rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview index 5e8ecca9f06..18a06d57ca7 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview @@ -2,7 +2,7 @@ diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js new file mode 100644 index 00000000000..a7fc8e91d3d --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js @@ -0,0 +1,102 @@ +import * as React from 'react'; +import { + Avatar, + Divider, + Button, + Typography, + Stack, + IconButton, +} from '@mui/material'; +import WalletIcon from '@mui/icons-material/AccountBalance'; +import SendIcon from '@mui/icons-material/Send'; +import ShoppingCart from '@mui/icons-material/ShoppingCart'; +import CopyIcon from '@mui/icons-material/ContentCopy'; + +import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +const mockData = { + address: '0x1234...5678', + balance: '1,234.56 ETH', + usdBalance: '$2,345,678.90 USD', +}; + +function CryptoWalletInfo() { + return ( +
+
+
+ + + +
+ + {mockData.address} + + + + + + Main Account + +
+
+
+ + {mockData.balance} + + + {mockData.usdBalance} + +
+ + + + +
+ +
+ ); +} + +export default function AccountSlotsInfo() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx new file mode 100644 index 00000000000..6c7e09dded7 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import { + Avatar, + Divider, + Button, + Typography, + Stack, + IconButton, +} from '@mui/material'; +import WalletIcon from '@mui/icons-material/AccountBalance'; +import SendIcon from '@mui/icons-material/Send'; +import ShoppingCart from '@mui/icons-material/ShoppingCart'; +import CopyIcon from '@mui/icons-material/ContentCopy'; + +import { + Account, + AuthenticationContext, + SessionContext, + Session, +} from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +const mockData = { + address: '0x1234...5678', + balance: '1,234.56 ETH', + usdBalance: '$2,345,678.90 USD', +}; + +function CryptoWalletInfo() { + return ( +
+
+
+ + + +
+ + {mockData.address} + + + + + + Main Account + +
+
+
+ + {mockData.balance} + + + {mockData.usdBalance} + +
+ + + + +
+ +
+ ); +} + +export default function AccountSlotsInfo() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview new file mode 100644 index 00000000000..29ceba32bb0 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/CustomMenu.js b/docs/data/toolpad/core/components/account/CustomMenu.js index 2bbee81379a..c77081db7dd 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.js +++ b/docs/data/toolpad/core/components/account/CustomMenu.js @@ -1,10 +1,36 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { Menu, MenuItem, MenuList, Divider, ListItemIcon } from '@mui/material'; +import { + Menu, + MenuItem, + MenuList, + Divider, + ListItemIcon, + ListItemText, + Typography, + Avatar, +} from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import SettingsIcon from '@mui/icons-material/Settings'; -import AddIcon from '@mui/icons-material/Add'; -function CustomSettingsMenu(props) { +// Function to generate a random color +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i += 1) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const accounts = [ + { id: 1, name: 'John Doe', email: 'john@example.com', color: getRandomColor() }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com', color: getRandomColor() }, + { id: 3, name: 'Bob Johnson', email: 'bob@example.com', color: getRandomColor() }, +]; + +function AccountSwitcher(props) { const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; return ( @@ -24,22 +50,51 @@ function CustomSettingsMenu(props) { }} > { handleEnter(); }} onMouseLeave={handleLeave} > - Profile - My account + + Accounts + + + {accounts.map((account) => ( + + + + {account.name.charAt(0)} + + + + + ))} ); } -CustomSettingsMenu.propTypes = { +AccountSwitcher.propTypes = { anchorEl: PropTypes.object, handleEnter: PropTypes.func.isRequired, handleLeave: PropTypes.func.isRequired, @@ -47,10 +102,10 @@ CustomSettingsMenu.propTypes = { open: PropTypes.bool.isRequired, }; -export default function CustomMenu() { +export default function CustomContent() { const handleMenuNavigation = (route) => () => { console.log( - 'Toolpad Core Account Demo --- CustomMenuItems --- handleMenuNavigation --- route: ', + 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', route, ); }; @@ -93,8 +148,20 @@ export default function CustomMenu() { return ( + + + + Profile + + - + - Add another account + Switch Account - void; @@ -11,7 +21,23 @@ interface CustomSettingsMenuProps { handleEnter: () => void; } -function CustomSettingsMenu(props: CustomSettingsMenuProps) { +// Function to generate a random color +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i += 1) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const accounts = [ + { id: 1, name: 'John Doe', email: 'john@example.com', color: getRandomColor() }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com', color: getRandomColor() }, + { id: 3, name: 'Bob Johnson', email: 'bob@example.com', color: getRandomColor() }, +]; + +function AccountSwitcher(props: AccountSwitcherProps) { const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; return ( @@ -31,25 +57,54 @@ function CustomSettingsMenu(props: CustomSettingsMenuProps) { }} > { handleEnter(); }} onMouseLeave={handleLeave} > - Profile - My account + + Accounts + + + {accounts.map((account) => ( + + + + {account.name.charAt(0)} + + + + + ))} ); } -export default function CustomMenu() { +export default function CustomContent() { const handleMenuNavigation = (route: string) => () => { console.log( - 'Toolpad Core Account Demo --- CustomMenuItems --- handleMenuNavigation --- route: ', + 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', route, ); }; @@ -96,8 +151,20 @@ export default function CustomMenu() { return ( + + + + Profile + + - + - Add another account + Switch Account - - {slots?.menuItems ? : null} + {slots?.content ? : null} {slots?.signOutButton ? ( ) : ( @@ -231,7 +231,7 @@ Account.propTypes /* remove-proptypes */ = { * The components used for each slot inside. */ slots: PropTypes.shape({ - menuItems: PropTypes.elementType, + content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, }), diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index b21df1beee3..e963918e059 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -681,7 +681,7 @@ DashboardLayout.propTypes /* remove-proptypes */ = { signOutButton: PropTypes.object, }), slots: PropTypes.shape({ - menuItems: PropTypes.elementType, + content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, }), From 01c7fa6d31081ef0c8ad4b866477ade43e3334fe Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Wed, 2 Oct 2024 13:58:44 +0200 Subject: [PATCH 02/43] fix: lint --- docs/data/toolpad/core/components/account/CustomMenu.js | 2 +- docs/data/toolpad/core/components/account/CustomMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/toolpad/core/components/account/CustomMenu.js b/docs/data/toolpad/core/components/account/CustomMenu.js index c77081db7dd..6b1e2d34f0f 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.js +++ b/docs/data/toolpad/core/components/account/CustomMenu.js @@ -102,7 +102,7 @@ AccountSwitcher.propTypes = { open: PropTypes.bool.isRequired, }; -export default function CustomContent() { +export default function CustomMenu() { const handleMenuNavigation = (route) => () => { console.log( 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', diff --git a/docs/data/toolpad/core/components/account/CustomMenu.tsx b/docs/data/toolpad/core/components/account/CustomMenu.tsx index a5d18144015..7db92408f3d 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.tsx +++ b/docs/data/toolpad/core/components/account/CustomMenu.tsx @@ -101,7 +101,7 @@ function AccountSwitcher(props: AccountSwitcherProps) { ); } -export default function CustomContent() { +export default function CustomMenu() { const handleMenuNavigation = (route: string) => () => { console.log( 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', From 809860822c3482aef4e9587a15a7a7fa46d4d439 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Wed, 2 Oct 2024 14:00:50 +0200 Subject: [PATCH 03/43] fix: Incorrect default --- docs/pages/toolpad/core/api/account.json | 2 +- packages/toolpad-core/src/Account/Account.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/toolpad/core/api/account.json b/docs/pages/toolpad/core/api/account.json index 0d67c143d07..a3ec92f5492 100644 --- a/docs/pages/toolpad/core/api/account.json +++ b/docs/pages/toolpad/core/api/account.json @@ -33,7 +33,7 @@ { "name": "signOutButton", "description": "The component used for the sign out button.", - "default": "MenuItem", + "default": "Button", "class": null }, { diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index 87000949afb..606d26f2d22 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -35,7 +35,7 @@ export interface AccountSlots { signInButton?: React.ElementType; /** * The component used for the sign out button. - * @default MenuItem + * @default Button */ signOutButton?: React.ElementType; /** From 6c1c07361a2bedc507be7404907d421900d3ebf2 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 03:52:57 +0200 Subject: [PATCH 04/43] fix: Add `userDetailsContainer` proposal --- .../core/components/account/AccountCustom.js | 26 ++-- .../core/components/account/AccountCustom.tsx | 26 ++-- .../account/AccountCustom.tsx.preview | 28 +++-- .../account/AccountCustomUserDetails.js | 113 ++++++++++++++++++ .../account/AccountCustomUserDetails.tsx | 113 ++++++++++++++++++ .../AccountCustomUserDetails.tsx.preview | 5 + .../core/components/account/AccountLocale.js | 46 +++++++ .../core/components/account/AccountLocale.tsx | 51 ++++++++ .../account/AccountLocale.tsx.preview | 6 + .../components/account/AccountSlotsInfo.js | 52 +++++--- .../components/account/AccountSlotsInfo.tsx | 52 +++++--- .../account/AccountSlotsInfo.tsx.preview | 9 -- .../core/components/account/account.md | 22 +++- docs/pages/toolpad/core/api/account.json | 12 +- .../toolpad/core/api/dashboard-layout.json | 2 +- .../api-docs/account/account.json | 5 +- packages/toolpad-core/src/Account/Account.tsx | 69 ++++++----- .../src/DashboardLayout/DashboardLayout.tsx | 2 + 18 files changed, 536 insertions(+), 103 deletions(-) create mode 100644 docs/data/toolpad/core/components/account/AccountCustomUserDetails.js create mode 100644 docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx create mode 100644 docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx.preview create mode 100644 docs/data/toolpad/core/components/account/AccountLocale.js create mode 100644 docs/data/toolpad/core/components/account/AccountLocale.tsx create mode 100644 docs/data/toolpad/core/components/account/AccountLocale.tsx.preview delete mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview diff --git a/docs/data/toolpad/core/components/account/AccountCustom.js b/docs/data/toolpad/core/components/account/AccountCustom.js index a28fc6d7756..81f17802a77 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.js +++ b/docs/data/toolpad/core/components/account/AccountCustom.js @@ -1,5 +1,6 @@ import * as React from 'react'; import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; const demoSession = { @@ -13,6 +14,7 @@ const demoSession = { export default function AccountCustom() { const [session, setSession] = React.useState(demoSession); const [signedOutSession, setSignedOutSession] = React.useState(null); + const theme = useTheme(); const authenticationSignedIn = React.useMemo(() => { return { @@ -60,12 +62,21 @@ export default function AccountCustom() { Signed in + {/* preview-start */} + {/* preview-end */} - {/* preview-start */} - {/* preview-end */} diff --git a/docs/data/toolpad/core/components/account/AccountCustom.tsx b/docs/data/toolpad/core/components/account/AccountCustom.tsx index 9bc161b8c96..759368e8999 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.tsx +++ b/docs/data/toolpad/core/components/account/AccountCustom.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import Typography from '@mui/material/Typography'; +import useTheme from '@mui/material/styles/useTheme'; import { Account, AuthenticationContext, @@ -20,6 +21,7 @@ export default function AccountCustom() { const [signedOutSession, setSignedOutSession] = React.useState( null, ); + const theme = useTheme(); const authenticationSignedIn = React.useMemo(() => { return { @@ -67,12 +69,21 @@ export default function AccountCustom() { Signed in + {/* preview-start */} + {/* preview-end */} - {/* preview-start */} - {/* preview-end */} diff --git a/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview b/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview index 1143a6ea501..5bf00e67417 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview @@ -1,22 +1,32 @@ \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js new file mode 100644 index 00000000000..d2959618b3f --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.js @@ -0,0 +1,113 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Box, Stack, Typography, Avatar, Link } from '@mui/material'; +import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharat@mui.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, + org: { + name: 'MUI Inc.', + url: 'https://mui.com', + logo: 'https://mui.com/static/logo.svg', + }, +}; + +function UserDetailsContainer({ session }) { + if (!session?.user) { + return No user session available; + } + const { name, email, image } = session.user; + const { logo: orgLogo, name: orgName, url: orgUrl } = session.org; + + return ( + + + + + + {name} + + {email} + + + + {session.org && ( + + + This account is managed by + + + + + + {orgName} + + + {orgUrl} + + + + + )} + + + ); +} + +UserDetailsContainer.propTypes = { + session: PropTypes.shape({ + org: PropTypes.shape({ + logo: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + }).isRequired, + user: PropTypes.shape({ + email: PropTypes.string, + id: PropTypes.string, + image: PropTypes.string, + name: PropTypes.string, + }), + }).isRequired, +}; + +export default function AccountCustomUserDetails() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + {/* preview-start */} + + {/* preview-end */} + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx new file mode 100644 index 00000000000..9cbadbd8aee --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { Box, Stack, Typography, Avatar, Link } from '@mui/material'; +import { + Account, + AuthenticationContext, + SessionContext, + Session, +} from '@toolpad/core'; + +interface CustomSession extends Session { + org: { + name: string; + url: string; + logo: string; + }; +} + +const demoSession: CustomSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharat@mui.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, + org: { + name: 'MUI Inc.', + url: 'https://mui.com', + logo: 'https://mui.com/static/logo.svg', + }, +}; + +interface UserDetailsContainerProps { + session: CustomSession; +} + +function UserDetailsContainer({ session }: UserDetailsContainerProps) { + if (!session?.user) { + return No user session available; + } + const { name, email, image } = session.user; + const { logo: orgLogo, name: orgName, url: orgUrl } = session.org; + + return ( + + + + + + {name} + + {email} + + + + {session.org && ( + + + This account is managed by + + + + + + {orgName} + + + {orgUrl} + + + + + )} + + + ); +} + +export default function AccountCustomUserDetails() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + {/* preview-start */} + + {/* preview-end */} + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx.preview b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx.preview new file mode 100644 index 00000000000..a396c739186 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountCustomUserDetails.tsx.preview @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/AccountLocale.js b/docs/data/toolpad/core/components/account/AccountLocale.js new file mode 100644 index 00000000000..9cce8f49e96 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountLocale.js @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +export default function AccountLocale() { + const [session, setSession] = React.useState(demoSession); + + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession({ + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, + }); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + {/* preview-start */} + + {/* preview-end */} + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountLocale.tsx b/docs/data/toolpad/core/components/account/AccountLocale.tsx new file mode 100644 index 00000000000..613169ec221 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountLocale.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { + Account, + AuthenticationContext, + SessionContext, + Session, +} from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +export default function AccountLocale() { + const [session, setSession] = React.useState(demoSession); + + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession({ + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, + }); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + {/* preview-start */} + + {/* preview-end */} + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountLocale.tsx.preview b/docs/data/toolpad/core/components/account/AccountLocale.tsx.preview new file mode 100644 index 00000000000..78c0013b2a5 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountLocale.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js index a7fc8e91d3d..d6ae056ce0e 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js @@ -1,8 +1,8 @@ import * as React from 'react'; import { Avatar, - Divider, Button, + Divider, Typography, Stack, IconButton, @@ -31,13 +31,17 @@ const mockData = { function CryptoWalletInfo() { return (
-
-
- - + + + + -
- + + {mockData.address} @@ -46,30 +50,38 @@ function CryptoWalletInfo() { Main Account -
-
-
+ + + + {mockData.balance} {mockData.usdBalance} -
- + + + - -
+
); @@ -95,6 +107,18 @@ export default function AccountSlotsInfo() { slots={{ content: CryptoWalletInfo, }} + slotProps={{ + userDetailsContainer: { + sx: { + justifyContent: 'flex-start', + '& .Account-userDetailsContainer': { + display: 'flex', + flexDirection: 'column', + rowGap: 0, + }, + }, + }, + }} /> diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx index 6c7e09dded7..a4c1a2e25c2 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Avatar, - Divider, Button, + Divider, Typography, Stack, IconButton, @@ -36,13 +36,17 @@ const mockData = { function CryptoWalletInfo() { return (
-
-
- - + + + + -
- + + {mockData.address} @@ -51,30 +55,38 @@ function CryptoWalletInfo() { Main Account -
-
-
+ + + + {mockData.balance} {mockData.usdBalance} -
- + + + - -
+
); @@ -100,6 +112,18 @@ export default function AccountSlotsInfo() { slots={{ content: CryptoWalletInfo, }} + slotProps={{ + userDetailsContainer: { + sx: { + justifyContent: 'flex-start', + '& .Account-userDetailsContainer': { + display: 'flex', + flexDirection: 'column', + rowGap: 0, + }, + }, + }, + }} /> diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview deleted file mode 100644 index 29ceba32bb0..00000000000 --- a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 3e9f4f84407..13a93a53e4d 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -30,19 +30,25 @@ When signed out, the component renders as an inline sign in button within the da ## Customization -### Slot Props - -The underlying `signInButton`, `signOutButton` and `iconButton` components can be customized by passing in `slotProps` to the `Account` component. +### Locale Text Labels for the sign in and sign out buttons can be customized through the `localeText` prop. +{{"demo": "AccountLocale.js", "bg": "outlined" }} + +### Slot Props + +The underlying `signInButton`, `signOutButton`, `iconButton` and `userDetailsContainer` sections can be customized by passing in `slotProps` to the `Account` component. + {{"demo": "AccountCustom.js", "bg": "outlined" }} ### Slots +You can pass in your own components to completely override the default components inside the `Account` popover through the `slots` prop. + #### Content -You can pass in your own menu items to the `Account` popover through the `content` slot to add any additional options in the space between the user's account details and the sign out button, to create larger, more complex menus: +Use the `content` slot to add any additional options in the space between the user's account details and the sign out button, to create larger, more complex menus: ##### Account Switcher @@ -53,3 +59,11 @@ The `content` prop can take any React component, so you can use it to display in ##### Crypto Wallet {{"demo": "AccountSlotsInfo.js", "bg": "outlined" }} + +#### User Details Container + +You can override the section which displays the signed-in user's details with a custom component. This component receives a `session` prop which contains the current authentication session: + +##### Enterprise Profile + +{{"demo": "AccountCustomUserDetails.js", "bg": "outlined" }} diff --git a/docs/pages/toolpad/core/api/account.json b/docs/pages/toolpad/core/api/account.json index a3ec92f5492..ee154921036 100644 --- a/docs/pages/toolpad/core/api/account.json +++ b/docs/pages/toolpad/core/api/account.json @@ -7,13 +7,13 @@ "slotProps": { "type": { "name": "shape", - "description": "{ iconButton?: object, signInButton?: object, signOutButton?: object }" + "description": "{ iconButton?: object, signInButton?: object, signOutButton?: object, userDetailsContainer?: object }" } }, "slots": { "type": { "name": "shape", - "description": "{ content?: elementType, signInButton?: elementType, signOutButton?: elementType }" + "description": "{ content?: elementType, signInButton?: elementType, signOutButton?: elementType, userDetailsContainer?: elementType }" }, "additionalInfo": { "slotsApi": true } } @@ -38,9 +38,15 @@ }, { "name": "content", - "description": "The component used for the custom menu items.", + "description": "The component used for custom content in the account popover", "default": "null", "class": null + }, + { + "name": "userDetailsContainer", + "description": "The component used for the user details section in the account popover", + "default": "Box", + "class": null } ], "classes": [], diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index 225a1e63df0..40a9757333b 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -5,7 +5,7 @@ "slotProps": { "type": { "name": "shape", - "description": "{ toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { content?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" + "description": "{ toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object, userDetailsContainer?: object }, slots?: { content?: elementType, signInButton?: elementType, signOutButton?: elementType, userDetailsContainer?: elementType } }, toolbarActions?: object }" }, "default": "{}" }, diff --git a/docs/translations/api-docs/account/account.json b/docs/translations/api-docs/account/account.json index 46ed24ed1fc..df998e156d6 100644 --- a/docs/translations/api-docs/account/account.json +++ b/docs/translations/api-docs/account/account.json @@ -7,8 +7,9 @@ }, "classDescriptions": {}, "slotDescriptions": { - "content": "The component used for the custom menu items.", + "content": "The component used for custom content in the account popover", "signInButton": "The component used for the sign in button.", - "signOutButton": "The component used for the sign out button." + "signOutButton": "The component used for the sign out button.", + "userDetailsContainer": "The component used for the user details section in the account popover" } } diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index 606d26f2d22..21ef5b23f77 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -1,32 +1,18 @@ import * as React from 'react'; -import { styled } from '@mui/material/styles'; +import { useTheme } from '@mui/material/styles'; import PropTypes from 'prop-types'; import Popover from '@mui/material/Popover'; import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; import Button, { ButtonProps } from '@mui/material/Button'; import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; import Logout from '@mui/icons-material/Logout'; -import { Typography } from '@mui/material'; +import { Box, BoxProps, Typography } from '@mui/material'; import { SessionAvatar } from './SessionAvatar'; import { SessionContext, AuthenticationContext } from '../AppProvider/AppProvider'; import DEFAULT_LOCALE_TEXT from '../shared/locales/en'; -const AccountInfoContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - padding: theme.spacing(2), - gap: theme.spacing(2), -})); - -const SignOutContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'row', - padding: theme.spacing(1), - justifyContent: 'flex-end', -})); - export interface AccountSlots { /** * The component used for the sign in button. @@ -39,10 +25,15 @@ export interface AccountSlots { */ signOutButton?: React.ElementType; /** - * The component used for the custom menu items. + * The component used for custom content in the account popover * @default null */ content?: React.ElementType; + /** + * The component used for the user details section in the account popover + * @default Box + */ + userDetailsContainer?: React.ElementType; } export interface AccountProps { @@ -57,6 +48,7 @@ export interface AccountProps { signInButton?: ButtonProps; signOutButton?: ButtonProps; iconButton?: IconButtonProps; + userDetailsContainer?: BoxProps; }; /** * The labels for the account component. @@ -78,6 +70,7 @@ export interface AccountProps { */ function Account(props: AccountProps) { const { slots, slotProps, localeText = DEFAULT_LOCALE_TEXT } = props; + const theme = useTheme(); const [anchorEl, setAnchorEl] = React.useState(null); const session = React.useContext(SessionContext); const authentication = React.useContext(AuthenticationContext); @@ -166,19 +159,39 @@ function Account(props: AccountProps) { transformOrigin={{ horizontal: 'right', vertical: 'top' }} anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} > - - -
- {session.user.name} - {session.user.email} -
-
+ {slots?.userDetailsContainer ? ( + + ) : ( + + + + {session.user.name} + {session.user.email} + + + )} {slots?.content ? : null} {slots?.signOutButton ? ( ) : ( - + - + )} @@ -226,6 +239,7 @@ Account.propTypes /* remove-proptypes */ = { iconButton: PropTypes.object, signInButton: PropTypes.object, signOutButton: PropTypes.object, + userDetailsContainer: PropTypes.object, }), /** * The components used for each slot inside. @@ -234,6 +248,7 @@ Account.propTypes /* remove-proptypes */ = { content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, + userDetailsContainer: PropTypes.elementType, }), } as any; diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index e963918e059..3eff3b862ae 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -679,11 +679,13 @@ DashboardLayout.propTypes /* remove-proptypes */ = { iconButton: PropTypes.object, signInButton: PropTypes.object, signOutButton: PropTypes.object, + userDetailsContainer: PropTypes.object, }), slots: PropTypes.shape({ content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, + userDetailsContainer: PropTypes.elementType, }), }), toolbarActions: PropTypes.object, From 43a0978d47d97fb233234ced44124d6ac0d1b7fc Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 04:05:36 +0200 Subject: [PATCH 05/43] fix: No default `sx` in the slot --- .../toolpad/core/components/account/AccountCustom.js | 4 ++++ .../toolpad/core/components/account/AccountCustom.tsx | 4 ++++ .../core/components/account/AccountCustom.tsx.preview | 4 ++++ .../toolpad/core/components/account/AccountSlotsInfo.js | 9 ++++----- .../toolpad/core/components/account/AccountSlotsInfo.tsx | 9 ++++----- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/data/toolpad/core/components/account/AccountCustom.js b/docs/data/toolpad/core/components/account/AccountCustom.js index 81f17802a77..2a73cfe799f 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.js +++ b/docs/data/toolpad/core/components/account/AccountCustom.js @@ -68,6 +68,10 @@ export default function AccountCustom() { userDetailsContainer: { sx: { p: 2, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + gap: 2, '& .MuiStack-root': { color: theme.palette.grey[700], }, diff --git a/docs/data/toolpad/core/components/account/AccountCustom.tsx b/docs/data/toolpad/core/components/account/AccountCustom.tsx index 759368e8999..720bca8ba8f 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.tsx +++ b/docs/data/toolpad/core/components/account/AccountCustom.tsx @@ -75,6 +75,10 @@ export default function AccountCustom() { userDetailsContainer: { sx: { p: 2, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + gap: 2, '& .MuiStack-root': { color: theme.palette.grey[700], }, diff --git a/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview b/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview index 5bf00e67417..31a0cb44238 100644 --- a/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountCustom.tsx.preview @@ -3,6 +3,10 @@ userDetailsContainer: { sx: { p: 2, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + gap: 2, '& .MuiStack-root': { color: theme.palette.grey[700], }, diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js index d6ae056ce0e..94aa79a4d91 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js @@ -110,12 +110,11 @@ export default function AccountSlotsInfo() { slotProps={{ userDetailsContainer: { sx: { + p: 2, + gap: 2, + display: 'flex', + flexDirection: 'row', justifyContent: 'flex-start', - '& .Account-userDetailsContainer': { - display: 'flex', - flexDirection: 'column', - rowGap: 0, - }, }, }, }} diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx index a4c1a2e25c2..4805a1ab516 100644 --- a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx @@ -115,12 +115,11 @@ export default function AccountSlotsInfo() { slotProps={{ userDetailsContainer: { sx: { + p: 2, + gap: 2, + display: 'flex', + flexDirection: 'row', justifyContent: 'flex-start', - '& .Account-userDetailsContainer': { - display: 'flex', - flexDirection: 'column', - rowGap: 0, - }, }, }, }} From 7277cbf5e61f041e7b781625001d4667484709dd Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 04:05:42 +0200 Subject: [PATCH 06/43] fix: CI --- pnpm-lock.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fd7b1d772a..5b88d832104 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,7 +195,7 @@ importers: version: 7.37.0(eslint@8.57.1) eslint-plugin-react-compiler: specifier: latest - version: 0.0.0-experimental-939a02d-20240930(eslint@8.57.1) + version: 0.0.0-experimental-42acc6a-20241001(eslint@8.57.1) eslint-plugin-react-hooks: specifier: 4.6.2 version: 4.6.2(eslint@8.57.1) @@ -2066,7 +2066,7 @@ packages: resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^7.25.2 '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} @@ -2139,7 +2139,7 @@ packages: '@docsearch/react@3.6.2': resolution: {integrity: sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA==} peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' + '@types/react': ^18.3.4 react: '>= 16.8.0 < 19.0.0' react-dom: '>= 16.8.0 < 19.0.0' search-insights: '>= 1 < 3' @@ -3207,7 +3207,7 @@ packages: resolution: {integrity: sha512-HlRrgdJSPbYDXPpoVMWZV8AE7WcFtAk13rWNWAEVWKSanzBBkymjz3km+Th/Srowsh4pf1fTSP1B0L116wQBYw==} engines: {node: '>=14.0.0'} peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': ^18.3.4 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': @@ -3939,8 +3939,8 @@ packages: engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 - '@types/react-dom': ^18.0.0 + '@types/react': ^18.3.4 + '@types/react-dom': 18.3.0 react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: @@ -5932,8 +5932,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-react-compiler@0.0.0-experimental-939a02d-20240930: - resolution: {integrity: sha512-vBfN+FK71xyUbeKjQ5jC6RzMpIyOG9/BBOjQKJTVnSxybWgkfL3PVUZZSNUAXP50+6MqkMNfNJANBjHsn85jcQ==} + eslint-plugin-react-compiler@0.0.0-experimental-42acc6a-20241001: + resolution: {integrity: sha512-pzkTsWowlHK4yKHsK1d9tTKOUtApZzL7wI6jT5iN31d00DhI9JGDD0pkLohQ6Wfkll+2aiqTPGj9esJoGYmRaw==} engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} peerDependencies: eslint: '>=7' @@ -15739,7 +15739,7 @@ snapshots: globals: 13.24.0 rambda: 7.5.0 - eslint-plugin-react-compiler@0.0.0-experimental-939a02d-20240930(eslint@8.57.1): + eslint-plugin-react-compiler@0.0.0-experimental-42acc6a-20241001(eslint@8.57.1): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.6 From f909067e59620b115fd60d8780d14585197df4e4 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 04:16:36 +0200 Subject: [PATCH 07/43] fix: Readability --- docs/data/toolpad/core/components/account/account.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 13a93a53e4d..8f23ced5eff 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -62,7 +62,7 @@ The `content` prop can take any React component, so you can use it to display in #### User Details Container -You can override the section which displays the signed-in user's details with a custom component. This component receives a `session` prop which contains the current authentication session: +By passing a custom component to the `userDetailsContainer` prop, you can override the section which displays the signed-in user's details. The custom component receives a `session` prop which contains the current authentication session: ##### Enterprise Profile From e337ac0882d4b9c29335ece5e6c012678ada7271 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 04:18:41 +0200 Subject: [PATCH 08/43] fix: typo --- docs/data/toolpad/core/components/account/account.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 8f23ced5eff..6bcdb82e8d5 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -62,7 +62,7 @@ The `content` prop can take any React component, so you can use it to display in #### User Details Container -By passing a custom component to the `userDetailsContainer` prop, you can override the section which displays the signed-in user's details. The custom component receives a `session` prop which contains the current authentication session: +By passing a custom component to the `userDetailsContainer` slot, you can override the section which displays the signed-in user's details. The custom component receives a `session` prop which contains the current authentication session: ##### Enterprise Profile From 2e321babb5d6e61cd5e31b97e4f92590219e0f90 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 04:27:40 +0200 Subject: [PATCH 09/43] fix: More clarification --- .../toolpad/core/components/account/account.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 6bcdb82e8d5..429172b6723 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -64,6 +64,23 @@ The `content` prop can take any React component, so you can use it to display in By passing a custom component to the `userDetailsContainer` slot, you can override the section which displays the signed-in user's details. The custom component receives a `session` prop which contains the current authentication session: +```tsx +// ... +import { Session } from '@toolpad/core'; + +interface CustomSession extends Session { + org: { + name: string; + url: string; + logo: string; + }; +} + +function UserDetailsContainer({ session }: UserDetailsContainerProps) { + // ... +} +``` + ##### Enterprise Profile {{"demo": "AccountCustomUserDetails.js", "bg": "outlined" }} From 1086869b73d7ee8e328314f20f914edcb487e740 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 13:15:31 +0200 Subject: [PATCH 10/43] fix: CI --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1667da1e83c..c9c9d105c04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2066,7 +2066,7 @@ packages: resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^7.25.2 '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} @@ -2139,7 +2139,7 @@ packages: '@docsearch/react@3.6.2': resolution: {integrity: sha512-rtZce46OOkVflCQH71IdbXSFK+S8iJZlUF56XBW5rIgx/eG5qoomC7Ag3anZson1bBac/JFQn7XOBfved/IMRA==} peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' + '@types/react': ^18.3.4 react: '>= 16.8.0 < 19.0.0' react-dom: '>= 16.8.0 < 19.0.0' search-insights: '>= 1 < 3' @@ -3939,8 +3939,8 @@ packages: engines: {node: '>=18'} peerDependencies: '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 - '@types/react-dom': ^18.0.0 + '@types/react': ^18.3.4 + '@types/react-dom': 18.3.0 react: ^18.0.0 react-dom: ^18.0.0 peerDependenciesMeta: From 7938e3f3466c5d0885b6604868aa55ac5aee7ee5 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 13:28:30 +0200 Subject: [PATCH 11/43] fix: WIP `menuItems` -> `content` --- .../core/components/account/AccountSlots.tsx | 4 ++-- .../toolpad/core/components/account/account.md | 16 ++++++++++++++-- packages/toolpad-core/src/Account/Account.tsx | 6 +++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/data/toolpad/core/components/account/AccountSlots.tsx b/docs/data/toolpad/core/components/account/AccountSlots.tsx index 43f9af3a30f..2605403ead2 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlots.tsx @@ -5,7 +5,7 @@ import { SessionContext, Session, } from '@toolpad/core'; -import CustomMenuItems from './CustomMenu'; +import CustomMenu from './CustomMenu'; const demoSession = { user: { @@ -33,7 +33,7 @@ export default function AccountSlots() { diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 7b5121fb8d3..85fece53242 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -40,6 +40,18 @@ Labels for the sign in and sign out buttons can be customized through the `local ### Slots -You can pass in your own items to the `Account` menu through the `menuItems` slot to add additional menu items in the space between the user's account details and the sign out button, to create larger, more complex menus: +You can pass in your own components to completely override the default components inside the `Account` popover through the `slots` prop. -{{"demo": "AccountSlots.js", "bg": "gradient"}} +#### Content + +Use the `content` slot to add any additional options in the space between the user's account details and the sign out button, to create larger, more complex menus: + +##### Account Switcher + +{{"demo": "AccountSlotsAccountSwitcher.js", "bg": "gradient"}} + +The `content` prop can take any React component, so you can use it to display information instead of adding menu items: + +##### Crypto Wallet + +{{"demo": "AccountSlotsInfo.js", "bg": "outlined" }} diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index 7ea40743f0e..827ee4e1ccf 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -42,7 +42,7 @@ export interface AccountSlots { * The component used for the custom menu items. * @default null */ - menuItems?: React.ElementType; + content?: React.ElementType; } export interface AccountProps { @@ -168,13 +168,13 @@ function Account(props: AccountProps) { > -
+
{session.user.name} {session.user.email}
- {slots?.menuItems ? : null} + {slots?.content ? : null} {slots?.signOutButton ? ( ) : ( From 680725409c82983c289f9a162bb677536d6debc9 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 13:30:37 +0200 Subject: [PATCH 12/43] wip: Move demos over --- ...ts.tsx => AccountSlotsAccountSwitcher.tsx} | 2 +- .../components/account/AccountSlotsInfo.tsx | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) rename docs/data/toolpad/core/components/account/{AccountSlots.tsx => AccountSlotsAccountSwitcher.tsx} (94%) create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx diff --git a/docs/data/toolpad/core/components/account/AccountSlots.tsx b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx similarity index 94% rename from docs/data/toolpad/core/components/account/AccountSlots.tsx rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx index 2605403ead2..ea03da06080 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.tsx +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx @@ -15,7 +15,7 @@ const demoSession = { }, }; -export default function AccountSlots() { +export default function AccountSlotsAccountSwitcher() { const [session, setSession] = React.useState(demoSession); const authentication = React.useMemo(() => { return { diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx new file mode 100644 index 00000000000..861c6332880 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import { + Avatar, + Button, + Divider, + Typography, + Stack, + IconButton, +} from '@mui/material'; +import WalletIcon from '@mui/icons-material/AccountBalance'; +import SendIcon from '@mui/icons-material/Send'; +import ShoppingCart from '@mui/icons-material/ShoppingCart'; +import CopyIcon from '@mui/icons-material/ContentCopy'; + +import { + Account, + AuthenticationContext, + SessionContext, + Session, +} from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +const mockData = { + address: '0x1234...5678', + balance: '1,234.56 ETH', + usdBalance: '$2,345,678.90 USD', +}; + +function CryptoWalletInfo() { + return ( +
+ + + + + + + + {mockData.address} + + + + + + Main Account + + + + + + + {mockData.balance} + + + {mockData.usdBalance} + + + + + + + + + +
+ ); +} + +export default function AccountSlotsInfo() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + + + + ); +} From c2703a76bfba6a62020668dccf599d286d610e11 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Thu, 3 Oct 2024 13:31:16 +0200 Subject: [PATCH 13/43] fix: Move demos to new API --- .../core/components/account/CustomMenu.tsx | 98 ++++++++++++++++--- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/docs/data/toolpad/core/components/account/CustomMenu.tsx b/docs/data/toolpad/core/components/account/CustomMenu.tsx index bdf09af5c13..7db92408f3d 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.tsx +++ b/docs/data/toolpad/core/components/account/CustomMenu.tsx @@ -1,9 +1,19 @@ import * as React from 'react'; -import { Menu, MenuItem, MenuList, Divider, ListItemIcon } from '@mui/material'; +import { + Menu, + MenuItem, + MenuList, + Divider, + ListItemIcon, + ListItemText, + Typography, + Avatar, +} from '@mui/material'; +import PersonIcon from '@mui/icons-material/Person'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; import SettingsIcon from '@mui/icons-material/Settings'; -import AddIcon from '@mui/icons-material/Add'; -interface CustomSettingsMenuProps { +interface AccountSwitcherProps { open: boolean; anchorEl: HTMLButtonElement | null; handleMenuClose: () => void; @@ -11,7 +21,23 @@ interface CustomSettingsMenuProps { handleEnter: () => void; } -function CustomSettingsMenu(props: CustomSettingsMenuProps) { +// Function to generate a random color +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i += 1) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const accounts = [ + { id: 1, name: 'John Doe', email: 'john@example.com', color: getRandomColor() }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com', color: getRandomColor() }, + { id: 3, name: 'Bob Johnson', email: 'bob@example.com', color: getRandomColor() }, +]; + +function AccountSwitcher(props: AccountSwitcherProps) { const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; return ( @@ -31,16 +57,45 @@ function CustomSettingsMenu(props: CustomSettingsMenuProps) { }} > { handleEnter(); }} onMouseLeave={handleLeave} > - Profile - My account + + Accounts + + + {accounts.map((account) => ( + + + + {account.name.charAt(0)} + + + + + ))} ); @@ -49,7 +104,7 @@ function CustomSettingsMenu(props: CustomSettingsMenuProps) { export default function CustomMenu() { const handleMenuNavigation = (route: string) => () => { console.log( - 'Toolpad Core Account Demo --- CustomMenuItems --- handleMenuNavigation --- route: ', + 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', route, ); }; @@ -96,8 +151,20 @@ export default function CustomMenu() { return ( + + + + Profile + + - + - Add another account + Switch Account - Date: Mon, 7 Oct 2024 10:44:36 +0530 Subject: [PATCH 14/43] wip: Better account switcher --- ...lots.js => AccountSlotsAccountSwitcher.js} | 6 +- ...> AccountSlotsAccountSwitcher.tsx.preview} | 2 +- .../components/account/AccountSlotsInfo.js | 114 +++++++++++ .../account/AccountSlotsInfo.tsx.preview | 9 + .../core/components/account/CustomMenu.js | 192 +++++++++++++----- .../core/components/account/CustomMenu.tsx | 186 ++++++++++------- docs/pages/toolpad/core/api/account.json | 4 +- .../toolpad/core/api/dashboard-layout.json | 2 +- .../api-docs/account/account.json | 2 +- packages/toolpad-core/src/Account/Account.tsx | 4 +- .../src/DashboardLayout/DashboardLayout.tsx | 2 +- 11 files changed, 386 insertions(+), 137 deletions(-) rename docs/data/toolpad/core/components/account/{AccountSlots.js => AccountSlotsAccountSwitcher.js} (86%) rename docs/data/toolpad/core/components/account/{AccountSlots.tsx.preview => AccountSlotsAccountSwitcher.tsx.preview} (85%) create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.js create mode 100644 docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview diff --git a/docs/data/toolpad/core/components/account/AccountSlots.js b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js similarity index 86% rename from docs/data/toolpad/core/components/account/AccountSlots.js rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js index 31051b0adab..97b3aa14b99 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.js +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; -import CustomMenuItems from './CustomMenu'; +import CustomMenu from './CustomMenu'; const demoSession = { user: { @@ -10,7 +10,7 @@ const demoSession = { }, }; -export default function AccountSlots() { +export default function AccountSlotsAccountSwitcher() { const [session, setSession] = React.useState(demoSession); const authentication = React.useMemo(() => { return { @@ -28,7 +28,7 @@ export default function AccountSlots() { diff --git a/docs/data/toolpad/core/components/account/AccountSlots.tsx.preview b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview similarity index 85% rename from docs/data/toolpad/core/components/account/AccountSlots.tsx.preview rename to docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview index 5e8ecca9f06..18a06d57ca7 100644 --- a/docs/data/toolpad/core/components/account/AccountSlots.tsx.preview +++ b/docs/data/toolpad/core/components/account/AccountSlotsAccountSwitcher.tsx.preview @@ -2,7 +2,7 @@ diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.js b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js new file mode 100644 index 00000000000..a6506788fd8 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.js @@ -0,0 +1,114 @@ +import * as React from 'react'; +import { + Avatar, + Button, + Divider, + Typography, + Stack, + IconButton, +} from '@mui/material'; +import WalletIcon from '@mui/icons-material/AccountBalance'; +import SendIcon from '@mui/icons-material/Send'; +import ShoppingCart from '@mui/icons-material/ShoppingCart'; +import CopyIcon from '@mui/icons-material/ContentCopy'; + +import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; + +const demoSession = { + user: { + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, +}; + +const mockData = { + address: '0x1234...5678', + balance: '1,234.56 ETH', + usdBalance: '$2,345,678.90 USD', +}; + +function CryptoWalletInfo() { + return ( +
+ + + + + + + + {mockData.address} + + + + + + Main Account + + + + + + + {mockData.balance} + + + {mockData.usdBalance} + + + + + + + + + +
+ ); +} + +export default function AccountSlotsInfo() { + const [session, setSession] = React.useState(demoSession); + const authentication = React.useMemo(() => { + return { + signIn: () => { + setSession(demoSession); + }, + signOut: () => { + setSession(null); + }, + }; + }, []); + + return ( + + + + + + ); +} diff --git a/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview new file mode 100644 index 00000000000..29ceba32bb0 --- /dev/null +++ b/docs/data/toolpad/core/components/account/AccountSlotsInfo.tsx.preview @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/docs/data/toolpad/core/components/account/CustomMenu.js b/docs/data/toolpad/core/components/account/CustomMenu.js index 2bbee81379a..bc13f2c6b29 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.js +++ b/docs/data/toolpad/core/components/account/CustomMenu.js @@ -1,11 +1,54 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { Menu, MenuItem, MenuList, Divider, ListItemIcon } from '@mui/material'; -import SettingsIcon from '@mui/icons-material/Settings'; +import { + Menu, + MenuItem, + MenuList, + Button, + Divider, + ListItemIcon, + ListItemText, + Typography, + Avatar, +} from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; -function CustomSettingsMenu(props) { - const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; +// Function to generate a random color +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i += 1) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const accounts = [ + { + id: 1, + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + color: getRandomColor(), + projects: [ + { + id: 3, + title: 'Project X', + }, + ], + }, + { + id: 2, + name: 'Bharat MUI', + email: 'bharat@mui.com', + color: getRandomColor(), + projects: [{ id: 4, title: 'Project A' }], + }, +]; + +function ProjectsList(props) { + const { open, anchorEl, handleMenuClose, handleEnter, handleLeave, projects } = + props; return ( { handleEnter(); }} onMouseLeave={handleLeave} > - Profile - My account + + Projects + + + {projects?.map((project) => ( + + + + ))} + + ); } -CustomSettingsMenu.propTypes = { +ProjectsList.propTypes = { anchorEl: PropTypes.object, handleEnter: PropTypes.func.isRequired, handleLeave: PropTypes.func.isRequired, handleMenuClose: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, + projects: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + }), + ).isRequired, }; export default function CustomMenu() { - const handleMenuNavigation = (route) => () => { - console.log( - 'Toolpad Core Account Demo --- CustomMenuItems --- handleMenuNavigation --- route: ', - route, - ); - }; - const mouseOnSubMenu = React.useRef(false); + const [selectedProjects, setSelectedProjects] = React.useState([]); + + const handleSelectProjects = React.useCallback((id) => { + setSelectedProjects( + accounts.find((account) => account.id === id)?.projects ?? [], + ); + }, []); + const [subMenuAnchorEl, setSubMenuAnchorEl] = React.useState(null); const subMenuOpen = Boolean(subMenuAnchorEl); - const handleTriggerEnter = React.useCallback((event) => { - setSubMenuAnchorEl(event.currentTarget); - }, []); + const handleTriggerEnter = React.useCallback( + (event, id) => { + handleSelectProjects(id); + setSubMenuAnchorEl(event.currentTarget); + }, + [handleSelectProjects], + ); const handleTriggerLeave = React.useCallback(() => { // Wait for 300ms to see if the mouse has moved to the sub menu @@ -92,41 +172,59 @@ export default function CustomMenu() { return ( - - - - - Settings - - - - - - Add another account - + + Accounts + + {accounts.map((account) => ( + handleSelectProjects(account.id)} + onMouseEnter={(event) => handleTriggerEnter(event, account.id)} + onMouseLeave={handleTriggerLeave} + > + + + {account.name[0]} + + + + + ))} - ); diff --git a/docs/data/toolpad/core/components/account/CustomMenu.tsx b/docs/data/toolpad/core/components/account/CustomMenu.tsx index 7db92408f3d..3e36e619022 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.tsx +++ b/docs/data/toolpad/core/components/account/CustomMenu.tsx @@ -1,24 +1,30 @@ import * as React from 'react'; import { Menu, + Box, MenuItem, MenuList, + Button, Divider, ListItemIcon, ListItemText, Typography, Avatar, } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import ExitToAppIcon from '@mui/icons-material/ExitToApp'; -import SettingsIcon from '@mui/icons-material/Settings'; +import AddIcon from '@mui/icons-material/Add'; -interface AccountSwitcherProps { +interface Project { + id: number; + title: string; +} + +interface ProjectsListProps { open: boolean; anchorEl: HTMLButtonElement | null; handleMenuClose: () => void; handleLeave: (event: React.MouseEvent) => void; handleEnter: () => void; + projects: Project[]; } // Function to generate a random color @@ -32,13 +38,31 @@ const getRandomColor = () => { }; const accounts = [ - { id: 1, name: 'John Doe', email: 'john@example.com', color: getRandomColor() }, - { id: 2, name: 'Jane Smith', email: 'jane@example.com', color: getRandomColor() }, - { id: 3, name: 'Bob Johnson', email: 'bob@example.com', color: getRandomColor() }, + { + id: 1, + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + color: getRandomColor(), + projects: [ + { + id: 3, + title: 'Project X', + }, + ], + }, + { + id: 2, + name: 'Bharat MUI', + email: 'bharat@mui.com', + color: getRandomColor(), + projects: [{ id: 4, title: 'Project A' }], + }, ]; -function AccountSwitcher(props: AccountSwitcherProps) { - const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; +function ProjectsList(props: ProjectsListProps) { + const { open, anchorEl, handleMenuClose, handleEnter, handleLeave, projects } = + props; return ( { @@ -66,60 +91,58 @@ function AccountSwitcher(props: AccountSwitcherProps) { }} onMouseLeave={handleLeave} > - - Accounts + + Projects - {accounts.map((account) => ( + {projects?.map((project) => ( - - - {account.name.charAt(0)} - - ))} + + ); } export default function CustomMenu() { - const handleMenuNavigation = (route: string) => () => { - console.log( - 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', - route, - ); - }; - const mouseOnSubMenu = React.useRef(false); + const [selectedProjects, setSelectedProjects] = React.useState([]); + + const handleSelectProjects = React.useCallback((id: number) => { + setSelectedProjects( + accounts.find((account) => account.id === id)?.projects ?? [], + ); + }, []); + const [subMenuAnchorEl, setSubMenuAnchorEl] = React.useState(null); const subMenuOpen = Boolean(subMenuAnchorEl); const handleTriggerEnter = React.useCallback( - (event: React.MouseEvent) => { + (event: React.MouseEvent, id: number) => { + handleSelectProjects(id); setSubMenuAnchorEl(event.currentTarget); }, - [], + [handleSelectProjects], ); const handleTriggerLeave = React.useCallback(() => { @@ -150,54 +173,59 @@ export default function CustomMenu() { return ( - - - - - Profile - - - - - - Settings - - - - - - Switch Account - + + Accounts + + {accounts.map((account) => ( + handleSelectProjects(account.id)} + onMouseEnter={(event) => handleTriggerEnter(event, account.id)} + onMouseLeave={handleTriggerLeave} + > + + + {account.name[0]} + + + + + ))} - ); diff --git a/docs/pages/toolpad/core/api/account.json b/docs/pages/toolpad/core/api/account.json index 16f12682220..0d67c143d07 100644 --- a/docs/pages/toolpad/core/api/account.json +++ b/docs/pages/toolpad/core/api/account.json @@ -13,7 +13,7 @@ "slots": { "type": { "name": "shape", - "description": "{ menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType }" + "description": "{ content?: elementType, signInButton?: elementType, signOutButton?: elementType }" }, "additionalInfo": { "slotsApi": true } } @@ -37,7 +37,7 @@ "class": null }, { - "name": "menuItems", + "name": "content", "description": "The component used for the custom menu items.", "default": "null", "class": null diff --git a/docs/pages/toolpad/core/api/dashboard-layout.json b/docs/pages/toolpad/core/api/dashboard-layout.json index 8e5707511f8..225a1e63df0 100644 --- a/docs/pages/toolpad/core/api/dashboard-layout.json +++ b/docs/pages/toolpad/core/api/dashboard-layout.json @@ -5,7 +5,7 @@ "slotProps": { "type": { "name": "shape", - "description": "{ toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { menuItems?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" + "description": "{ toolbarAccount?: { localeText?: { signInLabel: string, signOutLabel: string }, slotProps?: { iconButton?: object, signInButton?: object, signOutButton?: object }, slots?: { content?: elementType, signInButton?: elementType, signOutButton?: elementType } }, toolbarActions?: object }" }, "default": "{}" }, diff --git a/docs/translations/api-docs/account/account.json b/docs/translations/api-docs/account/account.json index 58620d755a2..46ed24ed1fc 100644 --- a/docs/translations/api-docs/account/account.json +++ b/docs/translations/api-docs/account/account.json @@ -7,7 +7,7 @@ }, "classDescriptions": {}, "slotDescriptions": { - "menuItems": "The component used for the custom menu items.", + "content": "The component used for the custom menu items.", "signInButton": "The component used for the sign in button.", "signOutButton": "The component used for the sign out button." } diff --git a/packages/toolpad-core/src/Account/Account.tsx b/packages/toolpad-core/src/Account/Account.tsx index 827ee4e1ccf..8912016befc 100644 --- a/packages/toolpad-core/src/Account/Account.tsx +++ b/packages/toolpad-core/src/Account/Account.tsx @@ -15,7 +15,7 @@ import DEFAULT_LOCALE_TEXT from '../shared/locales/en'; const AccountInfoContainer = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'row', - justifyContent: 'space-between', + justifyContent: 'flex-start', padding: theme.spacing(2), gap: theme.spacing(2), })); @@ -231,7 +231,7 @@ Account.propTypes /* remove-proptypes */ = { * The components used for each slot inside. */ slots: PropTypes.shape({ - menuItems: PropTypes.elementType, + content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, }), diff --git a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx index b21df1beee3..e963918e059 100644 --- a/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx +++ b/packages/toolpad-core/src/DashboardLayout/DashboardLayout.tsx @@ -681,7 +681,7 @@ DashboardLayout.propTypes /* remove-proptypes */ = { signOutButton: PropTypes.object, }), slots: PropTypes.shape({ - menuItems: PropTypes.elementType, + content: PropTypes.elementType, signInButton: PropTypes.elementType, signOutButton: PropTypes.elementType, }), From d76eebfdda69a4753938426e6000d730406884f0 Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Mon, 7 Oct 2024 14:10:30 +0530 Subject: [PATCH 15/43] fix: CI --- pnpm-lock.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9c9d105c04..1e1352faec8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,7 +195,7 @@ importers: version: 7.37.0(eslint@8.57.1) eslint-plugin-react-compiler: specifier: latest - version: 0.0.0-experimental-42acc6a-20241001(eslint@8.57.1) + version: 0.0.0-experimental-9a75bad-20241003(eslint@8.57.1) eslint-plugin-react-hooks: specifier: 4.6.2 version: 4.6.2(eslint@8.57.1) @@ -2066,7 +2066,7 @@ packages: resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.25.2 + '@babel/core': ^7.0.0-0 '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} @@ -5932,8 +5932,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-react-compiler@0.0.0-experimental-42acc6a-20241001: - resolution: {integrity: sha512-pzkTsWowlHK4yKHsK1d9tTKOUtApZzL7wI6jT5iN31d00DhI9JGDD0pkLohQ6Wfkll+2aiqTPGj9esJoGYmRaw==} + eslint-plugin-react-compiler@0.0.0-experimental-9a75bad-20241003: + resolution: {integrity: sha512-qZ/oEuECrXt3J4+13TvHEOBfrizBuUborCMFccZFn64UcWgbLzn+6Yb4xVx80YQiT1GT6UiKh62OkvnP6yYR2w==} engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} peerDependencies: eslint: '>=7' @@ -5984,6 +5984,7 @@ packages: eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -15739,7 +15740,7 @@ snapshots: globals: 13.24.0 rambda: 7.5.0 - eslint-plugin-react-compiler@0.0.0-experimental-42acc6a-20241001(eslint@8.57.1): + eslint-plugin-react-compiler@0.0.0-experimental-9a75bad-20241003(eslint@8.57.1): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.6 From c71f5ba98b18f94118719dbaa6c728d31195263a Mon Sep 17 00:00:00 2001 From: Bharat Kashyap Date: Mon, 7 Oct 2024 17:13:07 +0530 Subject: [PATCH 16/43] fix: Complete better account switcher --- .../core/components/account/CustomMenu.js | 34 ++++++++++--------- .../core/components/account/CustomMenu.tsx | 34 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/docs/data/toolpad/core/components/account/CustomMenu.js b/docs/data/toolpad/core/components/account/CustomMenu.js index bc13f2c6b29..5f0870331af 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.js +++ b/docs/data/toolpad/core/components/account/CustomMenu.js @@ -11,25 +11,15 @@ import { Typography, Avatar, } from '@mui/material'; +import FolderIcon from '@mui/icons-material/Folder'; import AddIcon from '@mui/icons-material/Add'; -// Function to generate a random color -const getRandomColor = () => { - const letters = '0123456789ABCDEF'; - let color = '#'; - for (let i = 0; i < 6; i += 1) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; -}; - const accounts = [ { id: 1, name: 'Bharat Kashyap', email: 'bharatkashyap@outlook.com', image: 'https://avatars.githubusercontent.com/u/19550456', - color: getRandomColor(), projects: [ { id: 3, @@ -41,7 +31,7 @@ const accounts = [ id: 2, name: 'Bharat MUI', email: 'bharat@mui.com', - color: getRandomColor(), + color: '#8B4513', // Brown color projects: [{ id: 4, title: 'Project A' }], }, ]; @@ -87,6 +77,9 @@ function ProjectsList(props) { onClick={handleMenuClose} sx={{ px: 1, my: 1, columnGap: '1.25rem' }} > + + +
); } -AccountSwitcher.propTypes = { +ProjectsList.propTypes = { anchorEl: PropTypes.object, handleEnter: PropTypes.func.isRequired, handleLeave: PropTypes.func.isRequired, handleMenuClose: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, + projects: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + }), + ).isRequired, }; export default function CustomMenu() { - const handleMenuNavigation = (route) => () => { - console.log( - 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', - route, - ); - }; - const mouseOnSubMenu = React.useRef(false); + const mouseOnMenuItem = React.useRef(false); + + const [selectedProjects, setSelectedProjects] = React.useState([]); + + const handleSelectProjects = React.useCallback((id) => { + setSelectedProjects( + accounts.find((account) => account.id === id)?.projects ?? [], + ); + }, []); const [subMenuAnchorEl, setSubMenuAnchorEl] = React.useState(null); const subMenuOpen = Boolean(subMenuAnchorEl); - const handleTriggerEnter = React.useCallback((event) => { - setSubMenuAnchorEl(event.currentTarget); - }, []); + const handleTriggerEnter = React.useCallback( + (event, id) => { + handleSelectProjects(id); + setSubMenuAnchorEl(event.currentTarget); + // Wait for 300ms to see if the mouse has moved to a menu item + setTimeout(() => { + mouseOnMenuItem.current = true; + }, 300); + }, + [handleSelectProjects], + ); const handleTriggerLeave = React.useCallback(() => { - // Wait for 300ms to see if the mouse has moved to the sub menu + mouseOnMenuItem.current = false; + // Wait for 320ms to see if the mouse has moved to the sub menu + // Timeout must be > 300ms to allow for `mouseOnMenuItem.current` to update + // inside `handleTriggerEnter` setTimeout(() => { - if (mouseOnSubMenu.current) { + if (mouseOnSubMenu.current || mouseOnMenuItem.current) { return; } + setSubMenuAnchorEl(null); - }, 300); + }, 320); }, []); const handleSubMenuEnter = React.useCallback(() => { @@ -146,56 +175,64 @@ export default function CustomMenu() { }, []); return ( - - - - - - Profile - - - - - - Settings - - - - - - Switch Account - - - - - + + + + + Accounts + + {accounts.map((account) => ( + handleSelectProjects(account.id)} + onMouseEnter={(event) => handleTriggerEnter(event, account.id)} + onMouseLeave={handleTriggerLeave} + > + + + {account.name[0]} + + + + + ))} + + + + + ); } diff --git a/docs/data/toolpad/core/components/account/CustomMenu.tsx b/docs/data/toolpad/core/components/account/CustomMenu.tsx index 7db92408f3d..ee67bb785a3 100644 --- a/docs/data/toolpad/core/components/account/CustomMenu.tsx +++ b/docs/data/toolpad/core/components/account/CustomMenu.tsx @@ -3,42 +3,57 @@ import { Menu, MenuItem, MenuList, + Button, Divider, ListItemIcon, ListItemText, Typography, Avatar, + Stack, } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import ExitToAppIcon from '@mui/icons-material/ExitToApp'; -import SettingsIcon from '@mui/icons-material/Settings'; +import { AccountDetails } from '@toolpad/core'; +import FolderIcon from '@mui/icons-material/Folder'; +import AddIcon from '@mui/icons-material/Add'; -interface AccountSwitcherProps { +interface Project { + id: number; + title: string; +} + +interface ProjectsListProps { open: boolean; anchorEl: HTMLButtonElement | null; handleMenuClose: () => void; handleLeave: (event: React.MouseEvent) => void; handleEnter: () => void; + projects: Project[]; } -// Function to generate a random color -const getRandomColor = () => { - const letters = '0123456789ABCDEF'; - let color = '#'; - for (let i = 0; i < 6; i += 1) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; -}; - const accounts = [ - { id: 1, name: 'John Doe', email: 'john@example.com', color: getRandomColor() }, - { id: 2, name: 'Jane Smith', email: 'jane@example.com', color: getRandomColor() }, - { id: 3, name: 'Bob Johnson', email: 'bob@example.com', color: getRandomColor() }, + { + id: 1, + name: 'Bharat Kashyap', + email: 'bharatkashyap@outlook.com', + image: 'https://avatars.githubusercontent.com/u/19550456', + projects: [ + { + id: 3, + title: 'Project X', + }, + ], + }, + { + id: 2, + name: 'Bharat MUI', + email: 'bharat@mui.com', + color: '#8B4513', // Brown color + projects: [{ id: 4, title: 'Project A' }], + }, ]; -function AccountSwitcher(props: AccountSwitcherProps) { - const { open, anchorEl, handleMenuClose, handleEnter, handleLeave } = props; +function ProjectsList(props: ProjectsListProps) { + const { open, anchorEl, handleMenuClose, handleEnter, handleLeave, projects } = + props; return ( { @@ -66,70 +82,80 @@ function AccountSwitcher(props: AccountSwitcherProps) { }} onMouseLeave={handleLeave} > - - Accounts + + Projects - {accounts.map((account) => ( + {projects?.map((project) => ( - - - {account.name.charAt(0)} - + + ))} + + ); } export default function CustomMenu() { - const handleMenuNavigation = (route: string) => () => { - console.log( - 'Toolpad Core Account Demo --- CustomContent --- handleMenuNavigation --- route: ', - route, - ); - }; - const mouseOnSubMenu = React.useRef(false); + const mouseOnMenuItem = React.useRef(false); + + const [selectedProjects, setSelectedProjects] = React.useState([]); + + const handleSelectProjects = React.useCallback((id: number) => { + setSelectedProjects( + accounts.find((account) => account.id === id)?.projects ?? [], + ); + }, []); const [subMenuAnchorEl, setSubMenuAnchorEl] = React.useState(null); const subMenuOpen = Boolean(subMenuAnchorEl); const handleTriggerEnter = React.useCallback( - (event: React.MouseEvent) => { + (event: React.MouseEvent, id: number) => { + handleSelectProjects(id); setSubMenuAnchorEl(event.currentTarget); + // Wait for 300ms to see if the mouse has moved to a menu item + setTimeout(() => { + mouseOnMenuItem.current = true; + }, 300); }, - [], + [handleSelectProjects], ); const handleTriggerLeave = React.useCallback(() => { - // Wait for 300ms to see if the mouse has moved to the sub menu + mouseOnMenuItem.current = false; + // Wait for 320ms to see if the mouse has moved to the sub menu + // Timeout must be > 300ms to allow for `mouseOnMenuItem.current` to update + // inside `handleTriggerEnter` setTimeout(() => { - if (mouseOnSubMenu.current) { + if (mouseOnSubMenu.current || mouseOnMenuItem.current) { return; } + setSubMenuAnchorEl(null); - }, 300); + }, 320); }, []); const handleSubMenuEnter = React.useCallback(() => { @@ -149,56 +175,64 @@ export default function CustomMenu() { }, []); return ( - - - - - - Profile - - - - - - Settings - - - - - - Switch Account - - - - - + + + + + Accounts + + {accounts.map((account) => ( + handleSelectProjects(account.id)} + onMouseEnter={(event) => handleTriggerEnter(event, account.id)} + onMouseLeave={handleTriggerLeave} + > + + + {account.name[0]} + + + + + ))} + + + + + ); } diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 429172b6723..29ab9eb6a63 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -1,7 +1,7 @@ --- productId: toolpad-core title: Account -components: Account +components: Account, AccountDetails --- # Account @@ -48,39 +48,29 @@ You can pass in your own components to completely override the default component #### Content -Use the `content` slot to add any additional options in the space between the user's account details and the sign out button, to create larger, more complex menus: +Use the `content` slot to customize the content of the popover. You can use the `AccountDetails` component inside your custom account popover: + +```tsx +import { AccountDetails } from '@toolpad/core'; + +function CustomAccount() { + return ( + + + + // ... + + + ); +} +``` ##### Account Switcher -{{"demo": "AccountSlotsAccountSwitcher.js", "bg": "gradient"}} +{{"demo": "AccountSlotsAccountSwitcher.js", "bg": "outlined"}} The `content` prop can take any React component, so you can use it to display information instead of adding menu items: ##### Crypto Wallet {{"demo": "AccountSlotsInfo.js", "bg": "outlined" }} - -#### User Details Container - -By passing a custom component to the `userDetailsContainer` slot, you can override the section which displays the signed-in user's details. The custom component receives a `session` prop which contains the current authentication session: - -```tsx -// ... -import { Session } from '@toolpad/core'; - -interface CustomSession extends Session { - org: { - name: string; - url: string; - logo: string; - }; -} - -function UserDetailsContainer({ session }: UserDetailsContainerProps) { - // ... -} -``` - -##### Enterprise Profile - -{{"demo": "AccountCustomUserDetails.js", "bg": "outlined" }} diff --git a/docs/data/toolpad/core/components/app-provider/app-provider.md b/docs/data/toolpad/core/components/app-provider/app-provider.md index 62b4b38b6e9..bffc6d437fd 100644 --- a/docs/data/toolpad/core/components/app-provider/app-provider.md +++ b/docs/data/toolpad/core/components/app-provider/app-provider.md @@ -31,8 +31,6 @@ The `AppProvider` for Next.js applications includes some Next.js integrations ou By using the specific `AppProvider` for Next.js you do not have to manually configure the integration between some Toolpad features and the corresponding Next.js features (such as routing), making the integration automatic and seamless. ```tsx -import { AppProvider } from '@toolpad/core/nextjs/AppProvider'; -// or import { AppProvider } from '@toolpad/core/nextjs'; ``` @@ -43,7 +41,7 @@ When using the **Next.js App Router**, the most typical file where to import and ```tsx // app/layout.tsx -import { AppProvider } from '@toolpad/core/nextjs/AppProvider'; +import { AppProvider } from '@toolpad/core/nextjs'; export default function Layout(props) { const { children } = props; @@ -65,7 +63,7 @@ When using the **Next.js Pages Router**, the most typical file where to import a ```tsx // pages/_app.tsx -import { AppProvider } from '@toolpad/core/nextjs/AppProvider'; +import { AppProvider } from '@toolpad/core/nextjs'; export default function App(props) { const { Component, pageProps } = props; @@ -78,6 +76,16 @@ export default function App(props) { } ``` +## Client-side Routing + +The `AppProvider` for React Router includes routing out-of-the-box for projects using [react-router-dom](https://www.npmjs.com/package/react-router-dom). + +This specific `AppProvider` is recommended when building single-page applications with tools such as [Vite](https://vite.dev/), as you do not have to manually configure your app routing, making the integration automatic and seamless. + +```tsx +import { AppProvider } from '@toolpad/core/react-router-dom'; +``` + ## Theming An `AppProvider` can set a visual theme for all elements inside it to adopt via the `theme` prop. This prop can be set in a few distinct ways with different advantages and disadvantages: diff --git a/docs/data/toolpad/core/components/crud-page/crud-page.md b/docs/data/toolpad/core/components/crud-page/crud-page.md new file mode 100644 index 00000000000..bb03fc56aa5 --- /dev/null +++ b/docs/data/toolpad/core/components/crud-page/crud-page.md @@ -0,0 +1,15 @@ +--- +productId: toolpad-core +title: CRUD Page +--- + +# CRUD Page 🚧 + +

The CRUD component provides a UI for editable data sources. With deep-linkable, form-based pages.

+ +:::warning +The CRUD component isn't available yet, but you can upvote [**this GitHub issue**](https://github.com/mui/toolpad/issues/4146) to see it arrive sooner. + +Don't hesitate to leave a comment there to influence what gets built. +Especially if you already have a use case for this component, or if you're facing a pain point with your current solution. +::: diff --git a/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutFullScreen.js b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutFullScreen.js new file mode 100644 index 00000000000..2f71df3e348 --- /dev/null +++ b/docs/data/toolpad/core/components/dashboard-layout/DashboardLayoutFullScreen.js @@ -0,0 +1,76 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { createTheme } from '@mui/material/styles'; +import MapIcon from '@mui/icons-material/Map'; +import { AppProvider } from '@toolpad/core/AppProvider'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; + +const NAVIGATION = [ + { + segment: 'map', + title: 'Map', + icon: , + }, +]; + +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 DashboardLayoutFullScreen(props) { + const { window } = props; + + const [pathname, setPathname] = React.useState('/map'); + + 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 ( + + +