diff --git a/docs/data/material/components/menus/menus.md b/docs/data/material/components/menus/menus.md index 9ae8536ade0a43..a6a90edaae7be2 100644 --- a/docs/data/material/components/menus/menus.md +++ b/docs/data/material/components/menus/menus.md @@ -1,7 +1,7 @@ --- productId: material-ui title: React Menu component -components: Menu, MenuItem, MenuList, ClickAwayListener, Popover, Popper +components: Menu, MenuItem, MenuList, MenuPreview, MenuPreviewTrigger, MenuPreviewPopup, MenuPreviewSubmenuPopup, MenuPreviewItem, MenuPreviewLinkItem, MenuPreviewCheckboxItem, MenuPreviewCheckboxItemIndicator, MenuPreviewRadioGroup, MenuPreviewRadioItem, MenuPreviewRadioItemIndicator, MenuPreviewGroup, MenuPreviewGroupLabel, MenuPreviewSeparator, MenuPreviewSubmenuRoot, MenuPreviewSubmenuTrigger, ClickAwayListener, Popover, Popper githubLabel: 'scope: menu' materialDesign: https://m2.material.io/components/menus waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ diff --git a/docs/data/material/pagesApi.js b/docs/data/material/pagesApi.js index ee76034af9e54a..5ca508d6d9e9eb 100644 --- a/docs/data/material/pagesApi.js +++ b/docs/data/material/pagesApi.js @@ -73,6 +73,22 @@ export default [ { pathname: '/material-ui/api/menu' }, { pathname: '/material-ui/api/menu-item' }, { pathname: '/material-ui/api/menu-list' }, + { pathname: '/material-ui/api/menu-preview' }, + { pathname: '/material-ui/api/menu-preview-checkbox-item' }, + { pathname: '/material-ui/api/menu-preview-checkbox-item-indicator' }, + { pathname: '/material-ui/api/menu-preview-group' }, + { pathname: '/material-ui/api/menu-preview-group-label' }, + { pathname: '/material-ui/api/menu-preview-item' }, + { pathname: '/material-ui/api/menu-preview-link-item' }, + { pathname: '/material-ui/api/menu-preview-popup' }, + { pathname: '/material-ui/api/menu-preview-radio-group' }, + { pathname: '/material-ui/api/menu-preview-radio-item' }, + { pathname: '/material-ui/api/menu-preview-radio-item-indicator' }, + { pathname: '/material-ui/api/menu-preview-separator' }, + { pathname: '/material-ui/api/menu-preview-submenu-popup' }, + { pathname: '/material-ui/api/menu-preview-submenu-root' }, + { pathname: '/material-ui/api/menu-preview-submenu-trigger' }, + { pathname: '/material-ui/api/menu-preview-trigger' }, { pathname: '/material-ui/api/mobile-stepper' }, { pathname: '/material-ui/api/modal' }, { pathname: '/material-ui/api/native-select' }, diff --git a/docs/pages/experiments/menu-preview.tsx b/docs/pages/experiments/menu-preview.tsx new file mode 100644 index 00000000000000..1af4536e68c26e --- /dev/null +++ b/docs/pages/experiments/menu-preview.tsx @@ -0,0 +1,587 @@ +import * as React from 'react'; +import Container from '@mui/material/Container'; +import CssBaseline from '@mui/material/CssBaseline'; +import Popover from '@mui/material/Popover'; +import Stack from '@mui/material/Stack'; +import Tooltip, { type TooltipProps } from '@mui/material/Tooltip'; +import Typography from '@mui/material/Typography'; +import KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded'; +import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded'; +import { ThemeProvider, createTheme, useTheme } from '@mui/material/styles'; +import Menu, { + CheckboxItem, + CheckboxItemIndicator, + Group, + GroupLabel, + Item, + LinkItem, + Popup, + RadioGroup, + RadioItem, + RadioItemIndicator, + Separator, + SubmenuPopup, + SubmenuRoot, + SubmenuTrigger, + Trigger, +} from '@mui/material/MenuPreview'; +import { AppLayoutHead as Head } from '@mui/internal-core-docs/AppLayout'; + +interface MenuSettings { + modal: boolean; + disabled: boolean; + submenusOpenOnHover: boolean; +} + +const theme = createTheme({}); + +const defaultSettings: MenuSettings = { + modal: true, + disabled: false, + submenusOpenOnHover: false, +}; + +interface PreviewCardItem { + id: string; + label: string; + description: string; + footer: string; +} + +const rootPreviewCardItems: PreviewCardItem[] = [ + { + id: 'template-gallery', + label: 'Template gallery', + description: 'Start from a polished document layout for notes, proposals, and project plans.', + footer: 'Opens the template picker', + }, + { + id: 'publish-web', + label: 'Publish to web', + description: 'Create a public read-only page that updates when this document changes.', + footer: 'Requires sharing permission', + }, +]; + +const versionHistoryPreviewCardItems: PreviewCardItem[] = [ + { + id: 'named-versions', + label: 'Named versions', + description: 'Create and manage named checkpoints for important document milestones.', + footer: 'Keeps the current version history', + }, + { + id: 'compare-changes', + label: 'Compare changes', + description: 'Review edits between two versions and inspect who changed each section.', + footer: 'Opens in a side-by-side view', + }, + { + id: 'restore-version', + label: 'Restore version', + description: 'Replace the current document with a selected earlier version.', + footer: 'Creates a new restore checkpoint', + }, +]; + +const previewCardItems = [...rootPreviewCardItems, ...versionHistoryPreviewCardItems]; + +const horizontalTooltipProps = { + placement: 'right', + slotProps: { + popper: { + popperOptions: { + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['left', 'right'], + }, + }, + ], + }, + }, + }, +} satisfies Partial; + +interface MenuTooltipChildProps { + onClickCapture?: React.MouseEventHandler; +} + +function MenuTooltip(props: { + title: string; + children: React.ReactElement; + tooltipProps?: Partial; +}) { + const { title, children, tooltipProps = horizontalTooltipProps } = props; + const [open, setOpen] = React.useState(false); + + const handleOpen = React.useCallback(() => { + setOpen(true); + }, []); + + const handleClose = React.useCallback(() => { + setOpen(false); + }, []); + + const child = React.cloneElement(children, { + onClickCapture: (event: React.MouseEvent) => { + setOpen(false); + children.props.onClickCapture?.(event); + }, + }); + + return ( + + {child} + + ); +} + +function MaterialPreviewCard(props: { + id: string | undefined; + item: PreviewCardItem | null; + anchorEl: HTMLElement | null; +}) { + const { id, item, anchorEl } = props; + const open = Boolean(item && anchorEl); + + return ( + + {item ? ( + + + {item.label} + + + {item.description} + + + {item.footer} + + + ) : null} + + ); +} + +function DisabledTooltip(props: { title: string; children: React.ReactElement }) { + const { title, children } = props; + + return ( + + {/* Disabled menu items need a wrapper for pointer events. This means aria-describedby + is attached to the wrapper, not the disabled menuitem itself. */} + {children} + + ); +} + +function MenuPreviewWithPreviewCardsDemo({ + submenusOpenOnHover, +}: { + submenusOpenOnHover: boolean; +}) { + const previewCardIdPrefix = React.useId(); + const [activeItemId, setActiveItemId] = React.useState(null); + const [anchorEl, setAnchorEl] = React.useState(null); + const activeItem = + previewCardItems.find((previewCardItem) => previewCardItem.id === activeItemId) ?? null; + const activePreviewCardId = activeItem + ? `${previewCardIdPrefix}-${activeItem.id}-preview-card` + : undefined; + + const clearActiveItem = () => { + setActiveItemId(null); + setAnchorEl(null); + }; + + const getPreviewCardProps = (item: PreviewCardItem) => { + const setActiveItem = (element: HTMLElement) => { + setActiveItemId(item.id); + setAnchorEl(element); + }; + + return { + 'aria-describedby': + activeItemId === item.id ? `${previewCardIdPrefix}-${item.id}-preview-card` : undefined, + onFocus: (event: React.FocusEvent) => { + setActiveItem(event.currentTarget); + }, + onMouseEnter: (event: React.MouseEvent) => { + setActiveItem(event.currentTarget); + }, + }; + }; + + return ( + { + if (!open) { + setActiveItemId(null); + setAnchorEl(null); + } + }} + > + }> + Help cards + + + + {rootPreviewCardItems[0].label} + + + + Version history + + + + {versionHistoryPreviewCardItems.map((item) => ( + + {item.label} + + ))} + + + + {rootPreviewCardItems[1].label} + + + + + ); +} + +function MenuPreviewDemo({ settings }: { settings: MenuSettings }) { + const handleItemClick = React.useCallback((event: React.MouseEvent) => { + // eslint-disable-next-line no-console + console.log(`${event.currentTarget.textContent} clicked`); + }, []); + + return ( + + }> + File + + + New document + Open… + Template gallery + Recent documents + Docs help center + Make a copy + + + Rename document + + + Offline editing unavailable + + + + + + View options + + + + + Document display + + + + 100% + + + + Fit + + + + Page width + + + + Custom zoom unavailable + + + + + + + + Show + + + Ruler + + + + Document outline + + + + Line numbers + + + + Page breaks unavailable + + + + + + + + More tools + + + + Word count + Dictionary + Accessibility settings + + + + + + + + Download + + + + Microsoft Word (.docx) + PDF document (.pdf) + Plain text (.txt) + + + + + + Add-ons unavailable + + + + Marketplace + + + + + ); +} + +function MenuPreviewWithTooltipsDemo({ submenusOpenOnHover }: { submenusOpenOnHover: boolean }) { + const { direction } = useTheme(); + const submenuTriggerTooltipProps = React.useMemo>( + () => ({ + placement: direction === 'rtl' ? 'right' : 'left', + slotProps: { + popper: { + popperOptions: { + modifiers: [ + { + // Submenus default to inline-end, so keep this tooltip on + // inline-start instead of letting Popper flip it onto the submenu. + name: 'flip', + enabled: false, + }, + ], + }, + }, + }, + }), + [direction], + ); + + return ( + + }> + Tools + + + + New document + + + Open recent + + + Make a copy + + + Import from Drive + + + Share with people + + + + + + + View options + + + + + + Show + + + + Comments + + + + + + Page breaks + + + + + + + + Zoom + + + + + Fit + + + + + + Custom + + + + + + + + + ); +} + +export default function MenuPreviewExperiment() { + const [settings, setSettings] = React.useState(defaultSettings); + + const handleCheckboxChange = (setting: keyof MenuSettings) => { + return (event: React.ChangeEvent) => { + setSettings((currentSettings) => ({ + ...currentSettings, + [setting]: event.target.checked, + })); + }; + }; + + return ( + + + + + + + Menu Preview + +
+ Demo controls + + + +
+
+

Fully-featured menu with submenus, links, radio groups, and checkbox items.

+ +
+
+

Material UI Tooltip integrated with every menu item.

+ +
+
+

Material UI Popover used as a PreviewCard-style menu item help card.

+ +
+ Base UI Menu API +
+
+
+ ); +} diff --git a/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.js b/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.js new file mode 100644 index 00000000000000..dc73f4f5df5157 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-checkbox-item-indicator/menu-preview-checkbox-item-indicator.json'; +import jsonPageContent from './menu-preview-checkbox-item-indicator.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.json b/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.json new file mode 100644 index 00000000000000..49c213f68f5fef --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-checkbox-item-indicator.json @@ -0,0 +1,51 @@ +{ + "props": { + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "keepMounted": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewCheckboxItemIndicator", + "imports": [ + "import MenuPreviewCheckboxItemIndicator from '@mui/material/MenuPreviewCheckboxItemIndicator';" + ], + "slots": [ + { "name": "root", "description": "", "class": "MuiMenuPreviewCheckboxItemIndicator-root" } + ], + "classes": [ + { + "key": "checked", + "className": "Mui-checked", + "description": "State class applied to the root element if `checked={true}`.", + "isGlobal": true + }, + { + "key": "disabled", + "className": "Mui-disabled", + "description": "State class applied to the root element if `disabled={true}`.", + "isGlobal": true + }, + { + "key": "highlighted", + "className": "MuiMenuPreviewCheckboxItemIndicator-highlighted", + "description": "State class applied to the root element if highlighted.", + "isGlobal": false + } + ], + "muiName": "MuiMenuPreviewCheckboxItemIndicator", + "filename": "/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/MenuPreviewCheckboxItemIndicator.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-checkbox-item.js b/docs/pages/material-ui/api/menu-preview-checkbox-item.js new file mode 100644 index 00000000000000..273dae896d9732 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-checkbox-item.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-checkbox-item/menu-preview-checkbox-item.json'; +import jsonPageContent from './menu-preview-checkbox-item.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-checkbox-item.json b/docs/pages/material-ui/api/menu-preview-checkbox-item.json new file mode 100644 index 00000000000000..e1ee7b88166402 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-checkbox-item.json @@ -0,0 +1,39 @@ +{ + "props": { + "checked": { "type": { "name": "bool" } }, + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeOnClick": { "type": { "name": "bool" }, "default": "false" }, + "component": { "type": { "name": "elementType" } }, + "defaultChecked": { "type": { "name": "bool" }, "default": "false" }, + "dense": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "disableGutters": { "type": { "name": "bool" }, "default": "false" }, + "divider": { "type": { "name": "bool" }, "default": "false" }, + "label": { "type": { "name": "string" } }, + "nativeButton": { "type": { "name": "bool" } }, + "onChange": { "type": { "name": "func" } }, + "selected": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewCheckboxItem", + "imports": ["import MenuPreviewCheckboxItem from '@mui/material/MenuPreviewCheckboxItem';"], + "slots": [{ "name": "root", "description": "", "class": null }], + "classes": [], + "muiName": "MuiMenuPreviewCheckboxItem", + "filename": "/packages/mui-material/src/MenuPreviewCheckboxItem/MenuPreviewCheckboxItem.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-group-label.js b/docs/pages/material-ui/api/menu-preview-group-label.js new file mode 100644 index 00000000000000..b27a7aea1cbebf --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-group-label.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-group-label/menu-preview-group-label.json'; +import jsonPageContent from './menu-preview-group-label.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-group-label.json b/docs/pages/material-ui/api/menu-preview-group-label.json new file mode 100644 index 00000000000000..15ac51dd170114 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-group-label.json @@ -0,0 +1,27 @@ +{ + "props": { + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewGroupLabel", + "imports": ["import MenuPreviewGroupLabel from '@mui/material/MenuPreviewGroupLabel';"], + "slots": [{ "name": "root", "description": "", "class": "MuiMenuPreviewGroupLabel-root" }], + "classes": [], + "muiName": "MuiMenuPreviewGroupLabel", + "filename": "/packages/mui-material/src/MenuPreviewGroupLabel/MenuPreviewGroupLabel.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-group.js b/docs/pages/material-ui/api/menu-preview-group.js new file mode 100644 index 00000000000000..dd950c267f3918 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-group.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-group/menu-preview-group.json'; +import jsonPageContent from './menu-preview-group.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-group.json b/docs/pages/material-ui/api/menu-preview-group.json new file mode 100644 index 00000000000000..0bbc5b7c39d31d --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-group.json @@ -0,0 +1,28 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewGroup", + "imports": ["import MenuPreviewGroup from '@mui/material/MenuPreviewGroup';"], + "slots": [{ "name": "root", "description": "", "class": "MuiMenuPreviewGroup-root" }], + "classes": [], + "muiName": "MuiMenuPreviewGroup", + "filename": "/packages/mui-material/src/MenuPreviewGroup/MenuPreviewGroup.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-item.js b/docs/pages/material-ui/api/menu-preview-item.js new file mode 100644 index 00000000000000..874e8da3b3785d --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-item.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-item/menu-preview-item.json'; +import jsonPageContent from './menu-preview-item.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-item.json b/docs/pages/material-ui/api/menu-preview-item.json new file mode 100644 index 00000000000000..80b360babe0bcc --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-item.json @@ -0,0 +1,36 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeOnClick": { "type": { "name": "bool" }, "default": "true" }, + "component": { "type": { "name": "elementType" } }, + "dense": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "disableGutters": { "type": { "name": "bool" }, "default": "false" }, + "divider": { "type": { "name": "bool" }, "default": "false" }, + "label": { "type": { "name": "string" } }, + "nativeButton": { "type": { "name": "bool" } }, + "selected": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewItem", + "imports": ["import MenuPreviewItem from '@mui/material/MenuPreviewItem';"], + "slots": [{ "name": "root", "description": "", "class": null }], + "classes": [], + "muiName": "MuiMenuPreviewItem", + "filename": "/packages/mui-material/src/MenuPreviewItem/MenuPreviewItem.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-link-item.js b/docs/pages/material-ui/api/menu-preview-link-item.js new file mode 100644 index 00000000000000..a4d03702e1b16c --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-link-item.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-link-item/menu-preview-link-item.json'; +import jsonPageContent from './menu-preview-link-item.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-link-item.json b/docs/pages/material-ui/api/menu-preview-link-item.json new file mode 100644 index 00000000000000..c1a50c2e21e20b --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-link-item.json @@ -0,0 +1,35 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeOnClick": { "type": { "name": "bool" }, "default": "false" }, + "component": { "type": { "name": "elementType" } }, + "dense": { "type": { "name": "bool" }, "default": "false" }, + "disableGutters": { "type": { "name": "bool" }, "default": "false" }, + "divider": { "type": { "name": "bool" }, "default": "false" }, + "href": { "type": { "name": "string" } }, + "label": { "type": { "name": "string" } }, + "selected": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewLinkItem", + "imports": ["import MenuPreviewLinkItem from '@mui/material/MenuPreviewLinkItem';"], + "slots": [{ "name": "root", "description": "", "class": null }], + "classes": [], + "muiName": "MuiMenuPreviewLinkItem", + "filename": "/packages/mui-material/src/MenuPreviewLinkItem/MenuPreviewLinkItem.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-popup.js b/docs/pages/material-ui/api/menu-preview-popup.js new file mode 100644 index 00000000000000..1a9fcae0e09e49 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-popup.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-popup/menu-preview-popup.json'; +import jsonPageContent from './menu-preview-popup.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-popup.json b/docs/pages/material-ui/api/menu-preview-popup.json new file mode 100644 index 00000000000000..74825d504428e0 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-popup.json @@ -0,0 +1,141 @@ +{ + "props": { + "align": { + "type": { + "name": "enum", + "description": "'center'
| 'end'
| 'start'" + }, + "default": "'start'" + }, + "alignOffset": { + "type": { "name": "union", "description": "func
| number" }, + "default": "0" + }, + "anchor": { + "type": { + "name": "union", + "description": "HTML element
| object
| func" + } + }, + "arrowPadding": { "type": { "name": "number" }, "default": "5" }, + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "collisionAvoidance": { + "type": { + "name": "union", + "description": "{ align?: 'flip'
| 'none'
| 'shift', fallbackAxisSide?: 'end'
| 'none'
| 'start', side?: 'flip'
| 'none' }
| { align?: 'none'
| 'shift', fallbackAxisSide?: 'end'
| 'none'
| 'start', side?: 'none'
| 'shift' }" + } + }, + "collisionBoundary": { + "type": { + "name": "union", + "description": "'clipping-ancestors'
| HTML element
| Array<HTML element>
| { height: number, width: number, x: number, y: number }" + }, + "default": "'clipping-ancestors'" + }, + "collisionPadding": { + "type": { + "name": "union", + "description": "number
| { bottom?: number, left?: number, right?: number, top?: number }" + }, + "default": "5" + }, + "container": { + "type": { + "name": "union", + "description": "HTML element
| object
| func" + } + }, + "disableAnchorTracking": { "type": { "name": "bool" }, "default": "false" }, + "finalFocus": { + "type": { + "name": "union", + "description": "func
| { current?: HTML element }
| bool" + } + }, + "keepMounted": { "type": { "name": "bool" }, "default": "false" }, + "positionMethod": { + "type": { "name": "enum", "description": "'absolute'
| 'fixed'" }, + "default": "'absolute'" + }, + "side": { + "type": { + "name": "enum", + "description": "'bottom'
| 'inline-end'
| 'inline-start'
| 'left'
| 'right'
| 'top'" + }, + "default": "'bottom'" + }, + "sideOffset": { + "type": { "name": "union", "description": "func
| number" }, + "default": "0" + }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ list?: func
| object, paper?: func
| object, popup?: func
| object, portal?: func
| object, positioner?: func
| object }" + } + }, + "slots": { + "type": { + "name": "shape", + "description": "{ list?: elementType, paper?: elementType, popup?: elementType, portal?: elementType, positioner?: elementType }" + } + }, + "sticky": { "type": { "name": "bool" }, "default": "false" }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewPopup", + "imports": ["import MenuPreviewPopup from '@mui/material/MenuPreviewPopup';"], + "slots": [ + { + "name": "portal", + "description": "The component used for the portal.", + "default": "BaseMenu.Portal", + "class": null + }, + { + "name": "positioner", + "description": "The component used for the positioner.", + "default": "BaseMenu.Positioner", + "class": null + }, + { + "name": "popup", + "description": "The component rendered by the Base UI popup.", + "default": "'div'", + "class": null + }, + { + "name": "paper", + "description": "The component used for the Material surface.", + "default": "Paper", + "class": "MuiMenuPreviewPopup-paper" + }, + { + "name": "list", + "description": "The component used for the presentational list wrapper.", + "default": "List", + "class": "MuiMenuPreviewPopup-list" + } + ], + "classes": [ + { + "key": "root", + "className": "MuiMenuPreviewPopup-root", + "description": "Styles applied to the root element.", + "isGlobal": false + } + ], + "muiName": "MuiMenuPreviewPopup", + "filename": "/packages/mui-material/src/MenuPreviewPopup/MenuPreviewPopup.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-group.js b/docs/pages/material-ui/api/menu-preview-radio-group.js new file mode 100644 index 00000000000000..3b9f531b0034b3 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-group.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-radio-group/menu-preview-radio-group.json'; +import jsonPageContent from './menu-preview-radio-group.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-group.json b/docs/pages/material-ui/api/menu-preview-radio-group.json new file mode 100644 index 00000000000000..c972d5644f3576 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-group.json @@ -0,0 +1,39 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "defaultValue": { "type": { "name": "any" } }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "onChange": { "type": { "name": "func" } }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + }, + "value": { "type": { "name": "any" } } + }, + "name": "MenuPreviewRadioGroup", + "imports": ["import MenuPreviewRadioGroup from '@mui/material/MenuPreviewRadioGroup';"], + "slots": [{ "name": "root", "description": "", "class": "MuiMenuPreviewRadioGroup-root" }], + "classes": [ + { + "key": "disabled", + "className": "Mui-disabled", + "description": "State class applied to the root element if `disabled={true}`.", + "isGlobal": true + } + ], + "muiName": "MuiMenuPreviewRadioGroup", + "filename": "/packages/mui-material/src/MenuPreviewRadioGroup/MenuPreviewRadioGroup.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-item-indicator.js b/docs/pages/material-ui/api/menu-preview-radio-item-indicator.js new file mode 100644 index 00000000000000..d44d1d01f14efc --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-item-indicator.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-radio-item-indicator/menu-preview-radio-item-indicator.json'; +import jsonPageContent from './menu-preview-radio-item-indicator.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-item-indicator.json b/docs/pages/material-ui/api/menu-preview-radio-item-indicator.json new file mode 100644 index 00000000000000..cc0278e7e2ba7d --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-item-indicator.json @@ -0,0 +1,51 @@ +{ + "props": { + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "keepMounted": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewRadioItemIndicator", + "imports": [ + "import MenuPreviewRadioItemIndicator from '@mui/material/MenuPreviewRadioItemIndicator';" + ], + "slots": [ + { "name": "root", "description": "", "class": "MuiMenuPreviewRadioItemIndicator-root" } + ], + "classes": [ + { + "key": "checked", + "className": "Mui-checked", + "description": "State class applied to the root element if `checked={true}`.", + "isGlobal": true + }, + { + "key": "disabled", + "className": "Mui-disabled", + "description": "State class applied to the root element if `disabled={true}`.", + "isGlobal": true + }, + { + "key": "highlighted", + "className": "MuiMenuPreviewRadioItemIndicator-highlighted", + "description": "State class applied to the root element if highlighted.", + "isGlobal": false + } + ], + "muiName": "MuiMenuPreviewRadioItemIndicator", + "filename": "/packages/mui-material/src/MenuPreviewRadioItemIndicator/MenuPreviewRadioItemIndicator.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-item.js b/docs/pages/material-ui/api/menu-preview-radio-item.js new file mode 100644 index 00000000000000..fc06a00097e28e --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-item.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-radio-item/menu-preview-radio-item.json'; +import jsonPageContent from './menu-preview-radio-item.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-radio-item.json b/docs/pages/material-ui/api/menu-preview-radio-item.json new file mode 100644 index 00000000000000..bb264de79c2630 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-radio-item.json @@ -0,0 +1,37 @@ +{ + "props": { + "value": { "type": { "name": "any" }, "required": true }, + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeOnClick": { "type": { "name": "bool" }, "default": "false" }, + "component": { "type": { "name": "elementType" } }, + "dense": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "disableGutters": { "type": { "name": "bool" }, "default": "false" }, + "divider": { "type": { "name": "bool" }, "default": "false" }, + "label": { "type": { "name": "string" } }, + "nativeButton": { "type": { "name": "bool" } }, + "selected": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewRadioItem", + "imports": ["import MenuPreviewRadioItem from '@mui/material/MenuPreviewRadioItem';"], + "slots": [{ "name": "root", "description": "", "class": null }], + "classes": [], + "muiName": "MuiMenuPreviewRadioItem", + "filename": "/packages/mui-material/src/MenuPreviewRadioItem/MenuPreviewRadioItem.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-separator.js b/docs/pages/material-ui/api/menu-preview-separator.js new file mode 100644 index 00000000000000..24e4dee90d1c5d --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-separator.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-separator/menu-preview-separator.json'; +import jsonPageContent from './menu-preview-separator.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-separator.json b/docs/pages/material-ui/api/menu-preview-separator.json new file mode 100644 index 00000000000000..f601b79a6a6eba --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-separator.json @@ -0,0 +1,31 @@ +{ + "props": { + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "component": { "type": { "name": "elementType" } }, + "orientation": { + "type": { "name": "enum", "description": "'horizontal'
| 'vertical'" }, + "default": "'horizontal'" + }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewSeparator", + "imports": ["import MenuPreviewSeparator from '@mui/material/MenuPreviewSeparator';"], + "slots": [{ "name": "root", "description": "", "class": "MuiMenuPreviewSeparator-root" }], + "classes": [], + "muiName": "MuiMenuPreviewSeparator", + "filename": "/packages/mui-material/src/MenuPreviewSeparator/MenuPreviewSeparator.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-popup.js b/docs/pages/material-ui/api/menu-preview-submenu-popup.js new file mode 100644 index 00000000000000..1feb03c4f823ef --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-popup.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-submenu-popup/menu-preview-submenu-popup.json'; +import jsonPageContent from './menu-preview-submenu-popup.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-popup.json b/docs/pages/material-ui/api/menu-preview-submenu-popup.json new file mode 100644 index 00000000000000..edb42f9acb55e1 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-popup.json @@ -0,0 +1,141 @@ +{ + "props": { + "align": { + "type": { + "name": "enum", + "description": "'center'
| 'end'
| 'start'" + }, + "default": "'start'" + }, + "alignOffset": { + "type": { "name": "union", "description": "func
| number" }, + "default": "0" + }, + "anchor": { + "type": { + "name": "union", + "description": "HTML element
| object
| func" + } + }, + "arrowPadding": { "type": { "name": "number" }, "default": "5" }, + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "collisionAvoidance": { + "type": { + "name": "union", + "description": "{ align?: 'flip'
| 'none'
| 'shift', fallbackAxisSide?: 'end'
| 'none'
| 'start', side?: 'flip'
| 'none' }
| { align?: 'none'
| 'shift', fallbackAxisSide?: 'end'
| 'none'
| 'start', side?: 'none'
| 'shift' }" + } + }, + "collisionBoundary": { + "type": { + "name": "union", + "description": "'clipping-ancestors'
| HTML element
| Array<HTML element>
| { height: number, width: number, x: number, y: number }" + }, + "default": "'clipping-ancestors'" + }, + "collisionPadding": { + "type": { + "name": "union", + "description": "number
| { bottom?: number, left?: number, right?: number, top?: number }" + }, + "default": "5" + }, + "container": { + "type": { + "name": "union", + "description": "HTML element
| object
| func" + } + }, + "disableAnchorTracking": { "type": { "name": "bool" }, "default": "false" }, + "finalFocus": { + "type": { + "name": "union", + "description": "func
| { current?: HTML element }
| bool" + } + }, + "keepMounted": { "type": { "name": "bool" }, "default": "false" }, + "positionMethod": { + "type": { "name": "enum", "description": "'absolute'
| 'fixed'" }, + "default": "'absolute'" + }, + "side": { + "type": { + "name": "enum", + "description": "'bottom'
| 'inline-end'
| 'inline-start'
| 'left'
| 'right'
| 'top'" + }, + "default": "'inline-end'" + }, + "sideOffset": { + "type": { "name": "union", "description": "func
| number" }, + "default": "0" + }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ list?: func
| object, paper?: func
| object, popup?: func
| object, portal?: func
| object, positioner?: func
| object }" + } + }, + "slots": { + "type": { + "name": "shape", + "description": "{ list?: elementType, paper?: elementType, popup?: elementType, portal?: elementType, positioner?: elementType }" + } + }, + "sticky": { "type": { "name": "bool" }, "default": "false" }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewSubmenuPopup", + "imports": ["import MenuPreviewSubmenuPopup from '@mui/material/MenuPreviewSubmenuPopup';"], + "slots": [ + { + "name": "portal", + "description": "The component used for the portal.", + "default": "BaseMenu.Portal", + "class": null + }, + { + "name": "positioner", + "description": "The component used for the positioner.", + "default": "BaseMenu.Positioner", + "class": null + }, + { + "name": "popup", + "description": "The component rendered by the Base UI popup.", + "default": "'div'", + "class": null + }, + { + "name": "paper", + "description": "The component used for the Material surface.", + "default": "Paper", + "class": "MuiMenuPreviewSubmenuPopup-paper" + }, + { + "name": "list", + "description": "The component used for the presentational list wrapper.", + "default": "List", + "class": "MuiMenuPreviewSubmenuPopup-list" + } + ], + "classes": [ + { + "key": "root", + "className": "MuiMenuPreviewSubmenuPopup-root", + "description": "Styles applied to the root element.", + "isGlobal": false + } + ], + "muiName": "MuiMenuPreviewSubmenuPopup", + "filename": "/packages/mui-material/src/MenuPreviewSubmenuPopup/MenuPreviewSubmenuPopup.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-root.js b/docs/pages/material-ui/api/menu-preview-submenu-root.js new file mode 100644 index 00000000000000..5f12e3a1db11de --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-root.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-submenu-root/menu-preview-submenu-root.json'; +import jsonPageContent from './menu-preview-submenu-root.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-root.json b/docs/pages/material-ui/api/menu-preview-submenu-root.json new file mode 100644 index 00000000000000..3ec88efda62789 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-root.json @@ -0,0 +1,24 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "closeParentOnEsc": { "type": { "name": "bool" }, "default": "false" }, + "defaultOpen": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "highlightItemOnHover": { "type": { "name": "bool" }, "default": "true" }, + "loopFocus": { "type": { "name": "bool" }, "default": "true" }, + "onOpenChange": { "type": { "name": "func" } }, + "onOpenChangeComplete": { "type": { "name": "func" } }, + "open": { "type": { "name": "bool" } }, + "orientation": { + "type": { "name": "enum", "description": "'horizontal'
| 'vertical'" }, + "default": "'vertical'" + } + }, + "name": "MenuPreviewSubmenuRoot", + "imports": ["import MenuPreviewSubmenuRoot from '@mui/material/MenuPreviewSubmenuRoot';"], + "classes": [], + "muiName": "MuiMenuPreviewSubmenuRoot", + "filename": "/packages/mui-material/src/MenuPreviewSubmenuRoot/MenuPreviewSubmenuRoot.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-trigger.js b/docs/pages/material-ui/api/menu-preview-submenu-trigger.js new file mode 100644 index 00000000000000..4d4d3bcc553a57 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-trigger.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-submenu-trigger/menu-preview-submenu-trigger.json'; +import jsonPageContent from './menu-preview-submenu-trigger.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-submenu-trigger.json b/docs/pages/material-ui/api/menu-preview-submenu-trigger.json new file mode 100644 index 00000000000000..387fe73eb537b9 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-submenu-trigger.json @@ -0,0 +1,38 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeDelay": { "type": { "name": "number" }, "default": "0" }, + "component": { "type": { "name": "elementType" } }, + "delay": { "type": { "name": "number" }, "default": "100" }, + "dense": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "disableGutters": { "type": { "name": "bool" }, "default": "false" }, + "divider": { "type": { "name": "bool" }, "default": "false" }, + "label": { "type": { "name": "string" } }, + "nativeButton": { "type": { "name": "bool" } }, + "openOnHover": { "type": { "name": "bool" } }, + "selected": { "type": { "name": "bool" }, "default": "false" }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewSubmenuTrigger", + "imports": ["import MenuPreviewSubmenuTrigger from '@mui/material/MenuPreviewSubmenuTrigger';"], + "slots": [{ "name": "root", "description": "", "class": null }], + "classes": [], + "muiName": "MuiMenuPreviewSubmenuTrigger", + "filename": "/packages/mui-material/src/MenuPreviewSubmenuTrigger/MenuPreviewSubmenuTrigger.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview-trigger.js b/docs/pages/material-ui/api/menu-preview-trigger.js new file mode 100644 index 00000000000000..623e31aedb5914 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-trigger.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview-trigger/menu-preview-trigger.json'; +import jsonPageContent from './menu-preview-trigger.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview-trigger.json b/docs/pages/material-ui/api/menu-preview-trigger.json new file mode 100644 index 00000000000000..96aca0e828262b --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview-trigger.json @@ -0,0 +1,45 @@ +{ + "props": { + "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, + "className": { "type": { "name": "string" } }, + "closeDelay": { "type": { "name": "number" }, "default": "0" }, + "component": { "type": { "name": "elementType" } }, + "delay": { "type": { "name": "number" }, "default": "100" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "nativeButton": { "type": { "name": "bool" } }, + "openOnHover": { "type": { "name": "bool" } }, + "slotProps": { + "type": { "name": "shape", "description": "{ root?: func
| object }" } + }, + "slots": { "type": { "name": "shape", "description": "{ root?: elementType }" } }, + "style": { "type": { "name": "object" } }, + "sx": { + "type": { + "name": "union", + "description": "Array<func
| object
| bool>
| func
| object" + }, + "additionalInfo": { "sx": true } + } + }, + "name": "MenuPreviewTrigger", + "imports": ["import MenuPreviewTrigger from '@mui/material/MenuPreviewTrigger';"], + "slots": [{ "name": "root", "description": "", "class": "MuiMenuPreviewTrigger-root" }], + "classes": [ + { + "key": "disabled", + "className": "Mui-disabled", + "description": "State class applied to the root element if `disabled={true}`.", + "isGlobal": true + }, + { + "key": "open", + "className": "Mui-open", + "description": "State class applied to the root element if the menu is open.", + "isGlobal": true + } + ], + "muiName": "MuiMenuPreviewTrigger", + "filename": "/packages/mui-material/src/MenuPreviewTrigger/MenuPreviewTrigger.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/pages/material-ui/api/menu-preview.js b/docs/pages/material-ui/api/menu-preview.js new file mode 100644 index 00000000000000..3cb1b61dd994ee --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview.js @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { ApiPage } from '@mui/internal-core-docs/ApiPage'; +import { mapApiPageTranslation } from '@mui/internal-core-docs/mapApiPageTranslations'; +import translation from 'docs/translations/api-docs/menu-preview/menu-preview.json'; +import jsonPageContent from './menu-preview.json'; + +export default function Page(props) { + const { descriptions } = props; + return ; +} + +export async function getStaticProps() { + const descriptions = mapApiPageTranslation(translation); + + return { props: { descriptions } }; +} diff --git a/docs/pages/material-ui/api/menu-preview.json b/docs/pages/material-ui/api/menu-preview.json new file mode 100644 index 00000000000000..084edc4242f118 --- /dev/null +++ b/docs/pages/material-ui/api/menu-preview.json @@ -0,0 +1,27 @@ +{ + "props": { + "children": { "type": { "name": "node" } }, + "closeParentOnEsc": { "type": { "name": "bool" }, "default": "false" }, + "defaultOpen": { "type": { "name": "bool" }, "default": "false" }, + "disabled": { "type": { "name": "bool" }, "default": "false" }, + "highlightItemOnHover": { "type": { "name": "bool" }, "default": "true" }, + "loopFocus": { "type": { "name": "bool" }, "default": "true" }, + "modal": { "type": { "name": "bool" }, "default": "true" }, + "onOpenChange": { "type": { "name": "func" } }, + "onOpenChangeComplete": { "type": { "name": "func" } }, + "open": { "type": { "name": "bool" } }, + "orientation": { + "type": { "name": "enum", "description": "'horizontal'
| 'vertical'" }, + "default": "'vertical'" + } + }, + "name": "MenuPreview", + "imports": ["import MenuPreview from '@mui/material/MenuPreview';"], + "classes": [], + "spread": true, + "themeDefaultProps": null, + "muiName": "MuiMenuPreview", + "filename": "/packages/mui-material/src/MenuPreview/MenuPreview.tsx", + "inheritance": null, + "demos": "" +} diff --git a/docs/translations/api-docs/menu-preview-checkbox-item-indicator/menu-preview-checkbox-item-indicator.json b/docs/translations/api-docs/menu-preview-checkbox-item-indicator/menu-preview-checkbox-item-indicator.json new file mode 100644 index 00000000000000..2849161fdc3460 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-checkbox-item-indicator/menu-preview-checkbox-item-indicator.json @@ -0,0 +1,35 @@ +{ + "componentDescription": "", + "propDescriptions": { + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "keepMounted": { + "description": "Whether to keep the HTML element in the DOM when the checkbox item is not checked." + }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": { + "checked": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "checked={true}" + }, + "disabled": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "disabled={true}" + }, + "highlighted": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "highlighted" + } + }, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-checkbox-item/menu-preview-checkbox-item.json b/docs/translations/api-docs/menu-preview-checkbox-item/menu-preview-checkbox-item.json new file mode 100644 index 00000000000000..fc921efed518e8 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-checkbox-item/menu-preview-checkbox-item.json @@ -0,0 +1,44 @@ +{ + "componentDescription": "", + "propDescriptions": { + "checked": { + "description": "Whether the checkbox item is currently ticked.
To render an uncontrolled checkbox item, use the defaultChecked prop instead." + }, + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeOnClick": { "description": "Whether to close the menu when the item is clicked." }, + "component": { "description": "The component used for the root node." }, + "defaultChecked": { + "description": "Whether the checkbox item is initially ticked.
To render a controlled checkbox item, use the checked prop instead." + }, + "dense": { + "description": "If true, compact vertical padding designed for keyboard and mouse input is used." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "disableGutters": { + "description": "If true, the left and right padding is removed." + }, + "divider": { + "description": "If true, a 1px light border is added to the bottom of the menu item." + }, + "label": { + "description": "Overrides the text label to use when the item is matched during keyboard text navigation." + }, + "nativeButton": { + "description": "Whether the component is rendered as a native button.
By default, this is inferred from the root slot and component prop." + }, + "onChange": { + "description": "Event handler called when the checkbox item is ticked or unticked." + }, + "selected": { "description": "If true, the component is selected." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-group-label/menu-preview-group-label.json b/docs/translations/api-docs/menu-preview-group-label/menu-preview-group-label.json new file mode 100644 index 00000000000000..8c92b392b4a3d8 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-group-label/menu-preview-group-label.json @@ -0,0 +1,16 @@ +{ + "componentDescription": "", + "propDescriptions": { + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-group/menu-preview-group.json b/docs/translations/api-docs/menu-preview-group/menu-preview-group.json new file mode 100644 index 00000000000000..8743d2e26e1f58 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-group/menu-preview-group.json @@ -0,0 +1,17 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-item/menu-preview-item.json b/docs/translations/api-docs/menu-preview-item/menu-preview-item.json new file mode 100644 index 00000000000000..552a6e719d95ae --- /dev/null +++ b/docs/translations/api-docs/menu-preview-item/menu-preview-item.json @@ -0,0 +1,35 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeOnClick": { "description": "Whether to close the menu when the item is clicked." }, + "component": { "description": "The component used for the root node." }, + "dense": { + "description": "If true, compact vertical padding designed for keyboard and mouse input is used." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "disableGutters": { + "description": "If true, the left and right padding is removed." + }, + "divider": { + "description": "If true, a 1px light border is added to the bottom of the menu item." + }, + "label": { + "description": "Overrides the text label to use when the item is matched during keyboard text navigation." + }, + "nativeButton": { + "description": "Whether the component is rendered as a native button.
By default, this is inferred from the root slot and component prop." + }, + "selected": { "description": "If true, the component is selected." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-link-item/menu-preview-link-item.json b/docs/translations/api-docs/menu-preview-link-item/menu-preview-link-item.json new file mode 100644 index 00000000000000..79bd5fbfce859a --- /dev/null +++ b/docs/translations/api-docs/menu-preview-link-item/menu-preview-link-item.json @@ -0,0 +1,32 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeOnClick": { "description": "Whether to close the menu when the item is clicked." }, + "component": { "description": "The component used for the root node." }, + "dense": { + "description": "If true, compact vertical padding designed for keyboard and mouse input is used." + }, + "disableGutters": { + "description": "If true, the left and right padding is removed." + }, + "divider": { + "description": "If true, a 1px light border is added to the bottom of the menu item." + }, + "href": { "description": "The URL that the link item points to." }, + "label": { + "description": "Overrides the text label to use when the item is matched during keyboard text navigation." + }, + "selected": { "description": "If true, the component is selected." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-popup/menu-preview-popup.json b/docs/translations/api-docs/menu-preview-popup/menu-preview-popup.json new file mode 100644 index 00000000000000..3c92b01c458026 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-popup/menu-preview-popup.json @@ -0,0 +1,55 @@ +{ + "componentDescription": "", + "propDescriptions": { + "align": { "description": "How to align the popup relative to the specified side." }, + "alignOffset": { "description": "Additional offset along the alignment axis in pixels." }, + "anchor": { + "description": "An element to position the popup against.
By default, the popup is positioned against the trigger." + }, + "arrowPadding": { + "description": "Minimum distance to maintain between the arrow and the edges of the popup." + }, + "children": { "description": "The menu items." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the Base UI popup element." }, + "collisionAvoidance": { + "description": "Determines how to handle collisions when positioning the popup." + }, + "collisionBoundary": { + "description": "An element or a rectangle that delimits the area that the popup is confined to." + }, + "collisionPadding": { + "description": "Additional space to maintain from the edge of the collision boundary." + }, + "container": { "description": "The container element to portal the popup into." }, + "disableAnchorTracking": { + "description": "Whether to disable the popup from tracking layout shifts of its positioning anchor." + }, + "finalFocus": { "description": "Determines the element to focus when the menu is closed." }, + "keepMounted": { + "description": "Whether to keep the portal mounted in the DOM while the popup is hidden." + }, + "positionMethod": { + "description": "Determines which CSS position property to use." + }, + "side": { "description": "Which side of the anchor element to align the popup against." }, + "sideOffset": { "description": "Distance between the anchor and the popup in pixels." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "sticky": { + "description": "Whether to maintain the popup in the viewport after the anchor element was scrolled out of view." + }, + "style": { "description": "Styles applied to the Base UI popup element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": { "root": { "description": "Styles applied to the root element." } }, + "slotDescriptions": { + "list": "The component used for the presentational list wrapper.", + "paper": "The component used for the Material surface.", + "popup": "The component rendered by the Base UI popup.", + "portal": "The component used for the portal.", + "positioner": "The component used for the positioner." + } +} diff --git a/docs/translations/api-docs/menu-preview-radio-group/menu-preview-radio-group.json b/docs/translations/api-docs/menu-preview-radio-group/menu-preview-radio-group.json new file mode 100644 index 00000000000000..8d6137b9a1f39a --- /dev/null +++ b/docs/translations/api-docs/menu-preview-radio-group/menu-preview-radio-group.json @@ -0,0 +1,31 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "defaultValue": { + "description": "The uncontrolled value of the radio item that should be initially selected." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "onChange": { "description": "Function called when the selected value changes." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + }, + "value": { + "description": "The controlled value of the radio item that should be currently selected." + } + }, + "classDescriptions": { + "disabled": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "disabled={true}" + } + }, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-radio-item-indicator/menu-preview-radio-item-indicator.json b/docs/translations/api-docs/menu-preview-radio-item-indicator/menu-preview-radio-item-indicator.json new file mode 100644 index 00000000000000..faa8d5c3697420 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-radio-item-indicator/menu-preview-radio-item-indicator.json @@ -0,0 +1,35 @@ +{ + "componentDescription": "", + "propDescriptions": { + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "keepMounted": { + "description": "Whether to keep the HTML element in the DOM when the radio item is inactive." + }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": { + "checked": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "checked={true}" + }, + "disabled": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "disabled={true}" + }, + "highlighted": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "highlighted" + } + }, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-radio-item/menu-preview-radio-item.json b/docs/translations/api-docs/menu-preview-radio-item/menu-preview-radio-item.json new file mode 100644 index 00000000000000..03866a7e3ba528 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-radio-item/menu-preview-radio-item.json @@ -0,0 +1,36 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeOnClick": { "description": "Whether to close the menu when the item is clicked." }, + "component": { "description": "The component used for the root node." }, + "dense": { + "description": "If true, compact vertical padding designed for keyboard and mouse input is used." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "disableGutters": { + "description": "If true, the left and right padding is removed." + }, + "divider": { + "description": "If true, a 1px light border is added to the bottom of the menu item." + }, + "label": { + "description": "Overrides the text label to use when the item is matched during keyboard text navigation." + }, + "nativeButton": { + "description": "Whether the component is rendered as a native button.
By default, this is inferred from the root slot and component prop." + }, + "selected": { "description": "If true, the component is selected." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + }, + "value": { "description": "Value of the radio item." } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-separator/menu-preview-separator.json b/docs/translations/api-docs/menu-preview-separator/menu-preview-separator.json new file mode 100644 index 00000000000000..39ea1cd2fc4e6b --- /dev/null +++ b/docs/translations/api-docs/menu-preview-separator/menu-preview-separator.json @@ -0,0 +1,17 @@ +{ + "componentDescription": "", + "propDescriptions": { + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "component": { "description": "The component used for the root node." }, + "orientation": { "description": "The orientation of the separator." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-submenu-popup/menu-preview-submenu-popup.json b/docs/translations/api-docs/menu-preview-submenu-popup/menu-preview-submenu-popup.json new file mode 100644 index 00000000000000..24661184a10744 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-submenu-popup/menu-preview-submenu-popup.json @@ -0,0 +1,55 @@ +{ + "componentDescription": "", + "propDescriptions": { + "align": { "description": "How to align the popup relative to the specified side." }, + "alignOffset": { "description": "Additional offset along the alignment axis in pixels." }, + "anchor": { + "description": "An element to position the popup against.
By default, the popup is positioned against the submenu trigger." + }, + "arrowPadding": { + "description": "Minimum distance to maintain between the arrow and the edges of the popup." + }, + "children": { "description": "The submenu items." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the Base UI popup element." }, + "collisionAvoidance": { + "description": "Determines how to handle collisions when positioning the popup." + }, + "collisionBoundary": { + "description": "An element or a rectangle that delimits the area that the popup is confined to." + }, + "collisionPadding": { + "description": "Additional space to maintain from the edge of the collision boundary." + }, + "container": { "description": "The container element to portal the popup into." }, + "disableAnchorTracking": { + "description": "Whether to disable the popup from tracking layout shifts of its positioning anchor." + }, + "finalFocus": { "description": "Determines the element to focus when the menu is closed." }, + "keepMounted": { + "description": "Whether to keep the portal mounted in the DOM while the popup is hidden." + }, + "positionMethod": { + "description": "Determines which CSS position property to use." + }, + "side": { "description": "Which side of the anchor element to align the popup against." }, + "sideOffset": { "description": "Distance between the anchor and the popup in pixels." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "sticky": { + "description": "Whether to maintain the popup in the viewport after the anchor element was scrolled out of view." + }, + "style": { "description": "Styles applied to the Base UI popup element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": { "root": { "description": "Styles applied to the root element." } }, + "slotDescriptions": { + "list": "The component used for the presentational list wrapper.", + "paper": "The component used for the Material surface.", + "popup": "The component rendered by the Base UI popup.", + "portal": "The component used for the portal.", + "positioner": "The component used for the positioner." + } +} diff --git a/docs/translations/api-docs/menu-preview-submenu-root/menu-preview-submenu-root.json b/docs/translations/api-docs/menu-preview-submenu-root/menu-preview-submenu-root.json new file mode 100644 index 00000000000000..ff6614738e49c7 --- /dev/null +++ b/docs/translations/api-docs/menu-preview-submenu-root/menu-preview-submenu-root.json @@ -0,0 +1,24 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the submenu." }, + "closeParentOnEsc": { + "description": "When in a submenu, determines whether pressing the Escape key closes the entire menu." + }, + "defaultOpen": { + "description": "Whether the submenu is initially open.
To render a controlled submenu, use the open prop instead." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "highlightItemOnHover": { + "description": "Whether moving the pointer over items should highlight them." + }, + "loopFocus": { "description": "Whether to loop keyboard focus back to the first item." }, + "onOpenChange": { "description": "Event handler called when the submenu is opened or closed." }, + "onOpenChangeComplete": { + "description": "Event handler called after any animations complete when the submenu is opened or closed." + }, + "open": { "description": "Whether the submenu is currently open." }, + "orientation": { "description": "The visual orientation of the submenu." } + }, + "classDescriptions": {} +} diff --git a/docs/translations/api-docs/menu-preview-submenu-trigger/menu-preview-submenu-trigger.json b/docs/translations/api-docs/menu-preview-submenu-trigger/menu-preview-submenu-trigger.json new file mode 100644 index 00000000000000..e577f74db637bf --- /dev/null +++ b/docs/translations/api-docs/menu-preview-submenu-trigger/menu-preview-submenu-trigger.json @@ -0,0 +1,43 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the component." }, + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeDelay": { + "description": "How long to wait before closing the submenu that was opened on hover, in milliseconds.
Requires the openOnHover prop." + }, + "component": { "description": "The component used for the root node." }, + "delay": { + "description": "How long to wait before the submenu may be opened on hover, in milliseconds.
Requires the openOnHover prop." + }, + "dense": { + "description": "If true, compact vertical padding designed for keyboard and mouse input is used." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "disableGutters": { + "description": "If true, the left and right padding is removed." + }, + "divider": { + "description": "If true, a 1px light border is added to the bottom of the menu item." + }, + "label": { + "description": "Overrides the text label to use when the item is matched during keyboard text navigation." + }, + "nativeButton": { + "description": "Whether the component is rendered as a native button.
By default, this is inferred from the root slot and component prop." + }, + "openOnHover": { + "description": "Whether the submenu should also open when the trigger is hovered." + }, + "selected": { "description": "If true, the component is selected." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": {}, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview-trigger/menu-preview-trigger.json b/docs/translations/api-docs/menu-preview-trigger/menu-preview-trigger.json new file mode 100644 index 00000000000000..1b289124ab482c --- /dev/null +++ b/docs/translations/api-docs/menu-preview-trigger/menu-preview-trigger.json @@ -0,0 +1,40 @@ +{ + "componentDescription": "", + "propDescriptions": { + "classes": { "description": "Override or extend the styles applied to the component." }, + "className": { "description": "CSS class applied to the element." }, + "closeDelay": { + "description": "How long to wait before closing the menu that was opened on hover, in milliseconds.
Requires the openOnHover prop." + }, + "component": { "description": "The component used for the root node." }, + "delay": { + "description": "How long to wait before the menu may be opened on hover, in milliseconds.
Requires the openOnHover prop." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "nativeButton": { + "description": "Whether the component is rendered as a native button.
By default, this is inferred from the root slot and component prop." + }, + "openOnHover": { + "description": "Whether the menu should also open when the trigger is hovered." + }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, + "style": { "description": "Styles applied to the root element." }, + "sx": { + "description": "The system prop that allows defining system overrides as well as additional CSS styles." + } + }, + "classDescriptions": { + "disabled": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "disabled={true}" + }, + "open": { + "description": "State class applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the root element", + "conditions": "the menu is open" + } + }, + "slotDescriptions": { "root": "" } +} diff --git a/docs/translations/api-docs/menu-preview/menu-preview.json b/docs/translations/api-docs/menu-preview/menu-preview.json new file mode 100644 index 00000000000000..31e7124fe6bf7f --- /dev/null +++ b/docs/translations/api-docs/menu-preview/menu-preview.json @@ -0,0 +1,25 @@ +{ + "componentDescription": "", + "propDescriptions": { + "children": { "description": "The content of the menu." }, + "closeParentOnEsc": { + "description": "When in a submenu, determines whether pressing the Escape key closes the entire menu." + }, + "defaultOpen": { + "description": "Whether the menu is initially open.
To render a controlled menu, use the open prop instead." + }, + "disabled": { "description": "Whether the component should ignore user interaction." }, + "highlightItemOnHover": { + "description": "Whether moving the pointer over items should highlight them." + }, + "loopFocus": { "description": "Whether to loop keyboard focus back to the first item." }, + "modal": { "description": "Determines if the menu enters a modal state when open." }, + "onOpenChange": { "description": "Event handler called when the menu is opened or closed." }, + "onOpenChangeComplete": { + "description": "Event handler called after any animations complete when the menu is opened or closed." + }, + "open": { "description": "Whether the menu is currently open." }, + "orientation": { "description": "The visual orientation of the menu." } + }, + "classDescriptions": {} +} diff --git a/packages-internal/api-docs-builder-core/materialUi/projectSettings.ts b/packages-internal/api-docs-builder-core/materialUi/projectSettings.ts index a52477fe5e64cd..e1a85bdc147ba5 100644 --- a/packages-internal/api-docs-builder-core/materialUi/projectSettings.ts +++ b/packages-internal/api-docs-builder-core/materialUi/projectSettings.ts @@ -1,6 +1,6 @@ import path from 'path'; import { LANGUAGES } from '@mui/internal-core-docs/constants'; -import { ProjectSettings, findApiPages } from '@mui/internal-api-docs-builder'; +import { ProjectSettings, findApiPages, toGitHubPath } from '@mui/internal-api-docs-builder'; import generateUtilityClass, { isGlobalState } from '@mui/utils/generateUtilityClass'; import { getMaterialUiComponentInfo } from './getMaterialUiComponentInfo'; @@ -13,6 +13,42 @@ const generateClassName = (componentName: string, slot: string, globalStatePrefi return generateUtilityClass(componentName, slot, globalStatePrefix); }; +const getComponentImports = (name: string, filename: string) => { + const githubPath = toGitHubPath(filename); + const directory = githubPath.match(/\/packages\/mui-material\/src\/([^/]+)\//)?.[1]; + + if (directory?.startsWith('MenuPreview')) { + return [`import ${name} from '@mui/material/${directory}';`]; + } + + const rootImportPath = githubPath.replace( + /\/packages\/mui(?:-(.+?))?\/src\/.*/, + (match, pkg) => `@mui/${pkg}`, + ); + + const subdirectoryImportPath = githubPath.replace( + /\/packages\/mui(?:-(.+?))?\/src\/([^\\/]+)\/.*/, + (match, pkg, subdirectory) => `@mui/${pkg}/${subdirectory}`, + ); + + let namedImportName = name; + const defaultImportName = name; + + if (githubPath.includes('Unstable_')) { + namedImportName = `Unstable_${name} as ${name}`; + } + + const useNamedImports = rootImportPath === '@mui/base'; + + const subpathImport = useNamedImports + ? `import { ${namedImportName} } from '${subdirectoryImportPath}';` + : `import ${defaultImportName} from '${subdirectoryImportPath}';`; + + const rootImport = `import { ${namedImportName} } from '${rootImportPath}';`; + + return [subpathImport, rootImport]; +}; + export const projectSettings: ProjectSettings = { output: { apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), @@ -23,6 +59,7 @@ export const projectSettings: ProjectSettings = { rootPath: path.join(process.cwd(), 'packages/mui-material'), entryPointPath: [ 'src/index.d.ts', + 'src/MenuPreview/apiDocs.d.ts', 'src/PigmentStack/PigmentStack.tsx', 'src/PigmentContainer/PigmentContainer.tsx', 'src/PigmentGrid/PigmentGrid.tsx', @@ -41,6 +78,7 @@ export const projectSettings: ProjectSettings = { return filename.match(/(ThemeProvider|CssVarsProvider|DefaultPropsProvider)/) !== null; }, translationPagesDirectory: 'docs/translations/api-docs', + getComponentImports, generateClassName, isGlobalClassName: isGlobalState, // #host-reference diff --git a/packages-internal/core-docs/package.json b/packages-internal/core-docs/package.json index 5efc02b42742bf..009ba5bff1d84d 100644 --- a/packages-internal/core-docs/package.json +++ b/packages-internal/core-docs/package.json @@ -53,7 +53,7 @@ "next": "15.5.18" }, "peerDependencies": { - "@base-ui/react": "^1", + "@base-ui/react": "^1.5.0", "@docsearch/react": "catalog:docs", "@emotion/cache": "catalog:docs", "@emotion/react": "catalog:docs", diff --git a/packages/mui-material/package.json b/packages/mui-material/package.json index 3692d62f304084..598d18c88b64bf 100644 --- a/packages/mui-material/package.json +++ b/packages/mui-material/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@babel/runtime": "^7.29.2", + "@base-ui/react": "^1.5.0", "@mui/core-downloads-tracker": "workspace:^", "@mui/system": "workspace:^", "@mui/types": "workspace:^", diff --git a/packages/mui-material/src/MenuPreview/MenuPreview.spec.tsx b/packages/mui-material/src/MenuPreview/MenuPreview.spec.tsx new file mode 100644 index 00000000000000..70b8079302e562 --- /dev/null +++ b/packages/mui-material/src/MenuPreview/MenuPreview.spec.tsx @@ -0,0 +1,204 @@ +import * as React from 'react'; +import { expectType } from '@mui/types'; +import Menu, { + CheckboxItem, + CheckboxItemIndicator, + Group, + GroupLabel, + Item, + LinkItem, + Popup, + RadioGroup, + RadioItem, + RadioItemIndicator, + Separator, + SubmenuPopup, + SubmenuRoot, + SubmenuTrigger, + Trigger, +} from '@mui/material/MenuPreview'; +import { createTheme } from '@mui/material/styles'; +// @ts-expect-error MenuPreview is intentionally not exported from the root barrel for this POC. +import { MenuPreview as RootBarrelMenuPreview } from '@mui/material'; + +function MenuPreviewComposition() { + return ( + { + expectType(open); + eventDetails.cancel(); + eventDetails.preventUnmountOnClose(); + }} + > + + Options + + + + Group + + Item + + Profile + { + expectType(event); + expectType(checked); + eventDetails.cancel(); + }} + > + + Checkbox + + { + expectType(event); + expectType(value); + eventDetails.cancel(); + }} + > + + + One + + + + { + expectType(open); + eventDetails.cancel(); + }} + > + + More + + + Nested + + + + + + ); +} + +createTheme({ + components: { + MuiMenuPreview: { + defaultProps: { + modal: false, + }, + }, + MuiMenuPreviewSubmenuRoot: { + defaultProps: { + defaultOpen: false, + }, + }, + MuiMenuPreviewItem: { + defaultProps: { + dense: true, + }, + styleOverrides: { + root: {}, + highlighted: {}, + }, + variants: [ + { + props: { selected: true }, + style: {}, + }, + ], + }, + MuiMenuPreviewPopup: { + defaultProps: { + align: 'start', + }, + styleOverrides: { + root: {}, + paper: {}, + list: {}, + }, + variants: [ + { + props: { align: 'start' }, + style: {}, + }, + ], + }, + MuiMenuPreviewRadioItem: { + variants: [ + { + props: { value: 'small' }, + style: {}, + }, + ], + }, + MuiMenuPreviewLinkItem: { + variants: [ + { + props: { href: '/profile' }, + style: {}, + }, + ], + }, + }, +}); + +; + +; + +} +> + Options +; + + + Options +; + +, + }, + }} +/>; diff --git a/packages/mui-material/src/MenuPreview/MenuPreview.test.tsx b/packages/mui-material/src/MenuPreview/MenuPreview.test.tsx new file mode 100644 index 00000000000000..7b7155e31b7ec4 --- /dev/null +++ b/packages/mui-material/src/MenuPreview/MenuPreview.test.tsx @@ -0,0 +1,972 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { spy } from 'sinon'; +import { createRenderer, isJsdom, screen, waitFor } from '@mui/internal-test-utils'; +import Tooltip from '@mui/material/Tooltip'; +import MenuPreview, { + MenuPreviewCheckboxItem, + MenuPreviewCheckboxItemIndicator, + MenuPreviewGroup, + MenuPreviewGroupLabel, + MenuPreviewItem, + MenuPreviewLinkItem, + MenuPreviewPopup, + MenuPreviewRadioGroup, + MenuPreviewRadioItem, + MenuPreviewRadioItemIndicator, + MenuPreviewSeparator, + MenuPreviewSubmenuPopup, + MenuPreviewSubmenuRoot, + MenuPreviewSubmenuTrigger, + MenuPreviewTrigger, + menuPreviewCheckboxItemClasses, + menuPreviewItemClasses, + menuPreviewPopupClasses, + menuPreviewTriggerClasses, +} from '@mui/material/MenuPreview'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; + +describe('', () => { + const { render } = createRenderer(); + type User = ReturnType['user']; + + async function expectTooltipOnHover(user: User, element: Element, title: string) { + await user.hover(element); + + expect(await screen.findByRole('tooltip')).to.have.text(title); + + await user.unhover(element); + + await waitFor(() => { + expect(screen.queryByRole('tooltip')).to.equal(null); + }); + } + + it('opens from the trigger and keeps Menu.Popup as the semantic menu root', async () => { + const { user } = render( + + Options + + Profile + + , + ); + + const trigger = screen.getByRole('button', { name: 'Options' }); + expect(trigger).to.have.class(menuPreviewTriggerClasses.root); + + await user.click(trigger); + + const menu = await screen.findByRole('menu'); + expect(menu).to.have.class(menuPreviewPopupClasses.root); + expect(screen.getByTestId('paper')).to.have.class(menuPreviewPopupClasses.paper); + + const list = screen.getByTestId('paper').querySelector(`.${menuPreviewPopupClasses.list}`); + expect(list).not.to.equal(null); + expect(list!.tagName).to.equal('DIV'); + + expect(screen.getByRole('menuitem', { name: 'Profile' })).to.have.class( + menuPreviewItemClasses.root, + ); + }); + + it('does not render the trigger as a link when href is passed by a JS caller', async () => { + const { user } = render( + + Options + + Profile + + , + ); + + const trigger = screen.getByRole('button', { name: 'Options' }); + expect(trigger.tagName).to.equal('BUTTON'); + expect(trigger).not.to.have.attribute('href'); + + await user.click(trigger); + + expect(await screen.findByRole('menu')).not.to.equal(null); + }); + + it('supports component props, slotProps, classes, styleOverrides, and variants', async () => { + const theme = createTheme({ + components: { + MuiMenuPreviewTrigger: { + defaultProps: { + variant: 'outlined', + }, + }, + MuiMenuPreviewPopup: { + styleOverrides: { + paper: { + minWidth: 128, + }, + }, + variants: [ + { + props: { align: 'start' }, + style: { + '--MenuPreviewPopup-variant': '"applied"', + }, + }, + ], + }, + MuiMenuPreviewItem: { + variants: [ + { + props: { selected: true }, + style: { + fontWeight: 700, + }, + }, + { + props: { disabled: true }, + style: { + '--MenuPreviewItem-disabledVariant': '"applied"', + }, + }, + ], + }, + MuiMenuPreviewCheckboxItem: { + variants: [ + { + props: { checked: true }, + style: { + '--MenuPreviewCheckboxItem-checkedVariant': '"applied"', + }, + }, + ], + }, + MuiMenuPreviewRadioItem: { + variants: [ + { + props: { value: 'small' }, + style: { + '--MenuPreviewRadioItem-valueVariant': '"applied"', + }, + }, + ], + }, + MuiMenuPreviewLinkItem: { + variants: [ + { + props: { href: '/profile' }, + style: { + '--MenuPreviewLinkItem-hrefVariant': '"applied"', + }, + }, + ], + }, + }, + }); + + const { user } = render( + + + Options + + + Profile + + Disabled profile + Checked profile + + Small + + Link profile + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + expect(screen.getByRole('button', { name: 'Options' })).to.have.class('custom-trigger'); + expect(await screen.findByTestId('list')).to.have.class('custom-list'); + expect( + window + .getComputedStyle(screen.getByRole('menu')) + .getPropertyValue('--MenuPreviewPopup-variant'), + ).to.equal('"applied"'); + expect(await screen.findByRole('menuitem', { name: 'Profile' })).to.have.class('custom-item'); + expect(screen.getByRole('menuitem', { name: 'Profile' })).to.have.class( + menuPreviewItemClasses.selected, + ); + expect(screen.getByRole('menuitem', { name: 'Disabled profile' })).to.have.class( + menuPreviewItemClasses.disabled, + ); + expect( + window + .getComputedStyle(screen.getByRole('menuitem', { name: 'Disabled profile' })) + .getPropertyValue('--MenuPreviewItem-disabledVariant'), + ).to.equal('"applied"'); + expect(screen.getByRole('menuitemcheckbox', { name: 'Checked profile' })).to.have.class( + menuPreviewCheckboxItemClasses.checked, + ); + expect( + window + .getComputedStyle(screen.getByRole('menuitemcheckbox', { name: 'Checked profile' })) + .getPropertyValue('--MenuPreviewCheckboxItem-checkedVariant'), + ).to.equal('"applied"'); + expect( + window + .getComputedStyle(screen.getByRole('menuitemradio', { name: 'Small' })) + .getPropertyValue('--MenuPreviewRadioItem-valueVariant'), + ).to.equal('"applied"'); + expect( + window + .getComputedStyle(screen.getByRole('menuitem', { name: 'Link profile' })) + .getPropertyValue('--MenuPreviewLinkItem-hrefVariant'), + ).to.equal('"applied"'); + }); + + it('composes popup class names', async () => { + const { user } = render( + + Options + + Profile + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + const menu = await screen.findByRole('menu'); + expect(menu).to.have.class('popup-open'); + expect(menu).to.have.class('popup-side-bottom'); + expect(menu).to.have.class(menuPreviewPopupClasses.root); + }); + + it('does not pass ownerState to host popup slots', async () => { + const { user } = render( + + Options + + Profile + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + expect(await screen.findByTestId('popup')).not.to.have.attribute('ownerState'); + }); + + it('derives native button behavior from host root slots', async () => { + const error = vi.spyOn(console, 'error').mockImplementation(() => {}); + + try { + const { user } = render( + + Options + + + Native item + + + Native checkbox + + + + Native radio + + + + + Native submenu trigger + + + Nested + + + + , + ); + + const trigger = screen.getByRole('button', { name: 'Options' }); + expect(trigger.tagName).to.equal('DIV'); + + trigger.focus(); + await user.keyboard('[Enter]'); + + expect(await screen.findByRole('menuitem', { name: 'Native item' })).to.have.property( + 'tagName', + 'BUTTON', + ); + expect(screen.getByRole('menuitemcheckbox', { name: 'Native checkbox' })).to.have.property( + 'tagName', + 'BUTTON', + ); + expect(screen.getByRole('menuitemradio', { name: 'Native radio' })).to.have.property( + 'tagName', + 'BUTTON', + ); + expect(screen.getByRole('menuitem', { name: 'Native submenu trigger' })).to.have.property( + 'tagName', + 'BUTTON', + ); + expect( + error.mock.calls.some(([message]) => String(message).includes('nativeButton')), + ).to.equal(false); + } finally { + error.mockRestore(); + } + }); + + it('allows nativeButton to override root slot inference for custom slots', async () => { + const error = vi.spyOn(console, 'error').mockImplementation(() => {}); + const CustomDivRoot = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<'div'> & { ownerState?: unknown } + >(function CustomDivRoot({ ownerState: _ownerState, ...props }, ref) { + return
; + }); + const CustomButtonRoot = React.forwardRef< + HTMLButtonElement, + React.ComponentPropsWithoutRef<'button'> & { ownerState?: unknown } + >(function CustomButtonRoot({ ownerState: _ownerState, ...props }, ref) { + return + + Options + + Profile + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + await user.click(await screen.findByRole('menuitem', { name: 'Profile' })); + + await waitFor(() => { + expect(document.activeElement).to.equal(finalFocusRef.current); + }); + }); + + it('returns focus to the trigger on Escape and closes on outside press', async () => { + const { user } = render( + + + + Options + + Profile + + + , + ); + + const trigger = screen.getByRole('button', { name: 'Options' }); + await user.click(trigger); + await screen.findByRole('menu'); + + await user.keyboard('[Escape]'); + await waitFor(() => { + expect(screen.queryByRole('menu')).to.equal(null); + }); + expect(document.activeElement).to.equal(trigger); + + await user.click(trigger); + await screen.findByRole('menu'); + await user.click(screen.getByRole('button', { name: 'Outside' })); + + await waitFor(() => { + expect(screen.queryByRole('menu')).to.equal(null); + }); + }); + + it('supports touch trigger interactions', async () => { + const { user } = render( + + Options + + Profile + + , + ); + + await user.pointer({ + keys: '[TouchA]', + target: screen.getByRole('button', { name: 'Options' }), + }); + + expect(await screen.findByRole('menu')).not.to.equal(null); + }); + + it('supports modal backdrop behavior', async () => { + const { user } = render( + + + Modal menu + + Profile + + + + Non-modal menu + + Settings + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Modal menu' })); + await screen.findByRole('menu'); + expect(screen.getByTestId('modal-positioner').previousElementSibling).to.have.attribute( + 'role', + 'presentation', + ); + + await user.keyboard('[Escape]'); + await waitFor(() => { + expect(screen.queryByRole('menu')).to.equal(null); + }); + + await user.click(screen.getByRole('button', { name: 'Non-modal menu' })); + await screen.findByRole('menu'); + expect(screen.getByTestId('non-modal-positioner').previousElementSibling).to.equal(null); + }); + + it('opens in an RTL tree', async () => { + const { user } = render( +
+ + Options + + Profile + + +
, + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + expect(await screen.findByRole('menu')).not.to.equal(null); + }); + + it.skipIf(isJsdom())('applies Base UI positioning attributes in the browser', async () => { + const { user } = render( +
+ + Options + + Profile + + +
, + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + const positioner = await screen.findByTestId('positioner'); + expect(positioner).to.have.attribute('data-side', 'bottom'); + expect(positioner).to.have.attribute('data-align', 'start'); + expect(positioner.style.transform).not.to.equal(''); + }); + + it('supports checkbox and radio item state', async () => { + const handleCheckboxChange = spy((event: Event, checked: boolean, eventDetails: any) => { + expect(event).to.be.instanceOf(Event); + expect(checked).to.equal(true); + expect(eventDetails.reason).to.equal('item-press'); + }); + const handleRadioChange = spy((event: Event, value: string, eventDetails: any) => { + expect(event).to.be.instanceOf(Event); + expect(value).to.equal('large'); + expect(eventDetails.reason).to.equal('item-press'); + }); + + const { user } = render( + + Options + + + + Show hidden files + + + + + Small + + + + Large + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + const checkbox = await screen.findByRole('menuitemcheckbox', { name: /show hidden files/i }); + expect(checkbox).to.have.attribute('aria-checked', 'false'); + + await user.click(checkbox); + + expect(checkbox).to.have.attribute('aria-checked', 'true'); + expect(checkbox).to.have.class(menuPreviewCheckboxItemClasses.checked); + expect(handleCheckboxChange.callCount).to.equal(1); + + expect(screen.getByRole('menuitemradio', { name: /small/i })).to.have.attribute( + 'aria-checked', + 'true', + ); + expect(screen.getByRole('menuitemradio', { name: /large/i })).to.have.attribute( + 'aria-checked', + 'false', + ); + + await user.click(screen.getByRole('menuitemradio', { name: /large/i })); + + expect(screen.getByRole('menuitemradio', { name: /large/i })).to.have.attribute( + 'aria-checked', + 'true', + ); + expect(handleRadioChange.callCount).to.equal(1); + }); + + it('keeps mounted unchecked indicator marks hidden', () => { + render( + + Options + + + + Show hidden files + + + + + Small + + + + Large + + + + , + ); + + const checkboxIndicator = screen.getByTestId('checkbox-indicator'); + const checkboxIcon = checkboxIndicator.querySelector('[data-mui-menu-preview-indicator-icon]'); + const checkboxMark = checkboxIndicator.querySelector('[data-mui-menu-preview-indicator-mark]'); + expect(checkboxIndicator).to.have.attribute('data-unchecked', ''); + expect(window.getComputedStyle(checkboxIndicator).visibility).to.equal('visible'); + expect(checkboxIcon).not.to.equal(null); + expect(window.getComputedStyle(checkboxIcon!).visibility).to.equal('visible'); + expect(checkboxMark).not.to.equal(null); + expect(window.getComputedStyle(checkboxMark!).visibility).to.equal('hidden'); + + const checkedRadioIndicator = screen.getByTestId('checked-radio-indicator'); + const checkedRadioIcon = checkedRadioIndicator.querySelector( + '[data-mui-menu-preview-indicator-icon]', + ); + const checkedRadioMark = checkedRadioIndicator.querySelector( + '[data-mui-menu-preview-indicator-mark]', + ); + expect(checkedRadioIndicator).to.have.attribute('data-checked', ''); + expect(checkedRadioIcon).not.to.equal(null); + expect(window.getComputedStyle(checkedRadioIcon!).visibility).to.equal('visible'); + expect(checkedRadioMark).not.to.equal(null); + expect(window.getComputedStyle(checkedRadioMark!).visibility).to.equal('visible'); + + const uncheckedRadioIndicator = screen.getByTestId('unchecked-radio-indicator'); + const uncheckedRadioIcon = uncheckedRadioIndicator.querySelector( + '[data-mui-menu-preview-indicator-icon]', + ); + const uncheckedRadioMark = uncheckedRadioIndicator.querySelector( + '[data-mui-menu-preview-indicator-mark]', + ); + expect(uncheckedRadioIndicator).to.have.attribute('data-unchecked', ''); + expect(window.getComputedStyle(uncheckedRadioIndicator).visibility).to.equal('visible'); + expect(uncheckedRadioIcon).not.to.equal(null); + expect(window.getComputedStyle(uncheckedRadioIcon!).visibility).to.equal('visible'); + expect(uncheckedRadioMark).not.to.equal(null); + expect(window.getComputedStyle(uncheckedRadioMark!).visibility).to.equal('hidden'); + }); + + it('supports groups, labels, separators, link items, and submenus', async () => { + const { user } = render( + + Options + + + Account + Profile + + + + More + + Archive + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + expect(await screen.findByText('Account')).not.to.equal(null); + expect(screen.getByRole('separator')).not.to.equal(null); + expect(screen.getByRole('menuitem', { name: 'Profile' })).to.have.attribute('href', '/profile'); + expect(screen.getByRole('menuitem', { name: 'More' })).to.not.equal(null); + expect(screen.getByRole('menuitem', { name: 'Archive' })).to.not.equal(null); + }); + + it('supports Material UI Tooltip on enabled item flavors', async () => { + const { user } = render( + + Options + + + New document + + + + + Comments + + + + + + + Fit + + + + + , + ); + + await expectTooltipOnHover( + user, + screen.getByRole('menuitem', { name: 'New document' }), + 'Create a blank document', + ); + await expectTooltipOnHover( + user, + screen.getByRole('menuitemcheckbox', { name: 'Comments' }), + 'Toggle comments', + ); + await expectTooltipOnHover( + user, + screen.getByRole('menuitemradio', { name: 'Fit' }), + 'Fit to viewport', + ); + }); + + it('can close a controlled Material UI Tooltip when a submenu trigger opens', async () => { + interface TooltipChildProps { + onClickCapture?: React.MouseEventHandler; + } + + function ClickClosingTooltip(props: { + title: string; + children: React.ReactElement; + }) { + const { title, children } = props; + const [open, setOpen] = React.useState(false); + + const child = React.cloneElement(children, { + onClickCapture: (event: React.MouseEvent) => { + setOpen(false); + children.props.onClickCapture?.(event); + }, + }); + + return ( + setOpen(true)} + onClose={() => setOpen(false)} + > + {child} + + ); + } + + const { user } = render( + + Options + + + + + View options + + + + Comments + + + + , + ); + + const submenuTrigger = screen.getByRole('menuitem', { name: 'View options' }); + + await user.hover(submenuTrigger); + expect(await screen.findByRole('tooltip')).to.have.text('Open view settings'); + + await user.click(submenuTrigger); + + expect(await screen.findByRole('menuitem', { name: 'Comments' })).not.to.equal(null); + await waitFor(() => { + expect(screen.queryByRole('tooltip')).to.equal(null); + }); + }); + + it('supports Material UI Tooltip on disabled items through a non-disabled wrapper', async () => { + const { user } = render( + + Options + + + + Import from Drive + + + + , + ); + + expect(screen.getByRole('menuitem', { name: 'Import from Drive' })).to.have.attribute( + 'aria-disabled', + 'true', + ); + await expectTooltipOnHover( + user, + screen.getByTestId('disabled-item-tooltip-target'), + 'Unavailable while offline', + ); + }); + + it('does not warn when a submenu trigger is disabled', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + try { + const { user } = render( + + Options + + + Add-ons unavailable + + Marketplace + + + + , + ); + + await user.click(screen.getByRole('button', { name: 'Options' })); + + const submenuTrigger = await screen.findByRole('menuitem', { + name: 'Add-ons unavailable', + }); + expect(submenuTrigger).to.have.attribute('aria-disabled', 'true'); + expect( + warn.mock.calls.some(([message]) => + String(message).includes('A disabled element was detected on '), + ), + ).to.equal(false); + } finally { + warn.mockRestore(); + } + }); +}); diff --git a/packages/mui-material/src/MenuPreview/MenuPreview.tsx b/packages/mui-material/src/MenuPreview/MenuPreview.tsx new file mode 100644 index 00000000000000..6ea6ec15b4d7fd --- /dev/null +++ b/packages/mui-material/src/MenuPreview/MenuPreview.tsx @@ -0,0 +1,142 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { useDefaultProps } from '../DefaultPropsProvider'; + +export interface MenuPreviewProps { + /** + * The content of the menu. + */ + children?: React.ReactNode; + /** + * Whether the menu is initially open. + * + * To render a controlled menu, use the `open` prop instead. + * @default false + */ + defaultOpen?: boolean | undefined; + /** + * Whether the menu is currently open. + */ + open?: boolean | undefined; + /** + * Event handler called when the menu is opened or closed. + */ + onOpenChange?: BaseMenu.Root.Props['onOpenChange']; + /** + * Event handler called after any animations complete when the menu is opened or closed. + */ + onOpenChangeComplete?: BaseMenu.Root.Props['onOpenChangeComplete']; + /** + * Determines if the menu enters a modal state when open. + * @default true + */ + modal?: boolean | undefined; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Whether to loop keyboard focus back to the first item. + * @default true + */ + loopFocus?: boolean | undefined; + /** + * Whether moving the pointer over items should highlight them. + * @default true + */ + highlightItemOnHover?: boolean | undefined; + /** + * The visual orientation of the menu. + * @default 'vertical' + */ + orientation?: 'horizontal' | 'vertical' | undefined; + /** + * When in a submenu, determines whether pressing the Escape key closes the entire menu. + * @default false + */ + closeParentOnEsc?: boolean | undefined; +} + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreview API](https://mui.com/material-ui/api/menu-preview/) + */ +function MenuPreview(props: MenuPreviewProps): React.JSX.Element { + const themedProps = useDefaultProps({ + props, + name: 'MuiMenuPreview', + }); + + return ; +} + +MenuPreview.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the menu. + */ + children: PropTypes.node, + /** + * When in a submenu, determines whether pressing the Escape key closes the entire menu. + * @default false + */ + closeParentOnEsc: PropTypes.bool, + /** + * Whether the menu is initially open. + * + * To render a controlled menu, use the `open` prop instead. + * @default false + */ + defaultOpen: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * Whether moving the pointer over items should highlight them. + * @default true + */ + highlightItemOnHover: PropTypes.bool, + /** + * Whether to loop keyboard focus back to the first item. + * @default true + */ + loopFocus: PropTypes.bool, + /** + * Determines if the menu enters a modal state when open. + * @default true + */ + modal: PropTypes.bool, + /** + * Event handler called when the menu is opened or closed. + */ + onOpenChange: PropTypes.func, + /** + * Event handler called after any animations complete when the menu is opened or closed. + */ + onOpenChangeComplete: PropTypes.func, + /** + * Whether the menu is currently open. + */ + open: PropTypes.bool, + /** + * The visual orientation of the menu. + * @default 'vertical' + */ + orientation: PropTypes.oneOf(['horizontal', 'vertical']), +} as any; + +export default MenuPreview; diff --git a/packages/mui-material/src/MenuPreview/apiDocs.d.ts b/packages/mui-material/src/MenuPreview/apiDocs.d.ts new file mode 100644 index 00000000000000..6c939d59992308 --- /dev/null +++ b/packages/mui-material/src/MenuPreview/apiDocs.d.ts @@ -0,0 +1,49 @@ +export { default as MenuPreview } from './MenuPreview'; +export * from './MenuPreview'; + +export { default as MenuPreviewTrigger } from '../MenuPreviewTrigger'; +export * from '../MenuPreviewTrigger'; + +export { default as MenuPreviewPopup } from '../MenuPreviewPopup'; +export * from '../MenuPreviewPopup'; + +export { default as MenuPreviewSubmenuPopup } from '../MenuPreviewSubmenuPopup'; +export * from '../MenuPreviewSubmenuPopup'; + +export { default as MenuPreviewItem } from '../MenuPreviewItem'; +export * from '../MenuPreviewItem'; + +export { default as MenuPreviewLinkItem } from '../MenuPreviewLinkItem'; +export * from '../MenuPreviewLinkItem'; + +export { default as MenuPreviewCheckboxItem } from '../MenuPreviewCheckboxItem'; +export * from '../MenuPreviewCheckboxItem'; + +export { default as MenuPreviewCheckboxItemIndicator } from '../MenuPreviewCheckboxItemIndicator'; +export * from '../MenuPreviewCheckboxItemIndicator'; + +export { default as MenuPreviewRadioGroup } from '../MenuPreviewRadioGroup'; +export * from '../MenuPreviewRadioGroup'; + +export { default as MenuPreviewRadioItem } from '../MenuPreviewRadioItem'; +export * from '../MenuPreviewRadioItem'; + +export { default as MenuPreviewRadioItemIndicator } from '../MenuPreviewRadioItemIndicator'; +export * from '../MenuPreviewRadioItemIndicator'; + +export { default as MenuPreviewGroup } from '../MenuPreviewGroup'; +export * from '../MenuPreviewGroup'; + +export { default as MenuPreviewGroupLabel } from '../MenuPreviewGroupLabel'; +export * from '../MenuPreviewGroupLabel'; + +export { default as MenuPreviewSeparator } from '../MenuPreviewSeparator'; +export * from '../MenuPreviewSeparator'; + +export { default as MenuPreviewSubmenuRoot } from '../MenuPreviewSubmenuRoot'; +export * from '../MenuPreviewSubmenuRoot'; + +export { default as MenuPreviewSubmenuTrigger } from '../MenuPreviewSubmenuTrigger'; +export * from '../MenuPreviewSubmenuTrigger'; + +export * from './menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreview/index.d.ts b/packages/mui-material/src/MenuPreview/index.d.ts new file mode 100644 index 00000000000000..43bb7637dc085b --- /dev/null +++ b/packages/mui-material/src/MenuPreview/index.d.ts @@ -0,0 +1,66 @@ +export { default } from './MenuPreview'; +export { default as MenuPreview } from './MenuPreview'; +export { default as Root } from './MenuPreview'; +export * from './MenuPreview'; + +export { default as Trigger } from '../MenuPreviewTrigger'; +export { default as MenuPreviewTrigger } from '../MenuPreviewTrigger'; +export * from '../MenuPreviewTrigger'; + +export { default as Popup } from '../MenuPreviewPopup'; +export { default as MenuPreviewPopup } from '../MenuPreviewPopup'; +export * from '../MenuPreviewPopup'; + +export { default as SubmenuPopup } from '../MenuPreviewSubmenuPopup'; +export { default as MenuPreviewSubmenuPopup } from '../MenuPreviewSubmenuPopup'; +export * from '../MenuPreviewSubmenuPopup'; + +export { default as Item } from '../MenuPreviewItem'; +export { default as MenuPreviewItem } from '../MenuPreviewItem'; +export * from '../MenuPreviewItem'; + +export { default as LinkItem } from '../MenuPreviewLinkItem'; +export { default as MenuPreviewLinkItem } from '../MenuPreviewLinkItem'; +export * from '../MenuPreviewLinkItem'; + +export { default as CheckboxItem } from '../MenuPreviewCheckboxItem'; +export { default as MenuPreviewCheckboxItem } from '../MenuPreviewCheckboxItem'; +export * from '../MenuPreviewCheckboxItem'; + +export { default as CheckboxItemIndicator } from '../MenuPreviewCheckboxItemIndicator'; +export { default as MenuPreviewCheckboxItemIndicator } from '../MenuPreviewCheckboxItemIndicator'; +export * from '../MenuPreviewCheckboxItemIndicator'; + +export { default as RadioGroup } from '../MenuPreviewRadioGroup'; +export { default as MenuPreviewRadioGroup } from '../MenuPreviewRadioGroup'; +export * from '../MenuPreviewRadioGroup'; + +export { default as RadioItem } from '../MenuPreviewRadioItem'; +export { default as MenuPreviewRadioItem } from '../MenuPreviewRadioItem'; +export * from '../MenuPreviewRadioItem'; + +export { default as RadioItemIndicator } from '../MenuPreviewRadioItemIndicator'; +export { default as MenuPreviewRadioItemIndicator } from '../MenuPreviewRadioItemIndicator'; +export * from '../MenuPreviewRadioItemIndicator'; + +export { default as Group } from '../MenuPreviewGroup'; +export { default as MenuPreviewGroup } from '../MenuPreviewGroup'; +export * from '../MenuPreviewGroup'; + +export { default as GroupLabel } from '../MenuPreviewGroupLabel'; +export { default as MenuPreviewGroupLabel } from '../MenuPreviewGroupLabel'; +export * from '../MenuPreviewGroupLabel'; + +export { default as Separator } from '../MenuPreviewSeparator'; +export { default as MenuPreviewSeparator } from '../MenuPreviewSeparator'; +export * from '../MenuPreviewSeparator'; + +export { default as SubmenuRoot } from '../MenuPreviewSubmenuRoot'; +export { default as MenuPreviewSubmenuRoot } from '../MenuPreviewSubmenuRoot'; +export * from '../MenuPreviewSubmenuRoot'; + +export { default as SubmenuTrigger } from '../MenuPreviewSubmenuTrigger'; +export { default as MenuPreviewSubmenuTrigger } from '../MenuPreviewSubmenuTrigger'; +export * from '../MenuPreviewSubmenuTrigger'; + +export * from './menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreview/index.js b/packages/mui-material/src/MenuPreview/index.js new file mode 100644 index 00000000000000..43bb7637dc085b --- /dev/null +++ b/packages/mui-material/src/MenuPreview/index.js @@ -0,0 +1,66 @@ +export { default } from './MenuPreview'; +export { default as MenuPreview } from './MenuPreview'; +export { default as Root } from './MenuPreview'; +export * from './MenuPreview'; + +export { default as Trigger } from '../MenuPreviewTrigger'; +export { default as MenuPreviewTrigger } from '../MenuPreviewTrigger'; +export * from '../MenuPreviewTrigger'; + +export { default as Popup } from '../MenuPreviewPopup'; +export { default as MenuPreviewPopup } from '../MenuPreviewPopup'; +export * from '../MenuPreviewPopup'; + +export { default as SubmenuPopup } from '../MenuPreviewSubmenuPopup'; +export { default as MenuPreviewSubmenuPopup } from '../MenuPreviewSubmenuPopup'; +export * from '../MenuPreviewSubmenuPopup'; + +export { default as Item } from '../MenuPreviewItem'; +export { default as MenuPreviewItem } from '../MenuPreviewItem'; +export * from '../MenuPreviewItem'; + +export { default as LinkItem } from '../MenuPreviewLinkItem'; +export { default as MenuPreviewLinkItem } from '../MenuPreviewLinkItem'; +export * from '../MenuPreviewLinkItem'; + +export { default as CheckboxItem } from '../MenuPreviewCheckboxItem'; +export { default as MenuPreviewCheckboxItem } from '../MenuPreviewCheckboxItem'; +export * from '../MenuPreviewCheckboxItem'; + +export { default as CheckboxItemIndicator } from '../MenuPreviewCheckboxItemIndicator'; +export { default as MenuPreviewCheckboxItemIndicator } from '../MenuPreviewCheckboxItemIndicator'; +export * from '../MenuPreviewCheckboxItemIndicator'; + +export { default as RadioGroup } from '../MenuPreviewRadioGroup'; +export { default as MenuPreviewRadioGroup } from '../MenuPreviewRadioGroup'; +export * from '../MenuPreviewRadioGroup'; + +export { default as RadioItem } from '../MenuPreviewRadioItem'; +export { default as MenuPreviewRadioItem } from '../MenuPreviewRadioItem'; +export * from '../MenuPreviewRadioItem'; + +export { default as RadioItemIndicator } from '../MenuPreviewRadioItemIndicator'; +export { default as MenuPreviewRadioItemIndicator } from '../MenuPreviewRadioItemIndicator'; +export * from '../MenuPreviewRadioItemIndicator'; + +export { default as Group } from '../MenuPreviewGroup'; +export { default as MenuPreviewGroup } from '../MenuPreviewGroup'; +export * from '../MenuPreviewGroup'; + +export { default as GroupLabel } from '../MenuPreviewGroupLabel'; +export { default as MenuPreviewGroupLabel } from '../MenuPreviewGroupLabel'; +export * from '../MenuPreviewGroupLabel'; + +export { default as Separator } from '../MenuPreviewSeparator'; +export { default as MenuPreviewSeparator } from '../MenuPreviewSeparator'; +export * from '../MenuPreviewSeparator'; + +export { default as SubmenuRoot } from '../MenuPreviewSubmenuRoot'; +export { default as MenuPreviewSubmenuRoot } from '../MenuPreviewSubmenuRoot'; +export * from '../MenuPreviewSubmenuRoot'; + +export { default as SubmenuTrigger } from '../MenuPreviewSubmenuTrigger'; +export { default as MenuPreviewSubmenuTrigger } from '../MenuPreviewSubmenuTrigger'; +export * from '../MenuPreviewSubmenuTrigger'; + +export * from './menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreview/menuPreviewClasses.ts b/packages/mui-material/src/MenuPreview/menuPreviewClasses.ts new file mode 100644 index 00000000000000..5c32722a12a4d0 --- /dev/null +++ b/packages/mui-material/src/MenuPreview/menuPreviewClasses.ts @@ -0,0 +1,280 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; + +export interface MenuPreviewTriggerClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if `disabled={true}`. */ + disabled: string; + /** State class applied to the root element if the menu is open. */ + open: string; +} + +export type MenuPreviewTriggerClassKey = keyof MenuPreviewTriggerClasses; + +export function getMenuPreviewTriggerUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewTrigger', slot); +} + +export const menuPreviewTriggerClasses: MenuPreviewTriggerClasses = generateUtilityClasses( + 'MuiMenuPreviewTrigger', + ['root', 'disabled', 'open'], +); + +export interface MenuPreviewPopupClasses { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the Material Paper element. */ + paper: string; + /** Styles applied to the Material List element. */ + list: string; +} + +export type MenuPreviewPopupClassKey = keyof MenuPreviewPopupClasses; + +export function getMenuPreviewPopupUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewPopup', slot); +} + +export const menuPreviewPopupClasses: MenuPreviewPopupClasses = generateUtilityClasses( + 'MuiMenuPreviewPopup', + ['root', 'paper', 'list'], +); + +export interface MenuPreviewSubmenuPopupClasses { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the Material Paper element. */ + paper: string; + /** Styles applied to the Material List element. */ + list: string; +} + +export type MenuPreviewSubmenuPopupClassKey = keyof MenuPreviewSubmenuPopupClasses; + +export function getMenuPreviewSubmenuPopupUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewSubmenuPopup', slot); +} + +export const menuPreviewSubmenuPopupClasses: MenuPreviewSubmenuPopupClasses = + generateUtilityClasses('MuiMenuPreviewSubmenuPopup', ['root', 'paper', 'list']); + +export interface MenuPreviewItemClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if highlighted. */ + highlighted: string; + /** State class applied to the root element if `disabled={true}`. */ + disabled: string; + /** Styles applied to the root element if `dense={true}`. */ + dense: string; + /** Styles applied to the root element if `divider={true}`. */ + divider: string; + /** Styles applied to the root element unless `disableGutters={true}`. */ + gutters: string; + /** State class applied to the root element if `selected={true}`. */ + selected: string; +} + +export type MenuPreviewItemClassKey = keyof MenuPreviewItemClasses; + +export function getMenuPreviewItemUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewItem', slot); +} + +export const menuPreviewItemClasses: MenuPreviewItemClasses = generateUtilityClasses( + 'MuiMenuPreviewItem', + ['root', 'highlighted', 'disabled', 'dense', 'divider', 'gutters', 'selected'], +); + +export interface MenuPreviewLinkItemClasses extends MenuPreviewItemClasses {} + +export type MenuPreviewLinkItemClassKey = keyof MenuPreviewLinkItemClasses; + +export function getMenuPreviewLinkItemUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewLinkItem', slot); +} + +export const menuPreviewLinkItemClasses: MenuPreviewLinkItemClasses = generateUtilityClasses( + 'MuiMenuPreviewLinkItem', + ['root', 'highlighted', 'disabled', 'dense', 'divider', 'gutters', 'selected'], +); + +export interface MenuPreviewCheckboxItemClasses extends MenuPreviewItemClasses { + /** State class applied to the root element if `checked={true}`. */ + checked: string; +} + +export type MenuPreviewCheckboxItemClassKey = keyof MenuPreviewCheckboxItemClasses; + +export function getMenuPreviewCheckboxItemUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewCheckboxItem', slot); +} + +export const menuPreviewCheckboxItemClasses: MenuPreviewCheckboxItemClasses = + generateUtilityClasses('MuiMenuPreviewCheckboxItem', [ + 'root', + 'highlighted', + 'disabled', + 'dense', + 'divider', + 'gutters', + 'selected', + 'checked', + ]); + +export interface MenuPreviewCheckboxItemIndicatorClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if `checked={true}`. */ + checked: string; + /** State class applied to the root element if `disabled={true}`. */ + disabled: string; + /** State class applied to the root element if highlighted. */ + highlighted: string; +} + +export type MenuPreviewCheckboxItemIndicatorClassKey = + keyof MenuPreviewCheckboxItemIndicatorClasses; + +export function getMenuPreviewCheckboxItemIndicatorUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewCheckboxItemIndicator', slot); +} + +export const menuPreviewCheckboxItemIndicatorClasses: MenuPreviewCheckboxItemIndicatorClasses = + generateUtilityClasses('MuiMenuPreviewCheckboxItemIndicator', [ + 'root', + 'checked', + 'disabled', + 'highlighted', + ]); + +export interface MenuPreviewRadioGroupClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if `disabled={true}`. */ + disabled: string; +} + +export type MenuPreviewRadioGroupClassKey = keyof MenuPreviewRadioGroupClasses; + +export function getMenuPreviewRadioGroupUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewRadioGroup', slot); +} + +export const menuPreviewRadioGroupClasses: MenuPreviewRadioGroupClasses = generateUtilityClasses( + 'MuiMenuPreviewRadioGroup', + ['root', 'disabled'], +); + +export interface MenuPreviewRadioItemClasses extends MenuPreviewItemClasses { + /** State class applied to the root element if `checked={true}`. */ + checked: string; +} + +export type MenuPreviewRadioItemClassKey = keyof MenuPreviewRadioItemClasses; + +export function getMenuPreviewRadioItemUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewRadioItem', slot); +} + +export const menuPreviewRadioItemClasses: MenuPreviewRadioItemClasses = generateUtilityClasses( + 'MuiMenuPreviewRadioItem', + ['root', 'highlighted', 'disabled', 'dense', 'divider', 'gutters', 'selected', 'checked'], +); + +export interface MenuPreviewRadioItemIndicatorClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if `checked={true}`. */ + checked: string; + /** State class applied to the root element if `disabled={true}`. */ + disabled: string; + /** State class applied to the root element if highlighted. */ + highlighted: string; +} + +export type MenuPreviewRadioItemIndicatorClassKey = keyof MenuPreviewRadioItemIndicatorClasses; + +export function getMenuPreviewRadioItemIndicatorUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewRadioItemIndicator', slot); +} + +export const menuPreviewRadioItemIndicatorClasses: MenuPreviewRadioItemIndicatorClasses = + generateUtilityClasses('MuiMenuPreviewRadioItemIndicator', [ + 'root', + 'checked', + 'disabled', + 'highlighted', + ]); + +export interface MenuPreviewGroupClasses { + /** Styles applied to the root element. */ + root: string; +} + +export type MenuPreviewGroupClassKey = keyof MenuPreviewGroupClasses; + +export function getMenuPreviewGroupUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewGroup', slot); +} + +export const menuPreviewGroupClasses: MenuPreviewGroupClasses = generateUtilityClasses( + 'MuiMenuPreviewGroup', + ['root'], +); + +export interface MenuPreviewGroupLabelClasses { + /** Styles applied to the root element. */ + root: string; +} + +export type MenuPreviewGroupLabelClassKey = keyof MenuPreviewGroupLabelClasses; + +export function getMenuPreviewGroupLabelUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewGroupLabel', slot); +} + +export const menuPreviewGroupLabelClasses: MenuPreviewGroupLabelClasses = generateUtilityClasses( + 'MuiMenuPreviewGroupLabel', + ['root'], +); + +export interface MenuPreviewSeparatorClasses { + /** Styles applied to the root element. */ + root: string; +} + +export type MenuPreviewSeparatorClassKey = keyof MenuPreviewSeparatorClasses; + +export function getMenuPreviewSeparatorUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewSeparator', slot); +} + +export const menuPreviewSeparatorClasses: MenuPreviewSeparatorClasses = generateUtilityClasses( + 'MuiMenuPreviewSeparator', + ['root'], +); + +export interface MenuPreviewSubmenuTriggerClasses extends MenuPreviewItemClasses { + /** State class applied to the root element if the submenu is open. */ + open: string; +} + +export type MenuPreviewSubmenuTriggerClassKey = keyof MenuPreviewSubmenuTriggerClasses; + +export function getMenuPreviewSubmenuTriggerUtilityClass(slot: string): string { + return generateUtilityClass('MuiMenuPreviewSubmenuTrigger', slot); +} + +export const menuPreviewSubmenuTriggerClasses: MenuPreviewSubmenuTriggerClasses = + generateUtilityClasses('MuiMenuPreviewSubmenuTrigger', [ + 'root', + 'highlighted', + 'disabled', + 'dense', + 'divider', + 'gutters', + 'selected', + 'open', + ]); diff --git a/packages/mui-material/src/MenuPreview/menuPreviewItemShared.tsx b/packages/mui-material/src/MenuPreview/menuPreviewItemShared.tsx new file mode 100644 index 00000000000000..904641b370bfbb --- /dev/null +++ b/packages/mui-material/src/MenuPreview/menuPreviewItemShared.tsx @@ -0,0 +1,244 @@ +'use client'; +import * as React from 'react'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { CSSInterpolation, SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + StateClassName, + mergeStateClassName, +} from './menuPreviewUtils'; + +export interface MenuPreviewItemOwnerState { + checked?: boolean | undefined; + dense: boolean; + disabled: boolean; + divider: boolean; + disableGutters: boolean; + selected: boolean; +} + +export interface MenuPreviewItemVisualProps< + Classes, + Slots = MenuPreviewRootSlots, + SlotProps = MenuPreviewRootSlotProps, +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * The components used for each slot inside. + */ + slots?: Slots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: SlotProps | undefined; + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense?: boolean | undefined; + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters?: boolean | undefined; + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider?: boolean | undefined; + /** + * If `true`, the component is selected. + * @default false + */ + selected?: boolean | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +export interface MenuPreviewItemBaseProps { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default true + */ + closeOnClick?: boolean | undefined; +} + +export interface MenuPreviewLinkItemBaseProps { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * The URL that the link item points to. + */ + href?: string | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick?: boolean | undefined; +} + +export interface MenuPreviewSubmenuTriggerBaseProps { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * How long to wait before the submenu may be opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 100 + */ + delay?: number | undefined; + /** + * How long to wait before closing the submenu that was opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 0 + */ + closeDelay?: number | undefined; + /** + * Whether the submenu should also open when the trigger is hovered. + */ + openOnHover?: boolean | undefined; +} + +export interface MenuPreviewBaseItemState { + disabled?: boolean; + highlighted?: boolean; +} + +export function menuPreviewItemOverridesResolver( + props: { ownerState: MenuPreviewItemOwnerState }, + styles: Record, +) { + const { ownerState } = props; + + return [ + styles.root, + ownerState.dense && styles.dense, + ownerState.divider && styles.divider, + !ownerState.disableGutters && styles.gutters, + ] as CSSInterpolation; +} + +export function getMenuPreviewItemOwnerState( + props: MenuPreviewItemVisualProps & { + checked?: boolean | undefined; + disabled?: boolean | undefined; + }, +): MenuPreviewItemOwnerState { + return { + checked: props.checked, + dense: props.dense ?? false, + disabled: props.disabled ?? false, + divider: props.divider ?? false, + disableGutters: props.disableGutters ?? false, + selected: props.selected ?? false, + }; +} + +export function useMenuPreviewItemUtilityClasses( + ownerState: MenuPreviewItemOwnerState & { + classes?: Partial; + checked?: boolean; + open?: boolean; + }, + getUtilityClass: (slot: string) => string, +) { + const { dense, disabled, divider, disableGutters, selected, checked, open, classes } = ownerState; + const slots = { + root: [ + 'root', + dense && 'dense', + disabled && 'disabled', + !disableGutters && 'gutters', + divider && 'divider', + selected && 'selected', + checked && 'checked', + open && 'open', + ], + highlighted: ['highlighted'], + disabled: ['disabled'], + checked: ['checked'], + open: ['open'], + }; + + return { + ...classes, + ...composeClasses(slots, getUtilityClass, classes as Record | undefined), + } as Classes; +} + +export function getMenuPreviewItemClassName( + classes: Partial>, + ownerState: MenuPreviewItemOwnerState, + state: State, +) { + return clsx( + classes.root, + state.highlighted && classes.highlighted, + state.disabled && !ownerState.disabled && classes.disabled, + ); +} + +export function mergeMenuPreviewItemClassName( + className: StateClassName, + classes: Partial>, + ownerState: MenuPreviewItemOwnerState, +) { + return mergeStateClassName(className, (state) => + getMenuPreviewItemClassName(classes, ownerState, state), + ); +} diff --git a/packages/mui-material/src/MenuPreview/menuPreviewPopupShared.tsx b/packages/mui-material/src/MenuPreview/menuPreviewPopupShared.tsx new file mode 100644 index 00000000000000..2e5bc2a049171e --- /dev/null +++ b/packages/mui-material/src/MenuPreview/menuPreviewPopupShared.tsx @@ -0,0 +1,368 @@ +'use client'; +import * as React from 'react'; +import clsx from 'clsx'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import appendOwnerState from '@mui/utils/appendOwnerState'; +import isHostComponent from '@mui/utils/isHostComponent'; +import { SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { PaperProps } from '../Paper'; +import { ListProps } from '../List'; +import { resolveSlotProps, SlotProps } from './menuPreviewUtils'; + +type ExternalSlotProps = Omit, 'className' | 'render' | 'style'> & { + className?: string | undefined; + render?: never; + style?: React.CSSProperties | undefined; +} & Record; + +function mergeSx(...sx: Array | undefined>) { + return sx.flatMap((style) => (Array.isArray(style) ? style : [style])).filter(Boolean); +} + +function setDefinedProp(props: Record, key: string, value: unknown) { + if (value !== undefined) { + props[key] = value; + } +} + +function omitProps | undefined>( + props: Props, + keys: readonly string[], +): Props { + if (props == null) { + return props; + } + + const result = { ...props }; + keys.forEach((key) => { + delete result[key]; + }); + + return result as Props; +} + +function getSlotProps>( + Slot: ElementType, + props: Props, + hostOmittedProps: readonly string[], +) { + return isHostComponent(Slot) ? omitProps(props, hostOmittedProps) : props; +} + +const portalHostOmittedProps = ['container', 'keepMounted'] as const; +const positionerHostOmittedProps = [ + 'align', + 'alignOffset', + 'anchor', + 'arrowPadding', + 'collisionAvoidance', + 'collisionBoundary', + 'collisionPadding', + 'disableAnchorTracking', + 'positionMethod', + 'render', + 'side', + 'sideOffset', + 'sticky', +] as const; +const paperHostOmittedProps = [ + 'classes', + 'component', + 'elevation', + 'square', + 'sx', + 'variant', +] as const; +const listHostOmittedProps = [ + 'classes', + 'component', + 'dense', + 'disablePadding', + 'subheader', + 'sx', +] as const; + +export interface MenuPreviewPopupSharedSlots { + /** + * The component used for the portal. + * @default BaseMenu.Portal + */ + portal?: React.ElementType; + /** + * The component used for the positioner. + * @default BaseMenu.Positioner + */ + positioner?: React.ElementType; + /** + * The component rendered by the Base UI popup. + * @default 'div' + */ + popup?: React.ElementType; + /** + * The component used for the Material surface. + * @default Paper + */ + paper?: React.ElementType; + /** + * The component used for the presentational list wrapper. + * @default List + */ + list?: React.ElementType; +} + +export interface MenuPreviewPopupSharedSlotProps { + portal?: SlotProps, OwnerState>; + positioner?: SlotProps, OwnerState>; + popup?: SlotProps, OwnerState>; + paper?: SlotProps, OwnerState>; + list?: SlotProps, OwnerState>; +} + +type MenuPreviewPositionerProps = BaseMenu.Positioner.Props; +type MenuPreviewPortalProps = BaseMenu.Portal.Props; + +export type MenuPreviewPopupState = BaseMenu.Popup.State; +export type MenuPreviewPopupSide = NonNullable; +export type MenuPreviewPopupAlign = NonNullable; +export type MenuPreviewPopupOffset = NonNullable; +export type MenuPreviewPopupAnchor = MenuPreviewPositionerProps['anchor']; +export type MenuPreviewPopupPositionMethod = MenuPreviewPositionerProps['positionMethod']; +export type MenuPreviewPopupCollisionBoundary = MenuPreviewPositionerProps['collisionBoundary']; +export type MenuPreviewPopupCollisionPadding = MenuPreviewPositionerProps['collisionPadding']; +export type MenuPreviewPopupCollisionAvoidance = MenuPreviewPositionerProps['collisionAvoidance']; +export type MenuPreviewPopupContainer = MenuPreviewPortalProps['container']; +export type MenuPreviewPopupFinalFocus = BaseMenu.Popup.Props['finalFocus']; + +export interface MenuPreviewPopupPublicProps { + /** + * The menu items. + */ + children?: React.ReactNode; + /** + * CSS class applied to the Base UI popup element. + */ + className?: string | undefined; + /** + * Styles applied to the Base UI popup element. + */ + style?: React.CSSProperties | undefined; + /** + * An element to position the popup against. + * + * By default, the popup is positioned against the trigger. + */ + anchor?: MenuPreviewPopupAnchor; + /** + * Determines which CSS `position` property to use. + * @default 'absolute' + */ + positionMethod?: MenuPreviewPopupPositionMethod; + /** + * Which side of the anchor element to align the popup against. + * @default 'bottom' + */ + side?: MenuPreviewPopupSide | undefined; + /** + * Distance between the anchor and the popup in pixels. + * @default 0 + */ + sideOffset?: MenuPreviewPopupOffset | undefined; + /** + * How to align the popup relative to the specified side. + * @default 'start' + */ + align?: MenuPreviewPopupAlign | undefined; + /** + * Additional offset along the alignment axis in pixels. + * @default 0 + */ + alignOffset?: MenuPreviewPopupOffset | undefined; + /** + * An element or a rectangle that delimits the area that the popup is confined to. + * @default 'clipping-ancestors' + */ + collisionBoundary?: MenuPreviewPopupCollisionBoundary; + /** + * Additional space to maintain from the edge of the collision boundary. + * @default 5 + */ + collisionPadding?: MenuPreviewPopupCollisionPadding; + /** + * Minimum distance to maintain between the arrow and the edges of the popup. + * @default 5 + */ + arrowPadding?: MenuPreviewPositionerProps['arrowPadding']; + /** + * Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. + * @default false + */ + sticky?: MenuPreviewPositionerProps['sticky']; + /** + * Whether to disable the popup from tracking layout shifts of its positioning anchor. + * @default false + */ + disableAnchorTracking?: MenuPreviewPositionerProps['disableAnchorTracking']; + /** + * Determines how to handle collisions when positioning the popup. + */ + collisionAvoidance?: MenuPreviewPopupCollisionAvoidance; + /** + * The container element to portal the popup into. + */ + container?: MenuPreviewPopupContainer; + /** + * Whether to keep the portal mounted in the DOM while the popup is hidden. + * @default false + */ + keepMounted?: MenuPreviewPortalProps['keepMounted']; + /** + * Determines the element to focus when the menu is closed. + */ + finalFocus?: MenuPreviewPopupFinalFocus; +} + +export interface MenuPreviewPopupSharedProps + extends + Omit, + MenuPreviewPopupPublicProps { + classes?: Partial>; + ownerState: OwnerState; + slots?: MenuPreviewPopupSharedSlots; + slotProps?: MenuPreviewPopupSharedSlotProps; + defaultSlots: { + popup: React.ElementType; + paper: React.ElementType; + list: React.ElementType; + }; + defaultPositionerProps?: Partial; + sx?: SxProps; +} + +export const MenuPreviewPopupBase = React.forwardRef(function MenuPreviewPopupBase( + props: MenuPreviewPopupSharedProps, + ref: React.ForwardedRef, +) { + const { + children, + className, + classes, + ownerState, + slots, + slotProps, + defaultSlots, + defaultPositionerProps, + sx, + container, + keepMounted, + anchor, + positionMethod, + side, + sideOffset, + align, + alignOffset, + collisionBoundary, + collisionPadding, + arrowPadding, + sticky, + disableAnchorTracking, + collisionAvoidance, + id, + finalFocus, + style, + ...other + } = props; + + const PortalSlot = slots?.portal ?? BaseMenu.Portal; + const PositionerSlot = slots?.positioner ?? BaseMenu.Positioner; + const PopupSlot = slots?.popup ?? defaultSlots.popup; + const PaperSlot = slots?.paper ?? defaultSlots.paper; + const ListSlot = slots?.list ?? defaultSlots.list; + + const resolvedPortalProps = resolveSlotProps(slotProps?.portal, ownerState); + const resolvedPositionerProps = resolveSlotProps(slotProps?.positioner, ownerState); + const resolvedPopupProps = resolveSlotProps(slotProps?.popup, ownerState); + const resolvedPaperProps = resolveSlotProps(slotProps?.paper, ownerState); + const resolvedListProps = resolveSlotProps(slotProps?.list, ownerState); + const { className: resolvedPopupClassName, ...resolvedPopupOtherProps } = + resolvedPopupProps ?? {}; + const positionerProps = { + ...defaultPositionerProps, + }; + + setDefinedProp(positionerProps, 'anchor', anchor); + setDefinedProp(positionerProps, 'positionMethod', positionMethod); + setDefinedProp(positionerProps, 'side', side); + setDefinedProp(positionerProps, 'sideOffset', sideOffset); + setDefinedProp(positionerProps, 'align', align); + setDefinedProp(positionerProps, 'alignOffset', alignOffset); + setDefinedProp(positionerProps, 'collisionBoundary', collisionBoundary); + setDefinedProp(positionerProps, 'collisionPadding', collisionPadding); + setDefinedProp(positionerProps, 'arrowPadding', arrowPadding); + setDefinedProp(positionerProps, 'sticky', sticky); + setDefinedProp(positionerProps, 'disableAnchorTracking', disableAnchorTracking); + setDefinedProp(positionerProps, 'collisionAvoidance', collisionAvoidance); + + const popupClassName = clsx(classes?.root, className, resolvedPopupClassName); + const popupRender = ; + const portalSlotProps = getSlotProps( + PortalSlot, + { + container, + keepMounted, + ...resolvedPortalProps, + }, + portalHostOmittedProps, + ); + const positionerSlotProps = getSlotProps( + PositionerSlot, + { + ...positionerProps, + ...resolvedPositionerProps, + }, + positionerHostOmittedProps, + ); + const paperSlotProps = getSlotProps( + PaperSlot, + { + elevation: 8, + ...resolvedPaperProps, + className: clsx(classes?.paper, resolvedPaperProps?.className), + sx: mergeSx(sx, resolvedPaperProps?.sx), + }, + paperHostOmittedProps, + ); + const listSlotProps = getSlotProps( + ListSlot, + { + component: 'div', + disablePadding: true, + ...resolvedListProps, + className: clsx(classes?.list, resolvedListProps?.className), + }, + listHostOmittedProps, + ); + + return ( + + + + + {children} + + + + + ); +}) as ( + props: MenuPreviewPopupSharedProps & React.RefAttributes, +) => React.JSX.Element; diff --git a/packages/mui-material/src/MenuPreview/menuPreviewSharedStyles.ts b/packages/mui-material/src/MenuPreview/menuPreviewSharedStyles.ts new file mode 100644 index 00000000000000..75295e4ea2b1fb --- /dev/null +++ b/packages/mui-material/src/MenuPreview/menuPreviewSharedStyles.ts @@ -0,0 +1,172 @@ +import { CSSInterpolation } from '@mui/system'; +import { dividerClasses } from '../Divider'; +import { listItemIconClasses } from '../ListItemIcon'; +import { listItemTextClasses } from '../ListItemText'; +import memoTheme from '../utils/memoTheme'; +import { Theme } from '../styles'; + +export interface SharedMenuPreviewItemClasses { + highlighted: string; + disabled: string; + dense: string; + divider: string; + gutters: string; + selected: string; +} + +interface MenuPreviewItemVariantOwnerState { + dense: boolean; + divider: boolean; + disableGutters: boolean; +} + +export function getMenuPreviewItemStyles( + theme: Theme, + classes: SharedMenuPreviewItemClasses, +): CSSInterpolation { + return { + ...theme.typography.body1, + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + position: 'relative', + textDecoration: 'none', + minHeight: 48, + paddingTop: 6, + paddingBottom: 6, + boxSizing: 'border-box', + whiteSpace: 'nowrap', + cursor: 'default', + userSelect: 'none', + outline: 0, + '&:hover': { + textDecoration: 'none', + backgroundColor: (theme.vars || theme).palette.action.hover, + '@media (hover: none)': { + backgroundColor: 'transparent', + }, + }, + [`&.${classes.selected}`]: { + backgroundColor: theme.alpha( + (theme.vars || theme).palette.primary.main, + (theme.vars || theme).palette.action.selectedOpacity, + ), + [`&.${classes.highlighted}`]: { + backgroundColor: theme.alpha( + (theme.vars || theme).palette.primary.main, + `${(theme.vars || theme).palette.action.selectedOpacity} + ${ + (theme.vars || theme).palette.action.focusOpacity + }`, + ), + }, + }, + [`&.${classes.selected}:hover`]: { + backgroundColor: theme.alpha( + (theme.vars || theme).palette.primary.main, + `${(theme.vars || theme).palette.action.selectedOpacity} + ${ + (theme.vars || theme).palette.action.hoverOpacity + }`, + ), + '@media (hover: none)': { + backgroundColor: theme.alpha( + (theme.vars || theme).palette.primary.main, + (theme.vars || theme).palette.action.selectedOpacity, + ), + }, + }, + [`&.${classes.highlighted}`]: { + backgroundColor: (theme.vars || theme).palette.action.focus, + }, + [`&.${classes.disabled}`]: { + opacity: (theme.vars || theme).palette.action.disabledOpacity, + pointerEvents: 'none', + }, + [`& + .${dividerClasses.root}`]: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + [`& + .${dividerClasses.inset}`]: { + marginLeft: 52, + }, + [`& .${listItemTextClasses.root}`]: { + marginTop: 0, + marginBottom: 0, + }, + [`& .${listItemTextClasses.inset}`]: { + paddingLeft: 36, + }, + [`& .${listItemIconClasses.root}`]: { + minWidth: 36, + }, + variants: [ + { + props: ({ ownerState }: { ownerState: MenuPreviewItemVariantOwnerState }) => + !ownerState.disableGutters, + style: { + paddingLeft: 16, + paddingRight: 16, + }, + }, + { + props: ({ ownerState }: { ownerState: MenuPreviewItemVariantOwnerState }) => + ownerState.divider, + style: { + borderBottom: `1px solid ${(theme.vars || theme).palette.divider}`, + backgroundClip: 'padding-box', + }, + }, + { + props: ({ ownerState }: { ownerState: MenuPreviewItemVariantOwnerState }) => + !ownerState.dense, + style: { + [theme.breakpoints.up('sm')]: { + minHeight: 'auto', + }, + }, + }, + { + props: ({ ownerState }: { ownerState: MenuPreviewItemVariantOwnerState }) => + ownerState.dense, + style: { + minHeight: 32, + paddingTop: 4, + paddingBottom: 4, + ...theme.typography.body2, + [`& .${listItemIconClasses.root} svg`]: { + fontSize: '1.25rem', + }, + }, + }, + ], + }; +} + +export const menuPreviewPopupPaperStyles: CSSInterpolation = { + maxHeight: 'calc(100% - 96px)', + WebkitOverflowScrolling: 'touch', +}; + +export const menuPreviewPopupListStyles: CSSInterpolation = { + outline: 0, +}; + +export const menuPreviewIndicatorStyles = memoTheme(({ theme }) => ({ + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + minWidth: 36, + color: (theme.vars || theme).palette.action.active, + '& [data-mui-menu-preview-indicator-icon]': { + display: 'inline-block', + flexShrink: 0, + width: '1.25rem', + height: '1.25rem', + fill: 'currentColor', + }, + '& [data-mui-menu-preview-checkbox-checkmark]': { + fill: (theme.vars || theme).palette.background.paper, + }, + '&[data-unchecked] [data-mui-menu-preview-indicator-mark]': { + visibility: 'hidden', + }, +})); diff --git a/packages/mui-material/src/MenuPreview/menuPreviewUtils.ts b/packages/mui-material/src/MenuPreview/menuPreviewUtils.ts new file mode 100644 index 00000000000000..bfb185ae77ba5e --- /dev/null +++ b/packages/mui-material/src/MenuPreview/menuPreviewUtils.ts @@ -0,0 +1,76 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import appendOwnerState from '@mui/utils/appendOwnerState'; +import isHostComponent from '@mui/utils/isHostComponent'; + +export type StateClassName = string | ((state: State) => string | undefined) | undefined; + +export function resolveStateClassName( + className: StateClassName, + state: State, +): string | undefined { + return typeof className === 'function' ? className(state) : className; +} + +export function mergeStateClassName( + className: StateClassName, + getClassName: (state: State) => string | undefined, +) { + return (state: State) => clsx(getClassName(state), resolveStateClassName(className, state)); +} + +export type SlotProps = + | SlotPropsValue + | ((ownerState: OwnerState) => SlotPropsValue) + | undefined; + +export function resolveSlotProps( + slotProps: SlotProps, + ownerState: OwnerState, +): SlotPropsValue | undefined { + return typeof slotProps === 'function' + ? (slotProps as (ownerState: OwnerState) => SlotPropsValue)(ownerState) + : slotProps; +} + +export interface MenuPreviewRootSlots { + root?: React.ElementType; +} + +export interface MenuPreviewRootSlotProps { + root?: SlotProps, OwnerState>; +} + +export function getMenuPreviewRootRender( + RootSlot: React.ElementType, + ownerState: OwnerState, + props?: Record, +) { + if (isHostComponent(RootSlot)) { + const hostProps = { ...(props ?? {}) }; + delete hostProps.as; + delete hostProps.component; + delete hostProps.ownerState; + delete hostProps.sx; + + return React.createElement(RootSlot, hostProps); + } + + return React.createElement(RootSlot, appendOwnerState(RootSlot, props ?? {}, ownerState)); +} + +export function isMenuPreviewRootNativeButton( + RootSlot: React.ElementType, + component: React.ElementType | undefined, + defaultNativeButton = false, +) { + if (isHostComponent(RootSlot)) { + return RootSlot === 'button'; + } + + if (component != null) { + return component === 'button'; + } + + return defaultNativeButton; +} diff --git a/packages/mui-material/src/MenuPreviewCheckboxItem/MenuPreviewCheckboxItem.tsx b/packages/mui-material/src/MenuPreviewCheckboxItem/MenuPreviewCheckboxItem.tsx new file mode 100644 index 00000000000000..bbfad0b75225b9 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItem/MenuPreviewCheckboxItem.tsx @@ -0,0 +1,308 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import ListContext from '../List/ListContext'; +import { styled } from '../zero-styled'; +import memoTheme from '../utils/memoTheme'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { getMenuPreviewItemStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + isMenuPreviewRootNativeButton, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewItemClassName, + getMenuPreviewItemOwnerState, + MenuPreviewItemBaseProps, + MenuPreviewItemOwnerState, + MenuPreviewItemVisualProps, + menuPreviewItemOverridesResolver, + useMenuPreviewItemUtilityClasses, +} from '../MenuPreview/menuPreviewItemShared'; +import { + getMenuPreviewCheckboxItemUtilityClass, + menuPreviewCheckboxItemClasses, + MenuPreviewCheckboxItemClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewCheckboxItemSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewCheckboxItemSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewCheckboxItemProps + extends + Omit< + BaseMenu.CheckboxItem.Props, + 'className' | 'nativeButton' | 'onChange' | 'onCheckedChange' | 'render' | 'style' + >, + MenuPreviewItemBaseProps, + MenuPreviewItemVisualProps< + MenuPreviewCheckboxItemClasses, + MenuPreviewCheckboxItemSlots, + MenuPreviewCheckboxItemSlotProps + > { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Whether the checkbox item is currently ticked. + * + * To render an uncontrolled checkbox item, use the `defaultChecked` prop instead. + */ + checked?: boolean | undefined; + /** + * Whether the checkbox item is initially ticked. + * + * To render a controlled checkbox item, use the `checked` prop instead. + * @default false + */ + defaultChecked?: boolean | undefined; + /** + * Event handler called when the checkbox item is ticked or unticked. + */ + onChange?: + | (( + event: Event, + checked: boolean, + eventDetails: BaseMenu.CheckboxItem.ChangeEventDetails, + ) => void) + | undefined; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick?: boolean | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; +} + +const MenuPreviewCheckboxItemRoot = styled('div', { + name: 'MuiMenuPreviewCheckboxItem', + slot: 'Root', + overridesResolver: menuPreviewItemOverridesResolver, +})<{ ownerState: MenuPreviewItemOwnerState }>( + memoTheme(({ theme }) => getMenuPreviewItemStyles(theme, menuPreviewCheckboxItemClasses)), +); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewCheckboxItem API](https://mui.com/material-ui/api/menu-preview-checkbox-item/) + */ +const MenuPreviewCheckboxItem = React.forwardRef(function MenuPreviewCheckboxItem( + inProps: MenuPreviewCheckboxItemProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewCheckboxItem', + }); + + const { + checked, + className, + classes: classesProp, + component, + dense = false, + disabled = false, + disableGutters = false, + divider = false, + nativeButton: nativeButtonProp, + onChange, + selected = false, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + ...getMenuPreviewItemOwnerState({ + checked, + dense, + disabled, + disableGutters, + divider, + selected, + }), + classes: classesProp, + }; + const classes = useMenuPreviewItemUtilityClasses( + ownerState, + getMenuPreviewCheckboxItemUtilityClass, + ); + const childContext = React.useMemo( + () => ({ + dense, + disableGutters, + }), + [dense, disableGutters], + ); + const handleCheckedChange = React.useCallback( + (newChecked: boolean, eventDetails: BaseMenu.CheckboxItem.ChangeEventDetails) => { + onChange?.(eventDetails.event, newChecked, eventDetails); + }, + [onChange], + ); + const RootSlot = slots?.root ?? MenuPreviewCheckboxItemRoot; + + return ( + + + clsx( + className, + getMenuPreviewItemClassName(classes, ownerState, state), + state.checked && classes.checked, + ) + } + checked={checked} + disabled={disabled} + nativeButton={nativeButtonProp ?? isMenuPreviewRootNativeButton(RootSlot, component)} + onCheckedChange={handleCheckedChange} + style={style} + {...other} + /> + + ); +}); + +MenuPreviewCheckboxItem.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * Whether the checkbox item is currently ticked. + * + * To render an uncontrolled checkbox item, use the `defaultChecked` prop instead. + */ + checked: PropTypes.bool, + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick: PropTypes.bool, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * Whether the checkbox item is initially ticked. + * + * To render a controlled checkbox item, use the `checked` prop instead. + * @default false + */ + defaultChecked: PropTypes.bool, + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters: PropTypes.bool, + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider: PropTypes.bool, + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label: PropTypes.string, + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton: PropTypes.bool, + /** + * Event handler called when the checkbox item is ticked or unticked. + */ + onChange: PropTypes.func, + /** + * If `true`, the component is selected. + * @default false + */ + selected: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewCheckboxItem; diff --git a/packages/mui-material/src/MenuPreviewCheckboxItem/index.d.ts b/packages/mui-material/src/MenuPreviewCheckboxItem/index.d.ts new file mode 100644 index 00000000000000..4372d2838e90bb --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItem/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewCheckboxItem'; +export * from './MenuPreviewCheckboxItem'; +export { + menuPreviewCheckboxItemClasses, + getMenuPreviewCheckboxItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewCheckboxItemClasses, + MenuPreviewCheckboxItemClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewCheckboxItem/index.js b/packages/mui-material/src/MenuPreviewCheckboxItem/index.js new file mode 100644 index 00000000000000..d453dbcbd0a5ab --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItem/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewCheckboxItem'; +export * from './MenuPreviewCheckboxItem'; +export { + menuPreviewCheckboxItemClasses, + getMenuPreviewCheckboxItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/MenuPreviewCheckboxItemIndicator.tsx b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/MenuPreviewCheckboxItemIndicator.tsx new file mode 100644 index 00000000000000..f133331c8c69d6 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/MenuPreviewCheckboxItemIndicator.tsx @@ -0,0 +1,224 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { menuPreviewIndicatorStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewCheckboxItemIndicatorUtilityClass, + MenuPreviewCheckboxItemIndicatorClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewCheckboxItemIndicatorSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewCheckboxItemIndicatorSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewCheckboxItemIndicatorProps extends Omit< + BaseMenu.CheckboxItemIndicator.Props, + 'className' | 'render' | 'style' +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Whether to keep the HTML element in the DOM when the checkbox item is not checked. + * @default false + */ + keepMounted?: boolean | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewCheckboxItemIndicatorSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewCheckboxItemIndicatorSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewCheckboxItemIndicatorProps) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + checked: ['checked'], + disabled: ['disabled'], + highlighted: ['highlighted'], + }; + + return { + ...classes, + ...composeClasses(slots, getMenuPreviewCheckboxItemIndicatorUtilityClass, classes), + }; +}; + +const MenuPreviewCheckboxItemIndicatorRoot = styled('span', { + name: 'MuiMenuPreviewCheckboxItemIndicator', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})(menuPreviewIndicatorStyles) as any; + +function DefaultCheckboxIndicatorIcon() { + return ( + + ); +} + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewCheckboxItemIndicator API](https://mui.com/material-ui/api/menu-preview-checkbox-item-indicator/) + */ +const MenuPreviewCheckboxItemIndicator = React.forwardRef(function MenuPreviewCheckboxItemIndicator( + inProps: MenuPreviewCheckboxItemIndicatorProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewCheckboxItemIndicator', + }); + + const { + children, + className, + classes: classesProp, + component, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + clsx( + className, + classes.root, + state.checked && classes.checked, + state.disabled && classes.disabled, + state.highlighted && classes.highlighted, + ) + } + style={style} + {...other} + > + {children ?? } + + ); +}); + +MenuPreviewCheckboxItemIndicator.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * Whether to keep the HTML element in the DOM when the checkbox item is not checked. + * @default false + */ + keepMounted: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewCheckboxItemIndicator; diff --git a/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.d.ts b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.d.ts new file mode 100644 index 00000000000000..4e0c311cd37242 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewCheckboxItemIndicator'; +export * from './MenuPreviewCheckboxItemIndicator'; +export { + menuPreviewCheckboxItemIndicatorClasses, + getMenuPreviewCheckboxItemIndicatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewCheckboxItemIndicatorClasses, + MenuPreviewCheckboxItemIndicatorClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.js b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.js new file mode 100644 index 00000000000000..fb24b5bf809efc --- /dev/null +++ b/packages/mui-material/src/MenuPreviewCheckboxItemIndicator/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewCheckboxItemIndicator'; +export * from './MenuPreviewCheckboxItemIndicator'; +export { + menuPreviewCheckboxItemIndicatorClasses, + getMenuPreviewCheckboxItemIndicatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewGroup/MenuPreviewGroup.tsx b/packages/mui-material/src/MenuPreviewGroup/MenuPreviewGroup.tsx new file mode 100644 index 00000000000000..863f2309232f1b --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroup/MenuPreviewGroup.tsx @@ -0,0 +1,174 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewGroupUtilityClass, + MenuPreviewGroupClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewGroupSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewGroupSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewGroupProps extends Omit< + BaseMenu.Group.Props, + 'className' | 'render' | 'style' +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewGroupSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewGroupSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewGroupProps) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getMenuPreviewGroupUtilityClass, classes); +}; + +const MenuPreviewGroupRoot = styled('div', { + name: 'MuiMenuPreviewGroup', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}) as any; + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewGroup API](https://mui.com/material-ui/api/menu-preview-group/) + */ +const MenuPreviewGroup = React.forwardRef(function MenuPreviewGroup( + inProps: MenuPreviewGroupProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewGroup', + }); + + const { + className, + classes: classesProp, + component, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}); + +MenuPreviewGroup.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewGroup; diff --git a/packages/mui-material/src/MenuPreviewGroup/index.d.ts b/packages/mui-material/src/MenuPreviewGroup/index.d.ts new file mode 100644 index 00000000000000..73a72486bb167f --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroup/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewGroup'; +export * from './MenuPreviewGroup'; +export { + menuPreviewGroupClasses, + getMenuPreviewGroupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewGroupClasses, + MenuPreviewGroupClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewGroup/index.js b/packages/mui-material/src/MenuPreviewGroup/index.js new file mode 100644 index 00000000000000..49e6742bc562e4 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroup/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewGroup'; +export * from './MenuPreviewGroup'; +export { + menuPreviewGroupClasses, + getMenuPreviewGroupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewGroupLabel/MenuPreviewGroupLabel.tsx b/packages/mui-material/src/MenuPreviewGroupLabel/MenuPreviewGroupLabel.tsx new file mode 100644 index 00000000000000..c640acd889bd07 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroupLabel/MenuPreviewGroupLabel.tsx @@ -0,0 +1,176 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import ListSubheader from '../ListSubheader'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewGroupLabelUtilityClass, + MenuPreviewGroupLabelClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewGroupLabelSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewGroupLabelSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewGroupLabelProps extends Omit< + BaseMenu.GroupLabel.Props, + 'className' | 'render' | 'style' +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewGroupLabelSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewGroupLabelSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewGroupLabelProps) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getMenuPreviewGroupLabelUtilityClass, classes); +}; + +const MenuPreviewGroupLabelRoot = styled(ListSubheader, { + name: 'MuiMenuPreviewGroupLabel', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}) as any; + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewGroupLabel API](https://mui.com/material-ui/api/menu-preview-group-label/) + */ +const MenuPreviewGroupLabel = React.forwardRef(function MenuPreviewGroupLabel( + inProps: MenuPreviewGroupLabelProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewGroupLabel', + }); + + const { + className, + classes: classesProp, + component, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}); + +MenuPreviewGroupLabel.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewGroupLabel; diff --git a/packages/mui-material/src/MenuPreviewGroupLabel/index.d.ts b/packages/mui-material/src/MenuPreviewGroupLabel/index.d.ts new file mode 100644 index 00000000000000..6b9ddf97eea6cf --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroupLabel/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewGroupLabel'; +export * from './MenuPreviewGroupLabel'; +export { + menuPreviewGroupLabelClasses, + getMenuPreviewGroupLabelUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewGroupLabelClasses, + MenuPreviewGroupLabelClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewGroupLabel/index.js b/packages/mui-material/src/MenuPreviewGroupLabel/index.js new file mode 100644 index 00000000000000..30bf6f4677fed1 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewGroupLabel/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewGroupLabel'; +export * from './MenuPreviewGroupLabel'; +export { + menuPreviewGroupLabelClasses, + getMenuPreviewGroupLabelUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewItem/MenuPreviewItem.tsx b/packages/mui-material/src/MenuPreviewItem/MenuPreviewItem.tsx new file mode 100644 index 00000000000000..6571355efac4e7 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewItem/MenuPreviewItem.tsx @@ -0,0 +1,241 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import ListContext from '../List/ListContext'; +import { styled } from '../zero-styled'; +import memoTheme from '../utils/memoTheme'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { getMenuPreviewItemStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewItemOwnerState, + MenuPreviewItemBaseProps, + MenuPreviewItemOwnerState, + MenuPreviewItemVisualProps, + menuPreviewItemOverridesResolver, + mergeMenuPreviewItemClassName, + useMenuPreviewItemUtilityClasses, +} from '../MenuPreview/menuPreviewItemShared'; +import { + getMenuPreviewRootRender, + isMenuPreviewRootNativeButton, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewItemUtilityClass, + menuPreviewItemClasses, + MenuPreviewItemClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewItemSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewItemSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewItemProps + extends + Omit, + MenuPreviewItemBaseProps, + MenuPreviewItemVisualProps< + MenuPreviewItemClasses, + MenuPreviewItemSlots, + MenuPreviewItemSlotProps + > { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default true + */ + closeOnClick?: boolean | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; +} + +const MenuPreviewItemRoot = styled('div', { + name: 'MuiMenuPreviewItem', + slot: 'Root', + overridesResolver: menuPreviewItemOverridesResolver, +})<{ ownerState: MenuPreviewItemOwnerState }>( + memoTheme(({ theme }) => getMenuPreviewItemStyles(theme, menuPreviewItemClasses)), +); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewItem API](https://mui.com/material-ui/api/menu-preview-item/) + */ +const MenuPreviewItem = React.forwardRef(function MenuPreviewItem( + inProps: MenuPreviewItemProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewItem', + }); + + const { + className, + classes: classesProp, + component, + dense = false, + disabled = false, + disableGutters = false, + divider = false, + nativeButton: nativeButtonProp, + selected = false, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + ...getMenuPreviewItemOwnerState({ dense, disabled, disableGutters, divider, selected }), + classes: classesProp, + }; + const classes = useMenuPreviewItemUtilityClasses( + ownerState, + getMenuPreviewItemUtilityClass, + ); + const childContext = React.useMemo( + () => ({ + dense, + disableGutters, + }), + [dense, disableGutters], + ); + const RootSlot = slots?.root ?? MenuPreviewItemRoot; + + return ( + + + + ); +}); + +MenuPreviewItem.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * Whether to close the menu when the item is clicked. + * @default true + */ + closeOnClick: PropTypes.bool, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters: PropTypes.bool, + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider: PropTypes.bool, + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label: PropTypes.string, + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton: PropTypes.bool, + /** + * If `true`, the component is selected. + * @default false + */ + selected: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewItem; diff --git a/packages/mui-material/src/MenuPreviewItem/index.d.ts b/packages/mui-material/src/MenuPreviewItem/index.d.ts new file mode 100644 index 00000000000000..febb5776db5b46 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewItem/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewItem'; +export * from './MenuPreviewItem'; +export { + menuPreviewItemClasses, + getMenuPreviewItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewItemClasses, + MenuPreviewItemClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewItem/index.js b/packages/mui-material/src/MenuPreviewItem/index.js new file mode 100644 index 00000000000000..f2296b0ea1f44f --- /dev/null +++ b/packages/mui-material/src/MenuPreviewItem/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewItem'; +export * from './MenuPreviewItem'; +export { + menuPreviewItemClasses, + getMenuPreviewItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewLinkItem/MenuPreviewLinkItem.tsx b/packages/mui-material/src/MenuPreviewLinkItem/MenuPreviewLinkItem.tsx new file mode 100644 index 00000000000000..9f8e8978ff8b2e --- /dev/null +++ b/packages/mui-material/src/MenuPreviewLinkItem/MenuPreviewLinkItem.tsx @@ -0,0 +1,233 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import ListContext from '../List/ListContext'; +import { styled } from '../zero-styled'; +import memoTheme from '../utils/memoTheme'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { getMenuPreviewItemStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewItemOwnerState, + MenuPreviewLinkItemBaseProps, + MenuPreviewItemOwnerState, + MenuPreviewItemVisualProps, + menuPreviewItemOverridesResolver, + mergeMenuPreviewItemClassName, + useMenuPreviewItemUtilityClasses, +} from '../MenuPreview/menuPreviewItemShared'; +import { + getMenuPreviewLinkItemUtilityClass, + menuPreviewLinkItemClasses, + MenuPreviewLinkItemClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewLinkItemSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewLinkItemSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewLinkItemProps + extends + Omit, + MenuPreviewLinkItemBaseProps, + MenuPreviewItemVisualProps< + MenuPreviewLinkItemClasses, + MenuPreviewLinkItemSlots, + MenuPreviewLinkItemSlotProps + > { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * The URL that the link item points to. + */ + href?: string | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick?: boolean | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; +} + +const MenuPreviewLinkItemRoot = styled('a', { + name: 'MuiMenuPreviewLinkItem', + slot: 'Root', + overridesResolver: menuPreviewItemOverridesResolver, +})<{ ownerState: MenuPreviewItemOwnerState }>( + memoTheme(({ theme }) => getMenuPreviewItemStyles(theme, menuPreviewLinkItemClasses)), +); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewLinkItem API](https://mui.com/material-ui/api/menu-preview-link-item/) + */ +const MenuPreviewLinkItem = React.forwardRef(function MenuPreviewLinkItem( + inProps: MenuPreviewLinkItemProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewLinkItem', + }); + + const { + className, + classes: classesProp, + component, + dense = false, + disableGutters = false, + divider = false, + selected = false, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + ...getMenuPreviewItemOwnerState({ + dense, + disabled: false, + disableGutters, + divider, + selected, + }), + classes: classesProp, + }; + const classes = useMenuPreviewItemUtilityClasses( + ownerState, + getMenuPreviewLinkItemUtilityClass, + ); + const childContext = React.useMemo( + () => ({ + dense, + disableGutters, + }), + [dense, disableGutters], + ); + + return ( + + + + ); +}); + +MenuPreviewLinkItem.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick: PropTypes.bool, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense: PropTypes.bool, + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters: PropTypes.bool, + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider: PropTypes.bool, + /** + * The URL that the link item points to. + */ + href: PropTypes.string, + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label: PropTypes.string, + /** + * If `true`, the component is selected. + * @default false + */ + selected: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewLinkItem; diff --git a/packages/mui-material/src/MenuPreviewLinkItem/index.d.ts b/packages/mui-material/src/MenuPreviewLinkItem/index.d.ts new file mode 100644 index 00000000000000..d67220bdee8596 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewLinkItem/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewLinkItem'; +export * from './MenuPreviewLinkItem'; +export { + menuPreviewLinkItemClasses, + getMenuPreviewLinkItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewLinkItemClasses, + MenuPreviewLinkItemClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewLinkItem/index.js b/packages/mui-material/src/MenuPreviewLinkItem/index.js new file mode 100644 index 00000000000000..7a69a871b02fdc --- /dev/null +++ b/packages/mui-material/src/MenuPreviewLinkItem/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewLinkItem'; +export * from './MenuPreviewLinkItem'; +export { + menuPreviewLinkItemClasses, + getMenuPreviewLinkItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewPopup/MenuPreviewPopup.tsx b/packages/mui-material/src/MenuPreviewPopup/MenuPreviewPopup.tsx new file mode 100644 index 00000000000000..cafc2212940014 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewPopup/MenuPreviewPopup.tsx @@ -0,0 +1,389 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import composeClasses from '@mui/utils/composeClasses'; +import HTMLElementType from '@mui/utils/HTMLElementType'; +import { SxProps } from '@mui/system'; +import Paper from '../Paper'; +import List from '../List'; +import { styled } from '../zero-styled'; +import { Theme } from '../styles'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + MenuPreviewPopupBase, + MenuPreviewPopupPublicProps, + MenuPreviewPopupSharedProps, + MenuPreviewPopupSharedSlotProps, + MenuPreviewPopupSharedSlots, +} from '../MenuPreview/menuPreviewPopupShared'; +import { + menuPreviewPopupListStyles, + menuPreviewPopupPaperStyles, +} from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewPopupUtilityClass, + MenuPreviewPopupClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewPopupProps extends Omit< + MenuPreviewPopupSharedProps, + | 'classes' + | 'defaultPositionerProps' + | 'defaultSlots' + | 'ownerState' + | keyof MenuPreviewPopupPublicProps +> { + /** + * The menu items. + */ + children?: React.ReactNode; + /** + * CSS class applied to the Base UI popup element. + */ + className?: MenuPreviewPopupPublicProps['className']; + /** + * Styles applied to the Base UI popup element. + */ + style?: MenuPreviewPopupPublicProps['style']; + /** + * An element to position the popup against. + * + * By default, the popup is positioned against the trigger. + */ + anchor?: MenuPreviewPopupPublicProps['anchor']; + /** + * Determines which CSS `position` property to use. + * @default 'absolute' + */ + positionMethod?: MenuPreviewPopupPublicProps['positionMethod']; + /** + * Which side of the anchor element to align the popup against. + * @default 'bottom' + */ + side?: MenuPreviewPopupPublicProps['side']; + /** + * Distance between the anchor and the popup in pixels. + * @default 0 + */ + sideOffset?: MenuPreviewPopupPublicProps['sideOffset']; + /** + * How to align the popup relative to the specified side. + * @default 'start' + */ + align?: MenuPreviewPopupPublicProps['align']; + /** + * Additional offset along the alignment axis in pixels. + * @default 0 + */ + alignOffset?: MenuPreviewPopupPublicProps['alignOffset']; + /** + * An element or a rectangle that delimits the area that the popup is confined to. + * @default 'clipping-ancestors' + */ + collisionBoundary?: MenuPreviewPopupPublicProps['collisionBoundary']; + /** + * Additional space to maintain from the edge of the collision boundary. + * @default 5 + */ + collisionPadding?: MenuPreviewPopupPublicProps['collisionPadding']; + /** + * Minimum distance to maintain between the arrow and the edges of the popup. + * @default 5 + */ + arrowPadding?: MenuPreviewPopupPublicProps['arrowPadding']; + /** + * Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. + * @default false + */ + sticky?: MenuPreviewPopupPublicProps['sticky']; + /** + * Whether to disable the popup from tracking layout shifts of its positioning anchor. + * @default false + */ + disableAnchorTracking?: MenuPreviewPopupPublicProps['disableAnchorTracking']; + /** + * Determines how to handle collisions when positioning the popup. + */ + collisionAvoidance?: MenuPreviewPopupPublicProps['collisionAvoidance']; + /** + * The container element to portal the popup into. + */ + container?: MenuPreviewPopupPublicProps['container']; + /** + * Whether to keep the portal mounted in the DOM while the popup is hidden. + * @default false + */ + keepMounted?: MenuPreviewPopupPublicProps['keepMounted']; + /** + * Determines the element to focus when the menu is closed. + */ + finalFocus?: MenuPreviewPopupPublicProps['finalFocus']; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewPopupSlotProps | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewPopupSlots | undefined; +} + +export interface MenuPreviewPopupOwnerState extends MenuPreviewPopupProps {} + +export interface MenuPreviewPopupSlots extends MenuPreviewPopupSharedSlots {} + +export interface MenuPreviewPopupSlotProps extends MenuPreviewPopupSharedSlotProps {} + +const useUtilityClasses = (ownerState: MenuPreviewPopupOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + paper: ['paper'], + list: ['list'], + }; + + return composeClasses(slots, getMenuPreviewPopupUtilityClass, classes); +}; + +const MenuPreviewPopupRoot = styled('div', { + name: 'MuiMenuPreviewPopup', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({ + outline: 0, +}); + +const MenuPreviewPopupPaper = styled(Paper, { + name: 'MuiMenuPreviewPopup', + slot: 'Paper', + overridesResolver: (props, styles) => styles.paper, +})(menuPreviewPopupPaperStyles); + +const MenuPreviewPopupList = styled(List, { + name: 'MuiMenuPreviewPopup', + slot: 'List', + overridesResolver: (props, styles) => styles.list, +})(menuPreviewPopupListStyles); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewPopup API](https://mui.com/material-ui/api/menu-preview-popup/) + */ +const MenuPreviewPopup = React.forwardRef(function MenuPreviewPopup( + inProps: MenuPreviewPopupProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewPopup', + }); + + const ownerState: MenuPreviewPopupOwnerState = { + side: 'bottom', + align: 'start', + ...props, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}); + +MenuPreviewPopup.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * How to align the popup relative to the specified side. + * @default 'start' + */ + align: PropTypes.oneOf(['center', 'end', 'start']), + /** + * Additional offset along the alignment axis in pixels. + * @default 0 + */ + alignOffset: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + /** + * An element to position the popup against. + * + * By default, the popup is positioned against the trigger. + */ + anchor: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.object, + PropTypes.func, + ]), + /** + * Minimum distance to maintain between the arrow and the edges of the popup. + * @default 5 + */ + arrowPadding: PropTypes.number, + /** + * The menu items. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the Base UI popup element. + */ + className: PropTypes.string, + /** + * Determines how to handle collisions when positioning the popup. + */ + collisionAvoidance: PropTypes.oneOfType([ + PropTypes.shape({ + align: PropTypes.oneOf(['flip', 'none', 'shift']), + fallbackAxisSide: PropTypes.oneOf(['end', 'none', 'start']), + side: PropTypes.oneOf(['flip', 'none']), + }), + PropTypes.shape({ + align: PropTypes.oneOf(['none', 'shift']), + fallbackAxisSide: PropTypes.oneOf(['end', 'none', 'start']), + side: PropTypes.oneOf(['none', 'shift']), + }), + ]), + /** + * An element or a rectangle that delimits the area that the popup is confined to. + * @default 'clipping-ancestors' + */ + collisionBoundary: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['clipping-ancestors']), + HTMLElementType, + PropTypes.arrayOf(HTMLElementType), + PropTypes.shape({ + height: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + ]), + /** + * Additional space to maintain from the edge of the collision boundary. + * @default 5 + */ + collisionPadding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), + /** + * The container element to portal the popup into. + */ + container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.object, + PropTypes.func, + ]), + /** + * Whether to disable the popup from tracking layout shifts of its positioning anchor. + * @default false + */ + disableAnchorTracking: PropTypes.bool, + /** + * Determines the element to focus when the menu is closed. + */ + finalFocus: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: HTMLElementType, + }), + PropTypes.bool, + ]), + /** + * Whether to keep the portal mounted in the DOM while the popup is hidden. + * @default false + */ + keepMounted: PropTypes.bool, + /** + * Determines which CSS `position` property to use. + * @default 'absolute' + */ + positionMethod: PropTypes.oneOf(['absolute', 'fixed']), + /** + * Which side of the anchor element to align the popup against. + * @default 'bottom' + */ + side: PropTypes.oneOf(['bottom', 'inline-end', 'inline-start', 'left', 'right', 'top']), + /** + * Distance between the anchor and the popup in pixels. + * @default 0 + */ + sideOffset: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + list: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popup: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + portal: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + positioner: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + list: PropTypes.elementType, + paper: PropTypes.elementType, + popup: PropTypes.elementType, + portal: PropTypes.elementType, + positioner: PropTypes.elementType, + }), + /** + * Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. + * @default false + */ + sticky: PropTypes.bool, + /** + * Styles applied to the Base UI popup element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewPopup; diff --git a/packages/mui-material/src/MenuPreviewPopup/index.d.ts b/packages/mui-material/src/MenuPreviewPopup/index.d.ts new file mode 100644 index 00000000000000..82ce5ac8e7311f --- /dev/null +++ b/packages/mui-material/src/MenuPreviewPopup/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewPopup'; +export * from './MenuPreviewPopup'; +export { + menuPreviewPopupClasses, + getMenuPreviewPopupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewPopupClasses, + MenuPreviewPopupClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewPopup/index.js b/packages/mui-material/src/MenuPreviewPopup/index.js new file mode 100644 index 00000000000000..25a0b859273605 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewPopup/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewPopup'; +export * from './MenuPreviewPopup'; +export { + menuPreviewPopupClasses, + getMenuPreviewPopupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioGroup/MenuPreviewRadioGroup.tsx b/packages/mui-material/src/MenuPreviewRadioGroup/MenuPreviewRadioGroup.tsx new file mode 100644 index 00000000000000..2ccf9ab360f89b --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioGroup/MenuPreviewRadioGroup.tsx @@ -0,0 +1,228 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewRadioGroupUtilityClass, + MenuPreviewRadioGroupClasses, +} from '../MenuPreview/menuPreviewClasses'; + +interface MenuPreviewRadioGroupOwnerState extends MenuPreviewRadioGroupProps {} + +export interface MenuPreviewRadioGroupSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewRadioGroupSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewRadioGroupProps extends Omit< + BaseMenu.RadioGroup.Props, + 'className' | 'onChange' | 'onValueChange' | 'render' | 'style' +> { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * The controlled value of the radio item that should be currently selected. + */ + value?: any; + /** + * The uncontrolled value of the radio item that should be initially selected. + */ + defaultValue?: any; + /** + * Function called when the selected value changes. + */ + onChange?: + | ((event: Event, value: any, eventDetails: BaseMenu.RadioGroup.ChangeEventDetails) => void) + | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewRadioGroupSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewRadioGroupSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewRadioGroupOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + disabled: ['disabled'], + }; + + return { + ...classes, + ...composeClasses(slots, getMenuPreviewRadioGroupUtilityClass, classes), + }; +}; + +const MenuPreviewRadioGroupRoot = styled('div', { + name: 'MuiMenuPreviewRadioGroup', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}) as any; + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewRadioGroup API](https://mui.com/material-ui/api/menu-preview-radio-group/) + */ +const MenuPreviewRadioGroup = React.forwardRef(function MenuPreviewRadioGroup( + inProps: MenuPreviewRadioGroupProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewRadioGroup', + }); + + const { + className, + classes: classesProp, + component, + onChange, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + }; + const classes = useUtilityClasses(ownerState); + const handleValueChange = React.useCallback( + (newValue: any, eventDetails: BaseMenu.RadioGroup.ChangeEventDetails) => { + onChange?.(eventDetails.event, newValue, eventDetails); + }, + [onChange], + ); + + return ( + clsx(className, classes.root, state.disabled && classes.disabled)} + onValueChange={handleValueChange} + style={style} + {...other} + /> + ); +}); + +MenuPreviewRadioGroup.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * The uncontrolled value of the radio item that should be initially selected. + */ + defaultValue: PropTypes.any, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * Function called when the selected value changes. + */ + onChange: PropTypes.func, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * The controlled value of the radio item that should be currently selected. + */ + value: PropTypes.any, +} as any; + +export default MenuPreviewRadioGroup; diff --git a/packages/mui-material/src/MenuPreviewRadioGroup/index.d.ts b/packages/mui-material/src/MenuPreviewRadioGroup/index.d.ts new file mode 100644 index 00000000000000..f4e14f49d9bc87 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioGroup/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewRadioGroup'; +export * from './MenuPreviewRadioGroup'; +export { + menuPreviewRadioGroupClasses, + getMenuPreviewRadioGroupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewRadioGroupClasses, + MenuPreviewRadioGroupClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioGroup/index.js b/packages/mui-material/src/MenuPreviewRadioGroup/index.js new file mode 100644 index 00000000000000..a5e718613154f1 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioGroup/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewRadioGroup'; +export * from './MenuPreviewRadioGroup'; +export { + menuPreviewRadioGroupClasses, + getMenuPreviewRadioGroupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioItem/MenuPreviewRadioItem.tsx b/packages/mui-material/src/MenuPreviewRadioItem/MenuPreviewRadioItem.tsx new file mode 100644 index 00000000000000..5a76496c40712d --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItem/MenuPreviewRadioItem.tsx @@ -0,0 +1,256 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import ListContext from '../List/ListContext'; +import { styled } from '../zero-styled'; +import memoTheme from '../utils/memoTheme'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { getMenuPreviewItemStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + isMenuPreviewRootNativeButton, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewItemClassName, + getMenuPreviewItemOwnerState, + MenuPreviewItemBaseProps, + MenuPreviewItemOwnerState, + MenuPreviewItemVisualProps, + menuPreviewItemOverridesResolver, + useMenuPreviewItemUtilityClasses, +} from '../MenuPreview/menuPreviewItemShared'; +import { + getMenuPreviewRadioItemUtilityClass, + menuPreviewRadioItemClasses, + MenuPreviewRadioItemClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewRadioItemSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewRadioItemSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewRadioItemProps + extends + Omit, + MenuPreviewItemBaseProps, + MenuPreviewItemVisualProps< + MenuPreviewRadioItemClasses, + MenuPreviewRadioItemSlots, + MenuPreviewRadioItemSlotProps + > { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Value of the radio item. + */ + value: any; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick?: boolean | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; +} + +const MenuPreviewRadioItemRoot = styled('div', { + name: 'MuiMenuPreviewRadioItem', + slot: 'Root', + overridesResolver: menuPreviewItemOverridesResolver, +})<{ ownerState: MenuPreviewItemOwnerState }>( + memoTheme(({ theme }) => getMenuPreviewItemStyles(theme, menuPreviewRadioItemClasses)), +); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewRadioItem API](https://mui.com/material-ui/api/menu-preview-radio-item/) + */ +const MenuPreviewRadioItem = React.forwardRef(function MenuPreviewRadioItem( + inProps: MenuPreviewRadioItemProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewRadioItem', + }); + + const { + className, + classes: classesProp, + component, + dense = false, + disabled = false, + disableGutters = false, + divider = false, + nativeButton: nativeButtonProp, + selected = false, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + ...getMenuPreviewItemOwnerState({ dense, disabled, disableGutters, divider, selected }), + classes: classesProp, + }; + const classes = useMenuPreviewItemUtilityClasses( + ownerState, + getMenuPreviewRadioItemUtilityClass, + ); + const childContext = React.useMemo( + () => ({ + dense, + disableGutters, + }), + [dense, disableGutters], + ); + const RootSlot = slots?.root ?? MenuPreviewRadioItemRoot; + + return ( + + + clsx( + className, + getMenuPreviewItemClassName(classes, ownerState, state), + state.checked && classes.checked, + ) + } + disabled={disabled} + nativeButton={nativeButtonProp ?? isMenuPreviewRootNativeButton(RootSlot, component)} + style={style} + {...other} + /> + + ); +}); + +MenuPreviewRadioItem.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * Whether to close the menu when the item is clicked. + * @default false + */ + closeOnClick: PropTypes.bool, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters: PropTypes.bool, + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider: PropTypes.bool, + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label: PropTypes.string, + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton: PropTypes.bool, + /** + * If `true`, the component is selected. + * @default false + */ + selected: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), + /** + * Value of the radio item. + */ + value: PropTypes.any.isRequired, +} as any; + +export default MenuPreviewRadioItem; diff --git a/packages/mui-material/src/MenuPreviewRadioItem/index.d.ts b/packages/mui-material/src/MenuPreviewRadioItem/index.d.ts new file mode 100644 index 00000000000000..05366886438934 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItem/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewRadioItem'; +export * from './MenuPreviewRadioItem'; +export { + menuPreviewRadioItemClasses, + getMenuPreviewRadioItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewRadioItemClasses, + MenuPreviewRadioItemClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioItem/index.js b/packages/mui-material/src/MenuPreviewRadioItem/index.js new file mode 100644 index 00000000000000..fa1950c32d8e36 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItem/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewRadioItem'; +export * from './MenuPreviewRadioItem'; +export { + menuPreviewRadioItemClasses, + getMenuPreviewRadioItemUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioItemIndicator/MenuPreviewRadioItemIndicator.tsx b/packages/mui-material/src/MenuPreviewRadioItemIndicator/MenuPreviewRadioItemIndicator.tsx new file mode 100644 index 00000000000000..09c7affeb4424d --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItemIndicator/MenuPreviewRadioItemIndicator.tsx @@ -0,0 +1,223 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { menuPreviewIndicatorStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewRadioItemIndicatorUtilityClass, + MenuPreviewRadioItemIndicatorClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewRadioItemIndicatorSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewRadioItemIndicatorSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewRadioItemIndicatorProps extends Omit< + BaseMenu.RadioItemIndicator.Props, + 'className' | 'render' | 'style' +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Whether to keep the HTML element in the DOM when the radio item is inactive. + * @default false + */ + keepMounted?: boolean | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewRadioItemIndicatorSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewRadioItemIndicatorSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewRadioItemIndicatorProps) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + checked: ['checked'], + disabled: ['disabled'], + highlighted: ['highlighted'], + }; + + return { + ...classes, + ...composeClasses(slots, getMenuPreviewRadioItemIndicatorUtilityClass, classes), + }; +}; + +const MenuPreviewRadioItemIndicatorRoot = styled('span', { + name: 'MuiMenuPreviewRadioItemIndicator', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})(menuPreviewIndicatorStyles) as any; + +function DefaultRadioIndicatorIcon() { + return ( + + ); +} + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewRadioItemIndicator API](https://mui.com/material-ui/api/menu-preview-radio-item-indicator/) + */ +const MenuPreviewRadioItemIndicator = React.forwardRef(function MenuPreviewRadioItemIndicator( + inProps: MenuPreviewRadioItemIndicatorProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewRadioItemIndicator', + }); + + const { + children, + className, + classes: classesProp, + component, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + clsx( + className, + classes.root, + state.checked && classes.checked, + state.disabled && classes.disabled, + state.highlighted && classes.highlighted, + ) + } + style={style} + {...other} + > + {children ?? } + + ); +}); + +MenuPreviewRadioItemIndicator.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * Whether to keep the HTML element in the DOM when the radio item is inactive. + * @default false + */ + keepMounted: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewRadioItemIndicator; diff --git a/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.d.ts b/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.d.ts new file mode 100644 index 00000000000000..4fa829e8dedd1c --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewRadioItemIndicator'; +export * from './MenuPreviewRadioItemIndicator'; +export { + menuPreviewRadioItemIndicatorClasses, + getMenuPreviewRadioItemIndicatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewRadioItemIndicatorClasses, + MenuPreviewRadioItemIndicatorClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.js b/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.js new file mode 100644 index 00000000000000..32b270dcd36da5 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewRadioItemIndicator/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewRadioItemIndicator'; +export * from './MenuPreviewRadioItemIndicator'; +export { + menuPreviewRadioItemIndicatorClasses, + getMenuPreviewRadioItemIndicatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSeparator/MenuPreviewSeparator.tsx b/packages/mui-material/src/MenuPreviewSeparator/MenuPreviewSeparator.tsx new file mode 100644 index 00000000000000..6a01bce0a07c73 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSeparator/MenuPreviewSeparator.tsx @@ -0,0 +1,184 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Separator as BaseSeparator } from '@base-ui/react/separator'; +import { SxProps } from '@mui/system'; +import Divider from '../Divider'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + getMenuPreviewRootRender, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewSeparatorUtilityClass, + MenuPreviewSeparatorClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewSeparatorSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewSeparatorSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewSeparatorProps extends Omit< + BaseSeparator.Props, + 'className' | 'render' | 'style' +> { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewSeparatorSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewSeparatorSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +const useUtilityClasses = (ownerState: MenuPreviewSeparatorProps) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + }; + + return composeClasses(slots, getMenuPreviewSeparatorUtilityClass, classes); +}; + +const MenuPreviewSeparatorRoot = styled(Divider, { + name: 'MuiMenuPreviewSeparator', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}) as any; + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewSeparator API](https://mui.com/material-ui/api/menu-preview-separator/) + */ +const MenuPreviewSeparator = React.forwardRef(function MenuPreviewSeparator( + inProps: MenuPreviewSeparatorProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewSeparator', + }); + + const { + className, + classes: classesProp, + component, + orientation = 'horizontal', + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + classes: classesProp, + orientation, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}); + +MenuPreviewSeparator.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * The orientation of the separator. + * @default 'horizontal' + */ + orientation: PropTypes.oneOf(['horizontal', 'vertical']), + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewSeparator; diff --git a/packages/mui-material/src/MenuPreviewSeparator/index.d.ts b/packages/mui-material/src/MenuPreviewSeparator/index.d.ts new file mode 100644 index 00000000000000..d39696b5ea6141 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSeparator/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewSeparator'; +export * from './MenuPreviewSeparator'; +export { + menuPreviewSeparatorClasses, + getMenuPreviewSeparatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewSeparatorClasses, + MenuPreviewSeparatorClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSeparator/index.js b/packages/mui-material/src/MenuPreviewSeparator/index.js new file mode 100644 index 00000000000000..b610767aee6389 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSeparator/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewSeparator'; +export * from './MenuPreviewSeparator'; +export { + menuPreviewSeparatorClasses, + getMenuPreviewSeparatorUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuPopup/MenuPreviewSubmenuPopup.tsx b/packages/mui-material/src/MenuPreviewSubmenuPopup/MenuPreviewSubmenuPopup.tsx new file mode 100644 index 00000000000000..752bd5347ad8d7 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuPopup/MenuPreviewSubmenuPopup.tsx @@ -0,0 +1,389 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import composeClasses from '@mui/utils/composeClasses'; +import HTMLElementType from '@mui/utils/HTMLElementType'; +import { SxProps } from '@mui/system'; +import Paper from '../Paper'; +import List from '../List'; +import { styled } from '../zero-styled'; +import { Theme } from '../styles'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + MenuPreviewPopupBase, + MenuPreviewPopupPublicProps, + MenuPreviewPopupSharedProps, + MenuPreviewPopupSharedSlotProps, + MenuPreviewPopupSharedSlots, +} from '../MenuPreview/menuPreviewPopupShared'; +import { + menuPreviewPopupListStyles, + menuPreviewPopupPaperStyles, +} from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewSubmenuPopupUtilityClass, + MenuPreviewSubmenuPopupClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewSubmenuPopupProps extends Omit< + MenuPreviewPopupSharedProps, + | 'classes' + | 'defaultPositionerProps' + | 'defaultSlots' + | 'ownerState' + | keyof MenuPreviewPopupPublicProps +> { + /** + * The submenu items. + */ + children?: React.ReactNode; + /** + * CSS class applied to the Base UI popup element. + */ + className?: MenuPreviewPopupPublicProps['className']; + /** + * Styles applied to the Base UI popup element. + */ + style?: MenuPreviewPopupPublicProps['style']; + /** + * An element to position the popup against. + * + * By default, the popup is positioned against the submenu trigger. + */ + anchor?: MenuPreviewPopupPublicProps['anchor']; + /** + * Determines which CSS `position` property to use. + * @default 'absolute' + */ + positionMethod?: MenuPreviewPopupPublicProps['positionMethod']; + /** + * Which side of the anchor element to align the popup against. + * @default 'inline-end' + */ + side?: MenuPreviewPopupPublicProps['side']; + /** + * Distance between the anchor and the popup in pixels. + * @default 0 + */ + sideOffset?: MenuPreviewPopupPublicProps['sideOffset']; + /** + * How to align the popup relative to the specified side. + * @default 'start' + */ + align?: MenuPreviewPopupPublicProps['align']; + /** + * Additional offset along the alignment axis in pixels. + * @default 0 + */ + alignOffset?: MenuPreviewPopupPublicProps['alignOffset']; + /** + * An element or a rectangle that delimits the area that the popup is confined to. + * @default 'clipping-ancestors' + */ + collisionBoundary?: MenuPreviewPopupPublicProps['collisionBoundary']; + /** + * Additional space to maintain from the edge of the collision boundary. + * @default 5 + */ + collisionPadding?: MenuPreviewPopupPublicProps['collisionPadding']; + /** + * Minimum distance to maintain between the arrow and the edges of the popup. + * @default 5 + */ + arrowPadding?: MenuPreviewPopupPublicProps['arrowPadding']; + /** + * Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. + * @default false + */ + sticky?: MenuPreviewPopupPublicProps['sticky']; + /** + * Whether to disable the popup from tracking layout shifts of its positioning anchor. + * @default false + */ + disableAnchorTracking?: MenuPreviewPopupPublicProps['disableAnchorTracking']; + /** + * Determines how to handle collisions when positioning the popup. + */ + collisionAvoidance?: MenuPreviewPopupPublicProps['collisionAvoidance']; + /** + * The container element to portal the popup into. + */ + container?: MenuPreviewPopupPublicProps['container']; + /** + * Whether to keep the portal mounted in the DOM while the popup is hidden. + * @default false + */ + keepMounted?: MenuPreviewPopupPublicProps['keepMounted']; + /** + * Determines the element to focus when the menu is closed. + */ + finalFocus?: MenuPreviewPopupPublicProps['finalFocus']; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewSubmenuPopupSlotProps | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewSubmenuPopupSlots | undefined; +} + +export interface MenuPreviewSubmenuPopupOwnerState extends MenuPreviewSubmenuPopupProps {} + +export interface MenuPreviewSubmenuPopupSlots extends MenuPreviewPopupSharedSlots {} + +export interface MenuPreviewSubmenuPopupSlotProps extends MenuPreviewPopupSharedSlotProps {} + +const useUtilityClasses = (ownerState: MenuPreviewSubmenuPopupOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + paper: ['paper'], + list: ['list'], + }; + + return composeClasses(slots, getMenuPreviewSubmenuPopupUtilityClass, classes); +}; + +const MenuPreviewSubmenuPopupRoot = styled('div', { + name: 'MuiMenuPreviewSubmenuPopup', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({ + outline: 0, +}); + +const MenuPreviewSubmenuPopupPaper = styled(Paper, { + name: 'MuiMenuPreviewSubmenuPopup', + slot: 'Paper', + overridesResolver: (props, styles) => styles.paper, +})(menuPreviewPopupPaperStyles); + +const MenuPreviewSubmenuPopupList = styled(List, { + name: 'MuiMenuPreviewSubmenuPopup', + slot: 'List', + overridesResolver: (props, styles) => styles.list, +})(menuPreviewPopupListStyles); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewSubmenuPopup API](https://mui.com/material-ui/api/menu-preview-submenu-popup/) + */ +const MenuPreviewSubmenuPopup = React.forwardRef(function MenuPreviewSubmenuPopup( + inProps: MenuPreviewSubmenuPopupProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewSubmenuPopup', + }); + + const ownerState: MenuPreviewSubmenuPopupOwnerState = { + side: 'inline-end', + align: 'start', + ...props, + }; + const classes = useUtilityClasses(ownerState); + + return ( + + ); +}); + +MenuPreviewSubmenuPopup.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * How to align the popup relative to the specified side. + * @default 'start' + */ + align: PropTypes.oneOf(['center', 'end', 'start']), + /** + * Additional offset along the alignment axis in pixels. + * @default 0 + */ + alignOffset: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + /** + * An element to position the popup against. + * + * By default, the popup is positioned against the submenu trigger. + */ + anchor: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.object, + PropTypes.func, + ]), + /** + * Minimum distance to maintain between the arrow and the edges of the popup. + * @default 5 + */ + arrowPadding: PropTypes.number, + /** + * The submenu items. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the Base UI popup element. + */ + className: PropTypes.string, + /** + * Determines how to handle collisions when positioning the popup. + */ + collisionAvoidance: PropTypes.oneOfType([ + PropTypes.shape({ + align: PropTypes.oneOf(['flip', 'none', 'shift']), + fallbackAxisSide: PropTypes.oneOf(['end', 'none', 'start']), + side: PropTypes.oneOf(['flip', 'none']), + }), + PropTypes.shape({ + align: PropTypes.oneOf(['none', 'shift']), + fallbackAxisSide: PropTypes.oneOf(['end', 'none', 'start']), + side: PropTypes.oneOf(['none', 'shift']), + }), + ]), + /** + * An element or a rectangle that delimits the area that the popup is confined to. + * @default 'clipping-ancestors' + */ + collisionBoundary: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.oneOf(['clipping-ancestors']), + HTMLElementType, + PropTypes.arrayOf(HTMLElementType), + PropTypes.shape({ + height: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + }), + ]), + /** + * Additional space to maintain from the edge of the collision boundary. + * @default 5 + */ + collisionPadding: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.shape({ + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + top: PropTypes.number, + }), + ]), + /** + * The container element to portal the popup into. + */ + container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.object, + PropTypes.func, + ]), + /** + * Whether to disable the popup from tracking layout shifts of its positioning anchor. + * @default false + */ + disableAnchorTracking: PropTypes.bool, + /** + * Determines the element to focus when the menu is closed. + */ + finalFocus: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: HTMLElementType, + }), + PropTypes.bool, + ]), + /** + * Whether to keep the portal mounted in the DOM while the popup is hidden. + * @default false + */ + keepMounted: PropTypes.bool, + /** + * Determines which CSS `position` property to use. + * @default 'absolute' + */ + positionMethod: PropTypes.oneOf(['absolute', 'fixed']), + /** + * Which side of the anchor element to align the popup against. + * @default 'inline-end' + */ + side: PropTypes.oneOf(['bottom', 'inline-end', 'inline-start', 'left', 'right', 'top']), + /** + * Distance between the anchor and the popup in pixels. + * @default 0 + */ + sideOffset: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + list: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + paper: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + popup: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + portal: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + positioner: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + list: PropTypes.elementType, + paper: PropTypes.elementType, + popup: PropTypes.elementType, + portal: PropTypes.elementType, + positioner: PropTypes.elementType, + }), + /** + * Whether to maintain the popup in the viewport after the anchor element was scrolled out of view. + * @default false + */ + sticky: PropTypes.bool, + /** + * Styles applied to the Base UI popup element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewSubmenuPopup; diff --git a/packages/mui-material/src/MenuPreviewSubmenuPopup/index.d.ts b/packages/mui-material/src/MenuPreviewSubmenuPopup/index.d.ts new file mode 100644 index 00000000000000..ee1f99c4e671e0 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuPopup/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewSubmenuPopup'; +export * from './MenuPreviewSubmenuPopup'; +export { + menuPreviewSubmenuPopupClasses, + getMenuPreviewSubmenuPopupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewSubmenuPopupClasses, + MenuPreviewSubmenuPopupClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuPopup/index.js b/packages/mui-material/src/MenuPreviewSubmenuPopup/index.js new file mode 100644 index 00000000000000..0941a947811103 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuPopup/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewSubmenuPopup'; +export * from './MenuPreviewSubmenuPopup'; +export { + menuPreviewSubmenuPopupClasses, + getMenuPreviewSubmenuPopupUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuRoot/MenuPreviewSubmenuRoot.tsx b/packages/mui-material/src/MenuPreviewSubmenuRoot/MenuPreviewSubmenuRoot.tsx new file mode 100644 index 00000000000000..11ee567dbc9571 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuRoot/MenuPreviewSubmenuRoot.tsx @@ -0,0 +1,132 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { useDefaultProps } from '../DefaultPropsProvider'; + +export interface MenuPreviewSubmenuRootProps { + /** + * The content of the submenu. + */ + children?: React.ReactNode; + /** + * Whether the submenu is initially open. + * + * To render a controlled submenu, use the `open` prop instead. + * @default false + */ + defaultOpen?: boolean | undefined; + /** + * Whether the submenu is currently open. + */ + open?: boolean | undefined; + /** + * Event handler called when the submenu is opened or closed. + */ + onOpenChange?: BaseMenu.SubmenuRoot.Props['onOpenChange']; + /** + * Event handler called after any animations complete when the submenu is opened or closed. + */ + onOpenChangeComplete?: BaseMenu.SubmenuRoot.Props['onOpenChangeComplete']; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Whether to loop keyboard focus back to the first item. + * @default true + */ + loopFocus?: boolean | undefined; + /** + * Whether moving the pointer over items should highlight them. + * @default true + */ + highlightItemOnHover?: boolean | undefined; + /** + * The visual orientation of the submenu. + * @default 'vertical' + */ + orientation?: 'horizontal' | 'vertical' | undefined; + /** + * When in a submenu, determines whether pressing the Escape key closes the entire menu. + * @default false + */ + closeParentOnEsc?: boolean | undefined; +} + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewSubmenuRoot API](https://mui.com/material-ui/api/menu-preview-submenu-root/) + */ +function MenuPreviewSubmenuRoot(props: MenuPreviewSubmenuRootProps): React.JSX.Element { + const themedProps = useDefaultProps({ + props, + name: 'MuiMenuPreviewSubmenuRoot', + }); + + return ; +} + +MenuPreviewSubmenuRoot.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the submenu. + */ + children: PropTypes.node, + /** + * When in a submenu, determines whether pressing the Escape key closes the entire menu. + * @default false + */ + closeParentOnEsc: PropTypes.bool, + /** + * Whether the submenu is initially open. + * + * To render a controlled submenu, use the `open` prop instead. + * @default false + */ + defaultOpen: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * Whether moving the pointer over items should highlight them. + * @default true + */ + highlightItemOnHover: PropTypes.bool, + /** + * Whether to loop keyboard focus back to the first item. + * @default true + */ + loopFocus: PropTypes.bool, + /** + * Event handler called when the submenu is opened or closed. + */ + onOpenChange: PropTypes.func, + /** + * Event handler called after any animations complete when the submenu is opened or closed. + */ + onOpenChangeComplete: PropTypes.func, + /** + * Whether the submenu is currently open. + */ + open: PropTypes.bool, + /** + * The visual orientation of the submenu. + * @default 'vertical' + */ + orientation: PropTypes.oneOf(['horizontal', 'vertical']), +} as any; + +export default MenuPreviewSubmenuRoot; diff --git a/packages/mui-material/src/MenuPreviewSubmenuRoot/index.d.ts b/packages/mui-material/src/MenuPreviewSubmenuRoot/index.d.ts new file mode 100644 index 00000000000000..df51cc07534051 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuRoot/index.d.ts @@ -0,0 +1,2 @@ +export { default } from './MenuPreviewSubmenuRoot'; +export * from './MenuPreviewSubmenuRoot'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuRoot/index.js b/packages/mui-material/src/MenuPreviewSubmenuRoot/index.js new file mode 100644 index 00000000000000..df51cc07534051 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuRoot/index.js @@ -0,0 +1,2 @@ +export { default } from './MenuPreviewSubmenuRoot'; +export * from './MenuPreviewSubmenuRoot'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuTrigger/MenuPreviewSubmenuTrigger.tsx b/packages/mui-material/src/MenuPreviewSubmenuTrigger/MenuPreviewSubmenuTrigger.tsx new file mode 100644 index 00000000000000..66c56f538bc352 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuTrigger/MenuPreviewSubmenuTrigger.tsx @@ -0,0 +1,274 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import ListContext from '../List/ListContext'; +import { styled } from '../zero-styled'; +import memoTheme from '../utils/memoTheme'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { getMenuPreviewItemStyles } from '../MenuPreview/menuPreviewSharedStyles'; +import { + getMenuPreviewRootRender, + isMenuPreviewRootNativeButton, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewItemClassName, + getMenuPreviewItemOwnerState, + MenuPreviewItemOwnerState, + MenuPreviewItemVisualProps, + MenuPreviewSubmenuTriggerBaseProps, + menuPreviewItemOverridesResolver, + useMenuPreviewItemUtilityClasses, +} from '../MenuPreview/menuPreviewItemShared'; +import { + getMenuPreviewSubmenuTriggerUtilityClass, + menuPreviewSubmenuTriggerClasses, + MenuPreviewSubmenuTriggerClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewSubmenuTriggerSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewSubmenuTriggerSlotProps extends MenuPreviewRootSlotProps {} + +export interface MenuPreviewSubmenuTriggerProps + extends + Omit, + MenuPreviewSubmenuTriggerBaseProps, + MenuPreviewItemVisualProps< + MenuPreviewSubmenuTriggerClasses, + MenuPreviewSubmenuTriggerSlots, + MenuPreviewSubmenuTriggerSlotProps + > { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label?: string | undefined; + /** + * How long to wait before the submenu may be opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 100 + */ + delay?: number | undefined; + /** + * How long to wait before closing the submenu that was opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 0 + */ + closeDelay?: number | undefined; + /** + * Whether the submenu should also open when the trigger is hovered. + */ + openOnHover?: boolean | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; +} + +const MenuPreviewSubmenuTriggerRoot = styled('div', { + name: 'MuiMenuPreviewSubmenuTrigger', + slot: 'Root', + overridesResolver: menuPreviewItemOverridesResolver, +})<{ ownerState: MenuPreviewItemOwnerState }>( + memoTheme(({ theme }) => getMenuPreviewItemStyles(theme, menuPreviewSubmenuTriggerClasses)), +); + +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewSubmenuTrigger API](https://mui.com/material-ui/api/menu-preview-submenu-trigger/) + */ +const MenuPreviewSubmenuTrigger = React.forwardRef(function MenuPreviewSubmenuTrigger( + inProps: MenuPreviewSubmenuTriggerProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewSubmenuTrigger', + }); + + const { + className, + classes: classesProp, + component, + dense = false, + disabled = false, + disableGutters = false, + divider = false, + nativeButton: nativeButtonProp, + selected = false, + slotProps, + slots, + sx, + style, + ...other + } = props; + const ownerState = { + ...props, + ...getMenuPreviewItemOwnerState({ dense, disabled, disableGutters, divider, selected }), + classes: classesProp, + }; + const classes = useMenuPreviewItemUtilityClasses( + ownerState, + getMenuPreviewSubmenuTriggerUtilityClass, + ); + const childContext = React.useMemo( + () => ({ + dense, + disableGutters, + }), + [dense, disableGutters], + ); + const RootSlot = slots?.root ?? MenuPreviewSubmenuTriggerRoot; + + return ( + + + clsx( + className, + getMenuPreviewItemClassName(classes, ownerState, state), + state.open && classes.open, + ) + } + disabled={disabled} + nativeButton={nativeButtonProp ?? isMenuPreviewRootNativeButton(RootSlot, component)} + style={style} + {...other} + /> + + ); +}); + +MenuPreviewSubmenuTrigger.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * How long to wait before closing the submenu that was opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 0 + */ + closeDelay: PropTypes.number, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * How long to wait before the submenu may be opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 100 + */ + delay: PropTypes.number, + /** + * If `true`, compact vertical padding designed for keyboard and mouse input is used. + * @default false + */ + dense: PropTypes.bool, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * If `true`, the left and right padding is removed. + * @default false + */ + disableGutters: PropTypes.bool, + /** + * If `true`, a 1px light border is added to the bottom of the menu item. + * @default false + */ + divider: PropTypes.bool, + /** + * Overrides the text label to use when the item is matched during keyboard text navigation. + */ + label: PropTypes.string, + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton: PropTypes.bool, + /** + * Whether the submenu should also open when the trigger is hovered. + */ + openOnHover: PropTypes.bool, + /** + * If `true`, the component is selected. + * @default false + */ + selected: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewSubmenuTrigger; diff --git a/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.d.ts b/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.d.ts new file mode 100644 index 00000000000000..c635b6f0a1818e --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewSubmenuTrigger'; +export * from './MenuPreviewSubmenuTrigger'; +export { + menuPreviewSubmenuTriggerClasses, + getMenuPreviewSubmenuTriggerUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewSubmenuTriggerClasses, + MenuPreviewSubmenuTriggerClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.js b/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.js new file mode 100644 index 00000000000000..21cf2aafd9dc18 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewSubmenuTrigger/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewSubmenuTrigger'; +export * from './MenuPreviewSubmenuTrigger'; +export { + menuPreviewSubmenuTriggerClasses, + getMenuPreviewSubmenuTriggerUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewTrigger/MenuPreviewTrigger.tsx b/packages/mui-material/src/MenuPreviewTrigger/MenuPreviewTrigger.tsx new file mode 100644 index 00000000000000..309c8a58b37bb6 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewTrigger/MenuPreviewTrigger.tsx @@ -0,0 +1,270 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import composeClasses from '@mui/utils/composeClasses'; +import { Menu as BaseMenu } from '@base-ui/react/menu'; +import { SxProps } from '@mui/system'; +import Button, { ButtonProps } from '../Button'; +import { Theme } from '../styles'; +import { styled } from '../zero-styled'; +import { useDefaultProps } from '../DefaultPropsProvider'; +import { + getMenuPreviewRootRender, + isMenuPreviewRootNativeButton, + MenuPreviewRootSlotProps, + MenuPreviewRootSlots, + resolveSlotProps, +} from '../MenuPreview/menuPreviewUtils'; +import { + getMenuPreviewTriggerUtilityClass, + MenuPreviewTriggerClasses, +} from '../MenuPreview/menuPreviewClasses'; + +export interface MenuPreviewTriggerSlots extends MenuPreviewRootSlots {} + +export interface MenuPreviewTriggerProps + extends + Omit< + BaseMenu.Trigger.Props, + 'className' | 'handle' | 'nativeButton' | 'payload' | 'render' | 'style' + >, + Omit< + ButtonProps, + keyof BaseMenu.Trigger.Props | 'classes' | 'component' | 'disabled' | 'href' | 'style' + > { + /** + * The component used for the root node. + */ + component?: React.ElementType | undefined; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial | undefined; + /** + * CSS class applied to the element. + */ + className?: string | undefined; + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled?: boolean | undefined; + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton?: boolean | undefined; + /** + * How long to wait before the menu may be opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 100 + */ + delay?: number | undefined; + /** + * How long to wait before closing the menu that was opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 0 + */ + closeDelay?: number | undefined; + /** + * Whether the menu should also open when the trigger is hovered. + */ + openOnHover?: boolean | undefined; + /** + * The components used for each slot inside. + */ + slots?: MenuPreviewTriggerSlots | undefined; + /** + * The props used for each slot inside. + */ + slotProps?: MenuPreviewTriggerSlotProps | undefined; + /** + * Styles applied to the root element. + */ + style?: React.CSSProperties | undefined; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps | undefined; +} + +interface MenuPreviewTriggerOwnerState extends MenuPreviewTriggerProps { + disabled: boolean; +} + +export interface MenuPreviewTriggerSlotProps extends MenuPreviewRootSlotProps {} + +const useUtilityClasses = (ownerState: MenuPreviewTriggerOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + disabled: ['disabled'], + open: ['open'], + }; + + return { + ...classes, + ...composeClasses(slots, getMenuPreviewTriggerUtilityClass, classes), + }; +}; + +const MenuPreviewTriggerRoot = styled(Button, { + name: 'MuiMenuPreviewTrigger', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}) as any; + +const BaseMenuTrigger = BaseMenu.Trigger as any; +/** + * + * Demos: + * + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [MenuPreviewTrigger API](https://mui.com/material-ui/api/menu-preview-trigger/) + */ +const MenuPreviewTrigger = React.forwardRef(function MenuPreviewTrigger( + inProps: MenuPreviewTriggerProps, + ref: React.ForwardedRef, +) { + const props = useDefaultProps({ + props: inProps, + name: 'MuiMenuPreviewTrigger', + }); + + const { href: ignoredHref, ...propsWithoutHref } = props as MenuPreviewTriggerProps & { + href?: unknown; + }; + void ignoredHref; + + const { + className, + classes: classesProp, + component, + disabled = false, + nativeButton: nativeButtonProp, + slotProps, + slots, + sx, + style, + ...other + } = propsWithoutHref; + const ownerState = { + ...propsWithoutHref, + classes: classesProp, + disabled, + }; + const classes = useUtilityClasses(ownerState); + const RootSlot = slots?.root ?? MenuPreviewTriggerRoot; + + return ( + + clsx( + className, + classes.root, + state.open && classes.open, + state.disabled && classes.disabled, + ) + } + nativeButton={nativeButtonProp ?? isMenuPreviewRootNativeButton(RootSlot, component, true)} + style={style} + {...other} + /> + ); +}) as ((props: MenuPreviewTriggerProps & React.RefAttributes) => React.JSX.Element) & { + propTypes?: any; +}; + +MenuPreviewTrigger.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * CSS class applied to the element. + */ + className: PropTypes.string, + /** + * How long to wait before closing the menu that was opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 0 + */ + closeDelay: PropTypes.number, + /** + * The component used for the root node. + */ + component: PropTypes.elementType, + /** + * How long to wait before the menu may be opened on hover, in milliseconds. + * + * Requires the `openOnHover` prop. + * @default 100 + */ + delay: PropTypes.number, + /** + * Whether the component should ignore user interaction. + * @default false + */ + disabled: PropTypes.bool, + /** + * Whether the component is rendered as a native button. + * + * By default, this is inferred from the root slot and `component` prop. + */ + nativeButton: PropTypes.bool, + /** + * Whether the menu should also open when the trigger is hovered. + */ + openOnHover: PropTypes.bool, + /** + * The props used for each slot inside. + */ + slotProps: PropTypes.shape({ + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + */ + slots: PropTypes.shape({ + root: PropTypes.elementType, + }), + /** + * Styles applied to the root element. + */ + style: PropTypes.object, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), +} as any; + +export default MenuPreviewTrigger; diff --git a/packages/mui-material/src/MenuPreviewTrigger/index.d.ts b/packages/mui-material/src/MenuPreviewTrigger/index.d.ts new file mode 100644 index 00000000000000..ea8d0c4a9ab413 --- /dev/null +++ b/packages/mui-material/src/MenuPreviewTrigger/index.d.ts @@ -0,0 +1,10 @@ +export { default } from './MenuPreviewTrigger'; +export * from './MenuPreviewTrigger'; +export { + menuPreviewTriggerClasses, + getMenuPreviewTriggerUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; +export type { + MenuPreviewTriggerClasses, + MenuPreviewTriggerClassKey, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/MenuPreviewTrigger/index.js b/packages/mui-material/src/MenuPreviewTrigger/index.js new file mode 100644 index 00000000000000..0b40dbfdcb13dd --- /dev/null +++ b/packages/mui-material/src/MenuPreviewTrigger/index.js @@ -0,0 +1,6 @@ +export { default } from './MenuPreviewTrigger'; +export * from './MenuPreviewTrigger'; +export { + menuPreviewTriggerClasses, + getMenuPreviewTriggerUtilityClass, +} from '../MenuPreview/menuPreviewClasses'; diff --git a/packages/mui-material/src/styles/components.ts b/packages/mui-material/src/styles/components.ts index 3404eaee7c94f0..3e859ef603969e 100644 --- a/packages/mui-material/src/styles/components.ts +++ b/packages/mui-material/src/styles/components.ts @@ -477,6 +477,116 @@ export interface Components { variants?: ComponentsVariants['MuiMenuList'] | undefined; } | undefined; + MuiMenuPreview?: + | { + defaultProps?: ComponentsProps['MuiMenuPreview'] | undefined; + } + | undefined; + MuiMenuPreviewCheckboxItem?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewCheckboxItem'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewCheckboxItem'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewCheckboxItem'] | undefined; + } + | undefined; + MuiMenuPreviewCheckboxItemIndicator?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewCheckboxItemIndicator'] | undefined; + styleOverrides?: + | ComponentsOverrides['MuiMenuPreviewCheckboxItemIndicator'] + | undefined; + variants?: ComponentsVariants['MuiMenuPreviewCheckboxItemIndicator'] | undefined; + } + | undefined; + MuiMenuPreviewGroup?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewGroup'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewGroup'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewGroup'] | undefined; + } + | undefined; + MuiMenuPreviewGroupLabel?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewGroupLabel'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewGroupLabel'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewGroupLabel'] | undefined; + } + | undefined; + MuiMenuPreviewItem?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewItem'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewItem'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewItem'] | undefined; + } + | undefined; + MuiMenuPreviewLinkItem?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewLinkItem'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewLinkItem'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewLinkItem'] | undefined; + } + | undefined; + MuiMenuPreviewPopup?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewPopup'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewPopup'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewPopup'] | undefined; + } + | undefined; + MuiMenuPreviewRadioGroup?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewRadioGroup'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewRadioGroup'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewRadioGroup'] | undefined; + } + | undefined; + MuiMenuPreviewRadioItem?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewRadioItem'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewRadioItem'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewRadioItem'] | undefined; + } + | undefined; + MuiMenuPreviewRadioItemIndicator?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewRadioItemIndicator'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewRadioItemIndicator'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewRadioItemIndicator'] | undefined; + } + | undefined; + MuiMenuPreviewSeparator?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewSeparator'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewSeparator'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewSeparator'] | undefined; + } + | undefined; + MuiMenuPreviewSubmenuPopup?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewSubmenuPopup'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewSubmenuPopup'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewSubmenuPopup'] | undefined; + } + | undefined; + MuiMenuPreviewSubmenuRoot?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewSubmenuRoot'] | undefined; + } + | undefined; + MuiMenuPreviewSubmenuTrigger?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewSubmenuTrigger'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewSubmenuTrigger'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewSubmenuTrigger'] | undefined; + } + | undefined; + MuiMenuPreviewTrigger?: + | { + defaultProps?: ComponentsProps['MuiMenuPreviewTrigger'] | undefined; + styleOverrides?: ComponentsOverrides['MuiMenuPreviewTrigger'] | undefined; + variants?: ComponentsVariants['MuiMenuPreviewTrigger'] | undefined; + } + | undefined; MuiMobileStepper?: | { defaultProps?: ComponentsProps['MuiMobileStepper'] | undefined; diff --git a/packages/mui-material/src/styles/overrides.ts b/packages/mui-material/src/styles/overrides.ts index fa9e23fd15ab1a..0006aaddee54a5 100644 --- a/packages/mui-material/src/styles/overrides.ts +++ b/packages/mui-material/src/styles/overrides.ts @@ -67,6 +67,22 @@ import { ListSubheaderClassKey } from '../ListSubheader'; import { MenuClassKey } from '../Menu'; import { MenuItemClassKey } from '../MenuItem'; import { MenuListClassKey } from '../MenuList'; +import { + MenuPreviewCheckboxItemClassKey, + MenuPreviewCheckboxItemIndicatorClassKey, + MenuPreviewGroupClassKey, + MenuPreviewGroupLabelClassKey, + MenuPreviewItemClassKey, + MenuPreviewLinkItemClassKey, + MenuPreviewPopupClassKey, + MenuPreviewRadioGroupClassKey, + MenuPreviewRadioItemClassKey, + MenuPreviewRadioItemIndicatorClassKey, + MenuPreviewSeparatorClassKey, + MenuPreviewSubmenuPopupClassKey, + MenuPreviewSubmenuTriggerClassKey, + MenuPreviewTriggerClassKey, +} from '../MenuPreview'; import { MobileStepperClassKey } from '../MobileStepper'; import { ModalClassKey } from '../Modal'; import { NativeSelectClassKey } from '../NativeSelect'; @@ -211,6 +227,20 @@ export interface ComponentNameToClassKey { MuiMenu: MenuClassKey; MuiMenuItem: MenuItemClassKey; MuiMenuList: MenuListClassKey; + MuiMenuPreviewCheckboxItem: MenuPreviewCheckboxItemClassKey; + MuiMenuPreviewCheckboxItemIndicator: MenuPreviewCheckboxItemIndicatorClassKey; + MuiMenuPreviewGroup: MenuPreviewGroupClassKey; + MuiMenuPreviewGroupLabel: MenuPreviewGroupLabelClassKey; + MuiMenuPreviewItem: MenuPreviewItemClassKey; + MuiMenuPreviewLinkItem: MenuPreviewLinkItemClassKey; + MuiMenuPreviewPopup: MenuPreviewPopupClassKey; + MuiMenuPreviewRadioGroup: MenuPreviewRadioGroupClassKey; + MuiMenuPreviewRadioItem: MenuPreviewRadioItemClassKey; + MuiMenuPreviewRadioItemIndicator: MenuPreviewRadioItemIndicatorClassKey; + MuiMenuPreviewSeparator: MenuPreviewSeparatorClassKey; + MuiMenuPreviewSubmenuPopup: MenuPreviewSubmenuPopupClassKey; + MuiMenuPreviewSubmenuTrigger: MenuPreviewSubmenuTriggerClassKey; + MuiMenuPreviewTrigger: MenuPreviewTriggerClassKey; MuiMobileStepper: MobileStepperClassKey; MuiModal: ModalClassKey; MuiNativeSelect: NativeSelectClassKey; diff --git a/packages/mui-material/src/styles/props.ts b/packages/mui-material/src/styles/props.ts index 35d8b8783307af..c60edf2923a04f 100644 --- a/packages/mui-material/src/styles/props.ts +++ b/packages/mui-material/src/styles/props.ts @@ -64,6 +64,22 @@ import { ListProps } from '../List'; import { ListSubheaderProps } from '../ListSubheader'; import { MenuItemProps } from '../MenuItem'; import { MenuListProps } from '../MenuList'; +import { MenuPreviewProps } from '../MenuPreview'; +import { MenuPreviewCheckboxItemProps } from '../MenuPreviewCheckboxItem'; +import { MenuPreviewCheckboxItemIndicatorProps } from '../MenuPreviewCheckboxItemIndicator'; +import { MenuPreviewGroupProps } from '../MenuPreviewGroup'; +import { MenuPreviewGroupLabelProps } from '../MenuPreviewGroupLabel'; +import { MenuPreviewItemProps } from '../MenuPreviewItem'; +import { MenuPreviewLinkItemProps } from '../MenuPreviewLinkItem'; +import { MenuPreviewPopupProps } from '../MenuPreviewPopup'; +import { MenuPreviewRadioGroupProps } from '../MenuPreviewRadioGroup'; +import { MenuPreviewRadioItemProps } from '../MenuPreviewRadioItem'; +import { MenuPreviewRadioItemIndicatorProps } from '../MenuPreviewRadioItemIndicator'; +import { MenuPreviewSeparatorProps } from '../MenuPreviewSeparator'; +import { MenuPreviewSubmenuPopupProps } from '../MenuPreviewSubmenuPopup'; +import { MenuPreviewSubmenuRootProps } from '../MenuPreviewSubmenuRoot'; +import { MenuPreviewSubmenuTriggerProps } from '../MenuPreviewSubmenuTrigger'; +import { MenuPreviewTriggerProps } from '../MenuPreviewTrigger'; import { MenuProps } from '../Menu'; import { MobileStepperProps } from '../MobileStepper'; import { ModalProps } from '../Modal'; @@ -189,6 +205,22 @@ export interface ComponentsPropsList { MuiMenu: MenuProps; MuiMenuItem: MenuItemProps; MuiMenuList: MenuListProps; + MuiMenuPreview: MenuPreviewProps; + MuiMenuPreviewCheckboxItem: MenuPreviewCheckboxItemProps; + MuiMenuPreviewCheckboxItemIndicator: MenuPreviewCheckboxItemIndicatorProps; + MuiMenuPreviewGroup: MenuPreviewGroupProps; + MuiMenuPreviewGroupLabel: MenuPreviewGroupLabelProps; + MuiMenuPreviewItem: MenuPreviewItemProps; + MuiMenuPreviewLinkItem: MenuPreviewLinkItemProps; + MuiMenuPreviewPopup: MenuPreviewPopupProps; + MuiMenuPreviewRadioGroup: MenuPreviewRadioGroupProps; + MuiMenuPreviewRadioItem: MenuPreviewRadioItemProps; + MuiMenuPreviewRadioItemIndicator: MenuPreviewRadioItemIndicatorProps; + MuiMenuPreviewSeparator: MenuPreviewSeparatorProps; + MuiMenuPreviewSubmenuPopup: MenuPreviewSubmenuPopupProps; + MuiMenuPreviewSubmenuRoot: MenuPreviewSubmenuRootProps; + MuiMenuPreviewSubmenuTrigger: MenuPreviewSubmenuTriggerProps; + MuiMenuPreviewTrigger: MenuPreviewTriggerProps; MuiMobileStepper: MobileStepperProps; MuiModal: ModalProps; MuiNativeSelect: NativeSelectProps; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fa95f059d5130..8f65a4d453db24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: docs: '@base-ui/react': - specifier: ^1.4.1 - version: 1.4.1 + specifier: ^1.5.0 + version: 1.5.0 '@docsearch/react': specifier: ^3.9.0 version: 3.9.0 @@ -262,7 +262,7 @@ importers: version: 7.29.7 '@base-ui/react': specifier: catalog:docs - version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docsearch/react': specifier: ^3.9.0 version: 3.9.0(@algolia/client-search@5.18.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.13.0) @@ -703,8 +703,8 @@ importers: specifier: ^7.29.2 version: 7.29.7 '@base-ui/react': - specifier: ^1 - version: 1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + specifier: ^1.5.0 + version: 1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@docsearch/react': specifier: catalog:docs version: 3.9.0(@algolia/client-search@5.18.0)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(search-insights@2.13.0) @@ -1120,6 +1120,9 @@ importers: '@babel/runtime': specifier: ^7.29.2 version: 7.29.7 + '@base-ui/react': + specifier: ^1.5.0 + version: 1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@emotion/react': specifier: ^11.5.0 version: 11.14.0(@types/react@19.2.14)(react@19.2.6) @@ -2406,8 +2409,8 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.4.1': - resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} + '@base-ui/react@1.5.0': + resolution: {integrity: sha512-z1gSAlced1yY+iM+mHDEtIkD8UI3Ebs52MuBPxvV6f5hRutk+xvCH/wuB7hDqDzK9JG5FoMz5nhrqtSs1wjt1A==} engines: {node: '>=14.0.0'} peerDependencies: '@date-fns/tz': ^1.2.0 @@ -2423,16 +2426,6 @@ packages: date-fns: optional: true - '@base-ui/utils@0.2.8': - resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@base-ui/utils@0.2.9': resolution: {integrity: sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw==} peerDependencies: @@ -4376,10 +4369,10 @@ packages: resolution: {integrity: sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==} engines: {node: ^20.17.0 || >=22.9.0} - '@nx/devkit@22.7.5': - resolution: {integrity: sha512-/63ziS7kdHXYTLLhwWBu9hFwoFFT8xf+PkcQjsNdPqc5JmkYkSew0cE/vp5ORgBpGLWWnFPJgmfqjbJoO2C7jA==} + '@nx/devkit@21.6.8': + resolution: {integrity: sha512-N0cj0NqdxY2pcI0IJV+fAu362B6tppdv2ohSBNGacNeSqxfAlJxO5TFZePDmxX5nt0t9hAqT+iasfu4BSYGfZw==} peerDependencies: - nx: '>= 21 <= 23 || ^22.0.0-0' + nx: '>= 20 <= 22' '@nx/nx-darwin-arm64@22.7.5': resolution: {integrity: sha512-eoPtwx0qZqvRUD+VVOHm150AlSYwYoPxkDHBBGqKCn5nzPspb0lLWw8q83crM/L1M928YgK0WmGf3C++7eqsTA==} @@ -5931,6 +5924,9 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-flatten@3.0.0: + resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} + array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} @@ -6001,6 +5997,9 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -6119,8 +6118,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + body-parser@2.0.2: + resolution: {integrity: sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==} engines: {node: '>=18'} boolbase@1.0.0: @@ -6486,10 +6485,6 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} - conventional-changelog-angular@7.0.0: resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} engines: {node: '>=16'} @@ -6535,8 +6530,8 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} cookie@1.1.1: @@ -6748,6 +6743,14 @@ packages: supports-color: optional: true + debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -6756,6 +6759,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -6840,6 +6852,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -6913,6 +6929,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + ejs@5.0.1: resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} engines: {node: '>=0.12.18'} @@ -6937,6 +6958,10 @@ packages: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -7296,8 +7321,8 @@ packages: exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + express@5.0.1: + resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} engines: {node: '>= 18'} extend@3.0.2: @@ -7384,6 +7409,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -7392,9 +7420,9 @@ packages: resolution: {integrity: sha512-Ohygw2lDgc2HNynfUu82Jp5U45+OLLeBQcwWbrex1IAbQw0uNaFMUbm5dhYCF6y7jcORgl5tg19/1zYxlJtLmg==} engines: {node: '>=18'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} + finalhandler@2.0.0: + resolution: {integrity: sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==} + engines: {node: '>= 0.8'} find-babel-config@2.1.1: resolution: {integrity: sha512-5Ji+EAysHGe1OipH7GN4qDjok5Z1uw5KAwDCbicU/4wyTZY7CqOCzcWbG7J5ad9mazq67k89fXlbc1MuIfl9uA==} @@ -7485,6 +7513,10 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -7628,8 +7660,8 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true @@ -7841,8 +7873,8 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} http-proxy-agent@7.0.2: @@ -7868,6 +7900,10 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + iconv-lite@0.5.2: + resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} + engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -8271,6 +8307,11 @@ packages: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} + jake@10.9.2: + resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} + engines: {node: '>=10'} + hasBin: true + java-properties@1.0.2: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} @@ -8836,6 +8877,10 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -8859,6 +8904,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -9021,8 +9070,16 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@8.0.7: - resolution: {integrity: sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} minimatch@9.0.9: @@ -9087,6 +9144,9 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -9669,8 +9729,9 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-to-regexp@8.1.0: + resolution: {integrity: sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==} + engines: {node: '>=16'} path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -9940,8 +10001,8 @@ packages: resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} engines: {node: '>=20'} - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -9962,9 +10023,9 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} raw-loader@4.0.2: resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==} @@ -10362,9 +10423,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} + router@2.0.0: + resolution: {integrity: sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==} + engines: {node: '>= 0.10'} rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -10457,15 +10518,15 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} serve-handler@6.1.7: resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + serve-static@2.1.0: + resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} engines: {node: '>= 18'} serve@14.2.6: @@ -10666,6 +10727,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -11144,9 +11209,13 @@ packages: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.0: + resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} + engines: {node: '>= 0.6'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -11328,6 +11397,10 @@ packages: util@0.10.4: resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + v8flags@3.2.0: resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} engines: {node: '>= 0.10'} @@ -12762,10 +12835,10 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@base-ui/react@1.4.1(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@base-ui/react@1.5.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@base-ui/utils': 0.2.9(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@floating-ui/utils': 0.2.11 react: 19.2.6 @@ -12774,17 +12847,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': - dependencies: - '@babel/runtime': 7.29.7 - '@floating-ui/utils': 0.2.11 - react: 19.2.6 - react-dom: 19.2.6(react@19.2.6) - reselect: 5.2.0 - use-sync-external-store: 1.6.0(react@19.2.6) - optionalDependencies: - '@types/react': 19.2.14 - '@base-ui/utils@0.2.9(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.7 @@ -14692,7 +14754,7 @@ snapshots: '@npmcli/package-json@7.0.2': dependencies: '@npmcli/git': 7.0.1 - glob: 11.1.0 + glob: 11.0.3 hosted-git-info: 9.0.2 json-parse-even-better-errors: 5.0.0 proc-log: 6.0.0 @@ -14728,12 +14790,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@nx/devkit@22.7.5(nx@22.7.5)': + '@nx/devkit@21.6.8(nx@22.7.5)': dependencies: - '@zkochan/js-yaml': 0.0.7 - ejs: 5.0.1 + ejs: 3.1.10 enquirer: 2.3.6 - minimatch: 10.2.5 + ignore: 5.3.2 + minimatch: 9.0.3 nx: 22.7.5 semver: 7.8.2 tslib: 2.8.1 @@ -15198,9 +15260,9 @@ snapshots: '@slack/web-api': 7.15.1 '@types/express': 5.0.0 axios: 1.16.0 - express: 5.2.1 - path-to-regexp: 8.4.2 - raw-body: 3.0.2 + express: 5.0.1 + path-to-regexp: 8.1.0 + raw-body: 3.0.0 tsscmp: 1.0.6 transitivePeerDependencies: - bufferutil @@ -16252,6 +16314,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-flatten@3.0.0: {} + array-ify@1.0.0: {} array-includes@3.1.9: @@ -16354,6 +16418,8 @@ snapshots: astral-regex@2.0.0: {} + async@3.2.6: {} + asynckit@0.4.0: {} autosuggest-highlight@3.3.4: @@ -16487,17 +16553,18 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@2.2.2: + body-parser@2.0.2: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.0 + debug: 3.1.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.5.2 on-finished: 2.4.1 - qs: 6.15.2 - raw-body: 3.0.2 - type-is: 2.1.0 + qs: 6.13.0 + raw-body: 3.0.0 + type-is: 1.6.18 transitivePeerDependencies: - supports-color @@ -16568,7 +16635,7 @@ snapshots: dependencies: '@npmcli/fs': 4.0.0 fs-minipass: 3.0.3 - glob: 11.1.0 + glob: 11.0.3 lru-cache: 11.5.1 minipass: 7.1.3 minipass-collect: 2.0.1 @@ -16857,8 +16924,6 @@ snapshots: content-type@1.0.5: {} - content-type@2.0.0: {} - conventional-changelog-angular@7.0.0: dependencies: compare-func: 2.0.0 @@ -16924,7 +16989,7 @@ snapshots: cookie-signature@1.2.2: {} - cookie@0.7.2: {} + cookie@0.7.1: {} cookie@1.1.1: {} @@ -17143,10 +17208,18 @@ snapshots: dependencies: ms: 2.0.0 + debug@3.1.0: + dependencies: + ms: 2.0.0 + debug@3.2.7: dependencies: ms: 2.1.3 + debug@4.3.6: + dependencies: + ms: 2.1.2 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -17209,6 +17282,8 @@ snapshots: dequal@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} devlop@1.1.0: @@ -17282,6 +17357,10 @@ snapshots: ee-first@1.1.1: {} + ejs@3.1.10: + dependencies: + jake: 10.9.2 + ejs@5.0.1: {} electron-to-chromium@1.5.368: {} @@ -17296,6 +17375,8 @@ snapshots: emojis-list@3.0.0: {} + encodeurl@1.0.2: {} + encodeurl@2.0.0: {} encoding@0.1.13: @@ -17860,35 +17941,39 @@ snapshots: exponential-backoff@3.1.2: {} - express@5.2.1: + express@5.0.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.2 + body-parser: 2.0.2 content-disposition: 1.0.0 content-type: 1.0.5 - cookie: 0.7.2 + cookie: 0.7.1 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.3.6 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.0.0 fresh: 2.0.0 - http-errors: 2.0.1 + http-errors: 2.0.0 merge-descriptors: 2.0.0 + methods: 1.1.2 mime-types: 3.0.2 on-finished: 2.4.1 once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.2 + qs: 6.13.0 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.1.0 + router: 2.0.0 + safe-buffer: 5.2.1 + send: 1.1.0 + serve-static: 2.1.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 2.0.0 + utils-merge: 1.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -17974,6 +18059,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + filelist@1.0.4: + dependencies: + minimatch: 5.1.9 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -17982,14 +18071,15 @@ snapshots: dependencies: '@babel/runtime': 7.29.7 - finalhandler@2.1.1: + finalhandler@2.0.0: dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 + debug: 2.6.9 + encodeurl: 1.0.2 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.2 + statuses: 2.0.1 + unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -18078,6 +18168,8 @@ snapshots: forwarded@0.2.0: {} + fresh@0.5.2: {} + fresh@2.0.0: {} fs-constants@1.0.0: {} @@ -18230,7 +18322,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.1.0: + glob@11.0.3: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 @@ -18257,7 +18349,7 @@ snapshots: glob@9.3.5: dependencies: fs.realpath: 1.0.0 - minimatch: 8.0.7 + minimatch: 8.0.4 minipass: 4.2.8 path-scurry: 1.11.1 @@ -18462,12 +18554,12 @@ snapshots: http-cache-semantics@4.1.1: {} - http-errors@2.0.1: + http-errors@2.0.0: dependencies: depd: 2.0.0 inherits: 2.0.4 setprototypeof: 1.2.0 - statuses: 2.0.2 + statuses: 2.0.1 toidentifier: 1.0.1 http-proxy-agent@7.0.2: @@ -18492,6 +18584,10 @@ snapshots: hyphenate-style-name@1.1.0: {} + iconv-lite@0.5.2: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -18832,6 +18928,13 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 + jake@10.9.2: + dependencies: + async: 3.2.6 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.5 + java-properties@1.0.2: {} jest-diff@30.2.0: @@ -19080,7 +19183,7 @@ snapshots: '@npmcli/arborist': 9.1.6 '@npmcli/package-json': 7.0.2 '@npmcli/run-script': 10.0.3 - '@nx/devkit': 22.7.5(nx@22.7.5) + '@nx/devkit': 21.6.8(nx@22.7.5) '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 20.1.2 aproba: 2.0.0 @@ -19606,6 +19709,8 @@ snapshots: mdn-data@2.27.1: {} + media-typer@0.3.0: {} + media-typer@1.1.0: {} meow@14.1.0: {} @@ -19630,6 +19735,8 @@ snapshots: merge2@1.4.1: {} + methods@1.1.2: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.0.2 @@ -19946,7 +20053,15 @@ snapshots: dependencies: brace-expansion: 1.1.15 - minimatch@8.0.7: + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + + minimatch@8.0.4: + dependencies: + brace-expansion: 2.1.1 + + minimatch@9.0.3: dependencies: brace-expansion: 2.1.1 @@ -20006,6 +20121,8 @@ snapshots: ms@2.0.0: {} + ms@2.1.2: {} + ms@2.1.3: {} multipipe@1.0.2: @@ -20787,7 +20904,7 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.4.2: {} + path-to-regexp@8.1.0: {} path-type@3.0.0: dependencies: @@ -21019,7 +21136,7 @@ snapshots: dependencies: hookified: 1.15.0 - qs@6.15.2: + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -21033,11 +21150,11 @@ snapshots: range-parser@1.2.1: {} - raw-body@3.0.2: + raw-body@3.0.0: dependencies: bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 unpipe: 1.0.0 raw-loader@4.0.2(webpack@5.107.1(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): @@ -21649,15 +21766,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.61.1 fsevents: 2.3.3 - router@2.2.0: + router@2.0.0: dependencies: - debug: 4.4.3 - depd: 2.0.0 + array-flatten: 3.0.0 is-promise: 4.0.0 + methods: 1.1.2 parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color + path-to-regexp: 8.1.0 + setprototypeof: 1.2.0 + utils-merge: 1.0.1 rrweb-cssom@0.8.0: {} @@ -21742,15 +21859,16 @@ snapshots: semver@7.8.2: {} - send@1.2.1: + send@1.1.0: dependencies: debug: 4.4.3 + destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 + fresh: 0.5.2 + http-errors: 2.0.0 + mime-types: 2.1.35 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 @@ -21768,12 +21886,12 @@ snapshots: path-to-regexp: 3.3.0 range-parser: 1.2.0 - serve-static@2.2.1: + serve-static@2.1.0: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.1.0 transitivePeerDependencies: - supports-color @@ -22026,6 +22144,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: {} + statuses@2.0.2: {} std-env@4.1.0: {} @@ -22492,9 +22612,14 @@ snapshots: type-fest@3.13.1: {} - type-is@2.1.0: + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.0: dependencies: - content-type: 2.0.0 + content-type: 1.0.5 media-typer: 1.1.0 mime-types: 3.0.2 @@ -22745,6 +22870,8 @@ snapshots: dependencies: inherits: 2.0.3 + utils-merge@1.0.1: {} + v8flags@3.2.0: dependencies: homedir-polyfill: 1.0.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5a75ac158c88c1..87e101b9bfa6c5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -27,7 +27,7 @@ trustPolicyIgnoreAfter: 525600 catalogs: docs: stylis: '4.2.0' - '@base-ui/react': '^1.4.1' + '@base-ui/react': '^1.5.0' '@docsearch/react': '^3.9.0' '@emotion/cache': '^11.14.0' '@emotion/react': '^11.14.0'