@commercetools/nimbus-mcp 2.10.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/docs/route-manifest.json +288 -0
- package/data/docs/routes/components-data-display-card.json +71 -5
- package/data/docs/routes/components-feedback-toast.json +1 -1
- package/data/docs/routes/components-layout-defaultpage.json +4 -4
- package/data/docs/routes/components-layout-modalpage.json +4 -4
- package/data/docs/routes/components-layout-scrollarea.json +428 -0
- package/data/docs/routes/components-media-avatar.json +24 -2
- package/data/docs/routes/patterns-actions-form-action-bar.json +412 -0
- package/data/docs/routes/patterns-dialogs-info-dialog.json +315 -0
- package/data/docs/routes/patterns-dialogs.json +78 -0
- package/data/docs/search-index.json +1 -1
- package/data/docs/types/AccordionHeader.json +33 -33
- package/data/docs/types/AccordionRoot.json +2 -2
- package/data/docs/types/AlertDescription.json +8 -8
- package/data/docs/types/AlertDismissButton.json +52 -52
- package/data/docs/types/AlertTitle.json +8 -8
- package/data/docs/types/Avatar.json +8 -8
- package/data/docs/types/Badge.json +2 -2
- package/data/docs/types/Body.json +6 -6
- package/data/docs/types/Box.json +6 -6
- package/data/docs/types/Button.json +24 -24
- package/data/docs/types/Caption.json +6 -6
- package/data/docs/types/Card.json +1 -1
- package/data/docs/types/{CardContent.json → CardBody.json} +2 -2
- package/data/docs/types/CardFooter.json +27 -0
- package/data/docs/types/CardRoot.json +18 -48
- package/data/docs/types/Cell.json +6 -6
- package/data/docs/types/Checkbox.json +2 -2
- package/data/docs/types/Code.json +10 -10
- package/data/docs/types/Column.json +6 -6
- package/data/docs/types/ColumnGroup.json +6 -6
- package/data/docs/types/ColumnHeader.json +6 -6
- package/data/docs/types/ComboBoxListBox.json +6 -6
- package/data/docs/types/ComboBoxPopover.json +8 -8
- package/data/docs/types/ComboBoxRoot.json +24 -24
- package/data/docs/types/ComboBoxSection.json +6 -6
- package/data/docs/types/ComboBoxTrigger.json +6 -6
- package/data/docs/types/DataTable.json +2 -2
- package/data/docs/types/DataTableBody.json +6 -6
- package/data/docs/types/DataTableHeader.json +7 -7
- package/data/docs/types/DataTableRoot.json +2 -2
- package/data/docs/types/DataTableTable.json +6 -6
- package/data/docs/types/DatePicker.json +16 -16
- package/data/docs/types/DateRangePickerField.json +17 -17
- package/data/docs/types/DefaultPageRoot.json +2 -2
- package/data/docs/types/DialogCloseTrigger.json +52 -52
- package/data/docs/types/DraggableListField.json +10 -10
- package/data/docs/types/DraggableListItem.json +6 -6
- package/data/docs/types/DraggableListRoot.json +8 -8
- package/data/docs/types/DrawerCloseTrigger.json +52 -52
- package/data/docs/types/FieldErrors.json +2 -2
- package/data/docs/types/Flex.json +22 -22
- package/data/docs/types/Footer.json +6 -6
- package/data/docs/types/FormActionBar.json +200 -0
- package/data/docs/types/FormFieldRoot.json +2 -2
- package/data/docs/types/Grid.json +24 -24
- package/data/docs/types/Group.json +2 -2
- package/data/docs/types/Header.json +6 -6
- package/data/docs/types/Heading.json +8 -8
- package/data/docs/types/Icon.json +4 -4
- package/data/docs/types/IconButton.json +24 -24
- package/data/docs/types/IconToggleButton.json +17 -17
- package/data/docs/types/Image.json +38 -38
- package/data/docs/types/Indicator.json +6 -6
- package/data/docs/types/InfoDialog.json +104 -0
- package/data/docs/types/InlineSvg.json +2 -2
- package/data/docs/types/Item.json +6 -6
- package/data/docs/types/Kbd.json +8 -8
- package/data/docs/types/Link.json +8 -8
- package/data/docs/types/ListIndicator.json +6 -6
- package/data/docs/types/ListItem.json +6 -6
- package/data/docs/types/ListRoot.json +10 -10
- package/data/docs/types/LoadingSpinner.json +2 -2
- package/data/docs/types/MenuTrigger.json +33 -33
- package/data/docs/types/MultilineTextInput.json +34 -34
- package/data/docs/types/MultilineTextInputField.json +19 -19
- package/data/docs/types/NumberInput.json +19 -19
- package/data/docs/types/NumberInputField.json +38 -38
- package/data/docs/types/PageContentColumn.json +6 -6
- package/data/docs/types/PageContentRoot.json +20 -20
- package/data/docs/types/PasswordInput.json +17 -17
- package/data/docs/types/PasswordInputField.json +17 -17
- package/data/docs/types/ProgressBar.json +21 -21
- package/data/docs/types/RichTextInput.json +2 -2
- package/data/docs/types/Root.json +10 -10
- package/data/docs/types/Row.json +6 -6
- package/data/docs/types/ScrollArea.json +129 -32
- package/data/docs/types/ScrollAreaElementIds.json +9 -0
- package/data/docs/types/ScrollAreaProps.json +9 -0
- package/data/docs/types/SearchInput.json +19 -19
- package/data/docs/types/SearchInputField.json +25 -25
- package/data/docs/types/SelectRoot.json +15 -15
- package/data/docs/types/Separator.json +4 -4
- package/data/docs/types/SimpleGrid.json +28 -28
- package/data/docs/types/SplitButton.json +2 -2
- package/data/docs/types/Stack.json +2 -2
- package/data/docs/types/StepsRoot.json +2 -2
- package/data/docs/types/Switch.json +2 -2
- package/data/docs/types/TabNavItem.json +19 -19
- package/data/docs/types/TabNavRoot.json +18 -18
- package/data/docs/types/TableBody.json +6 -6
- package/data/docs/types/TableCaption.json +6 -6
- package/data/docs/types/TableCell.json +6 -6
- package/data/docs/types/TableColumn.json +6 -6
- package/data/docs/types/TableColumnGroup.json +6 -6
- package/data/docs/types/TableColumnHeader.json +6 -6
- package/data/docs/types/TableFooter.json +6 -6
- package/data/docs/types/TableHeader.json +6 -6
- package/data/docs/types/TableRoot.json +18 -18
- package/data/docs/types/TableRow.json +6 -6
- package/data/docs/types/TableScrollArea.json +6 -6
- package/data/docs/types/TabsTab.json +17 -17
- package/data/docs/types/Text.json +8 -8
- package/data/docs/types/TextInput.json +16 -16
- package/data/docs/types/TextInputField.json +17 -17
- package/data/docs/types/ToggleButton.json +17 -17
- package/data/docs/types/ToggleButtonGroupButton.json +6 -6
- package/data/docs/types/ToggleButtonGroupRoot.json +6 -6
- package/data/docs/types/Toolbar.json +16 -16
- package/data/docs/types/TooltipContent.json +21 -21
- package/data/docs/types/TooltipRoot.json +3 -3
- package/data/docs/types/manifest.json +16 -11
- package/data/docs/types/toast.json +2 -17
- package/package.json +6 -6
|
@@ -207,10 +207,10 @@
|
|
|
207
207
|
}
|
|
208
208
|
]
|
|
209
209
|
},
|
|
210
|
-
"mdx": "\n## Overview\n\nModalPage is a fullscreen overlay that covers nearly the entire viewport,\nproviding a dedicated context for complex workflows. It is the Nimbus\nequivalent of the Merchant Center Application Kit's modal page patterns\n(form, info, and tabular variants).\n\nUnlike a regular Drawer, ModalPage has a fixed, structured layout with a\ntop navigation bar, header, scrollable content area, and optional footer.\nIt is always controlled — consumers manage the open state with `isOpen` and\n`onClose`.\n\n### Key features\n\n- **Controlled-only API**: Explicit `isOpen` + `onClose` — no hidden state\n- **Breadcrumb navigation**: `ModalPage.TopBar` shows previous/current path with an accessible back button\n- **Structured layout**: TopBar / Header / Content (scrollable) / Footer\n- **Tab navigation**: `ModalPage.TabNav` for tabular page patterns\n- **Multi-column content**: Use `PageContent.Root` inside `ModalPage.Content` for column layouts\n- **Accessibility first**: WCAG 2.1 AA compliant with focus management, keyboard navigation, and screen reader support\n\n### Resources\n\n[React Aria Dialog Docs](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n[ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n## Variables\n\nGet familiar with the features.\n\n### Info page\n\nA read-only detail view. Footer is optional.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Info Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Product Details\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Product Details</ModalPage.Title>\n <ModalPage.Subtitle>View the product information</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Product information goes here.</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Form page\n\nUse when the user needs to create or edit an entity. Always include a Footer\nwith Save/Cancel actions.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Form Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page\n\nUse when content is organized into multiple sections via tab navigation.\nPlace `ModalPage.TabNav` inside the header and display the active section\nin the content area.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Sidebar summary (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked pages\n\nUse when a workflow requires drilling into a sub-task — for example, opening\n\"Add Variant\" while the \"Edit Product\" page is open. Place the second\n`ModalPage.Root` inside the first `ModalPage.Content`. Each page manages its\nown independent open state.\n\n```jsx live\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = React.useState(false);\n const [isSecondOpen, setIsSecondOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Variant</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Product</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Guidelines\n\nUse ModalPage strategically to enhance user workflow without disrupting the\nexperience.\n\n### Best practices\n\n- **Always provide a title**: Use `ModalPage.Title` so screen readers can identify the dialog\n- **Use TopBar navigation**: Always supply meaningful `previousPathLabel` and `currentPathLabel`\n- **Footer actions for forms**: Include Save/Cancel in `ModalPage.Footer` for form pages\n- **Prefer Escape key close**: Don't disable keyboard dismissal — it matches OS conventions and WCAG 2.1\n- **Backdrop click is disabled**: Users must close via back button, Cancel, or Escape to prevent accidental data loss\n\n> [!TIP]\\\n> When to use\n\n- **Complex editing workflows**: Creating or editing entities with many fields\n- **Multi-section detail views**: When content is too rich for a standard Drawer\n- **Tabular data workflows**: When content requires tabs (general, items, shipping)\n- **Full-page focused context**: When the user needs to focus entirely on the task\n\n> [!CAUTION]\\\n> When not to use\n\n- **Simple confirmation dialogs**: Use `Dialog` instead\n- **Single-form quick edits**: Use `Drawer` instead — ModalPage is for complex, multi-field scenarios\n- **Navigation overlays**: Use proper routing for section changes\n\n## Specs\n\n<PropsTable id=\"ModalPage\" />\n\n## Accessibility\n\n### Keyboard Navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Moves focus between interactive elements |\n\nFocus is trapped inside the modal while open and returns to the triggering\nelement when the modal closes.\n\n### Screen Reader Support\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- `ModalPage.Title` automatically provides the accessible name for the dialog\n- The back button has a localized label referencing the previous path (e.g. \"Go back to Products\")\n- The breadcrumb separator is hidden from assistive technology\n- The current path label is marked as the current page\n",
|
|
210
|
+
"mdx": "\n## Overview\n\nModalPage is a fullscreen overlay that covers nearly the entire viewport,\nproviding a dedicated context for complex workflows. It is the Nimbus\nequivalent of the Merchant Center Application Kit's modal page patterns\n(form, info, and tabular variants).\n\nUnlike a regular Drawer, ModalPage has a fixed, structured layout with a\ntop navigation bar, header, scrollable content area, and optional footer.\nIt is always controlled — consumers manage the open state with `isOpen` and\n`onClose`.\n\n### Key features\n\n- **Controlled-only API**: Explicit `isOpen` + `onClose` — no hidden state\n- **Breadcrumb navigation**: `ModalPage.TopBar` shows previous/current path with an accessible back button\n- **Structured layout**: TopBar / Header / Content (scrollable) / Footer\n- **Tab navigation**: `ModalPage.TabNav` for tabular page patterns\n- **Multi-column content**: Use `PageContent.Root` inside `ModalPage.Content` for column layouts\n- **Accessibility first**: WCAG 2.1 AA compliant with focus management, keyboard navigation, and screen reader support\n\n### Resources\n\n[React Aria Dialog Docs](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n[ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n## Variables\n\nGet familiar with the features.\n\n### Info page\n\nA read-only detail view. Footer is optional.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Info Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Product Details\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Product Details</ModalPage.Title>\n <ModalPage.Subtitle>View the product information</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Product information goes here.</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Form page\n\nUse when the user needs to create or edit an entity. Always include a Footer\nwith Save/Cancel actions.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Form Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page\n\nUse when content is organized into multiple sections via tab navigation.\nPlace `ModalPage.TabNav` inside the header and display the active section\nin the content area.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Sidebar summary (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked pages\n\nUse when a workflow requires drilling into a sub-task — for example, opening\n\"Add Variant\" while the \"Edit Product\" page is open. Place the second\n`ModalPage.Root` inside the first `ModalPage.Content`. Each page manages its\nown independent open state.\n\n```jsx live\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = React.useState(false);\n const [isSecondOpen, setIsSecondOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Variant\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Product\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Guidelines\n\nUse ModalPage strategically to enhance user workflow without disrupting the\nexperience.\n\n### Best practices\n\n- **Always provide a title**: Use `ModalPage.Title` so screen readers can identify the dialog\n- **Use TopBar navigation**: Always supply meaningful `previousPathLabel` and `currentPathLabel`\n- **Footer actions for forms**: Include Save/Cancel in `ModalPage.Footer` for form pages\n- **Prefer Escape key close**: Don't disable keyboard dismissal — it matches OS conventions and WCAG 2.1\n- **Backdrop click is disabled**: Users must close via back button, Cancel, or Escape to prevent accidental data loss\n\n> [!TIP]\\\n> When to use\n\n- **Complex editing workflows**: Creating or editing entities with many fields\n- **Multi-section detail views**: When content is too rich for a standard Drawer\n- **Tabular data workflows**: When content requires tabs (general, items, shipping)\n- **Full-page focused context**: When the user needs to focus entirely on the task\n\n> [!CAUTION]\\\n> When not to use\n\n- **Simple confirmation dialogs**: Use `Dialog` instead\n- **Single-form quick edits**: Use `Drawer` instead — ModalPage is for complex, multi-field scenarios\n- **Navigation overlays**: Use proper routing for section changes\n\n## Specs\n\n<PropsTable id=\"ModalPage\" />\n\n## Accessibility\n\n### Keyboard Navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Moves focus between interactive elements |\n\nFocus is trapped inside the modal while open and returns to the triggering\nelement when the modal closes.\n\n### Screen Reader Support\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- `ModalPage.Title` automatically provides the accessible name for the dialog\n- The back button has a localized label referencing the previous path (e.g. \"Go back to Products\")\n- The breadcrumb separator is hidden from assistive technology\n- The current path label is marked as the current page\n",
|
|
211
211
|
"views": {
|
|
212
212
|
"overview": {
|
|
213
|
-
"mdx": "\n## Overview\n\nModalPage is a fullscreen overlay that covers nearly the entire viewport,\nproviding a dedicated context for complex workflows. It is the Nimbus\nequivalent of the Merchant Center Application Kit's modal page patterns\n(form, info, and tabular variants).\n\nUnlike a regular Drawer, ModalPage has a fixed, structured layout with a\ntop navigation bar, header, scrollable content area, and optional footer.\nIt is always controlled — consumers manage the open state with `isOpen` and\n`onClose`.\n\n### Key features\n\n- **Controlled-only API**: Explicit `isOpen` + `onClose` — no hidden state\n- **Breadcrumb navigation**: `ModalPage.TopBar` shows previous/current path with an accessible back button\n- **Structured layout**: TopBar / Header / Content (scrollable) / Footer\n- **Tab navigation**: `ModalPage.TabNav` for tabular page patterns\n- **Multi-column content**: Use `PageContent.Root` inside `ModalPage.Content` for column layouts\n- **Accessibility first**: WCAG 2.1 AA compliant with focus management, keyboard navigation, and screen reader support\n\n### Resources\n\n[React Aria Dialog Docs](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n[ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n## Variables\n\nGet familiar with the features.\n\n### Info page\n\nA read-only detail view. Footer is optional.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Info Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Product Details\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Product Details</ModalPage.Title>\n <ModalPage.Subtitle>View the product information</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Product information goes here.</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Form page\n\nUse when the user needs to create or edit an entity. Always include a Footer\nwith Save/Cancel actions.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Form Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page\n\nUse when content is organized into multiple sections via tab navigation.\nPlace `ModalPage.TabNav` inside the header and display the active section\nin the content area.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Sidebar summary (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked pages\n\nUse when a workflow requires drilling into a sub-task — for example, opening\n\"Add Variant\" while the \"Edit Product\" page is open. Place the second\n`ModalPage.Root` inside the first `ModalPage.Content`. Each page manages its\nown independent open state.\n\n```jsx live\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = React.useState(false);\n const [isSecondOpen, setIsSecondOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Variant</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Product</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Guidelines\n\nUse ModalPage strategically to enhance user workflow without disrupting the\nexperience.\n\n### Best practices\n\n- **Always provide a title**: Use `ModalPage.Title` so screen readers can identify the dialog\n- **Use TopBar navigation**: Always supply meaningful `previousPathLabel` and `currentPathLabel`\n- **Footer actions for forms**: Include Save/Cancel in `ModalPage.Footer` for form pages\n- **Prefer Escape key close**: Don't disable keyboard dismissal — it matches OS conventions and WCAG 2.1\n- **Backdrop click is disabled**: Users must close via back button, Cancel, or Escape to prevent accidental data loss\n\n> [!TIP]\\\n> When to use\n\n- **Complex editing workflows**: Creating or editing entities with many fields\n- **Multi-section detail views**: When content is too rich for a standard Drawer\n- **Tabular data workflows**: When content requires tabs (general, items, shipping)\n- **Full-page focused context**: When the user needs to focus entirely on the task\n\n> [!CAUTION]\\\n> When not to use\n\n- **Simple confirmation dialogs**: Use `Dialog` instead\n- **Single-form quick edits**: Use `Drawer` instead — ModalPage is for complex, multi-field scenarios\n- **Navigation overlays**: Use proper routing for section changes\n\n## Specs\n\n<PropsTable id=\"ModalPage\" />\n\n## Accessibility\n\n### Keyboard Navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Moves focus between interactive elements |\n\nFocus is trapped inside the modal while open and returns to the triggering\nelement when the modal closes.\n\n### Screen Reader Support\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- `ModalPage.Title` automatically provides the accessible name for the dialog\n- The back button has a localized label referencing the previous path (e.g. \"Go back to Products\")\n- The breadcrumb separator is hidden from assistive technology\n- The current path label is marked as the current page\n",
|
|
213
|
+
"mdx": "\n## Overview\n\nModalPage is a fullscreen overlay that covers nearly the entire viewport,\nproviding a dedicated context for complex workflows. It is the Nimbus\nequivalent of the Merchant Center Application Kit's modal page patterns\n(form, info, and tabular variants).\n\nUnlike a regular Drawer, ModalPage has a fixed, structured layout with a\ntop navigation bar, header, scrollable content area, and optional footer.\nIt is always controlled — consumers manage the open state with `isOpen` and\n`onClose`.\n\n### Key features\n\n- **Controlled-only API**: Explicit `isOpen` + `onClose` — no hidden state\n- **Breadcrumb navigation**: `ModalPage.TopBar` shows previous/current path with an accessible back button\n- **Structured layout**: TopBar / Header / Content (scrollable) / Footer\n- **Tab navigation**: `ModalPage.TabNav` for tabular page patterns\n- **Multi-column content**: Use `PageContent.Root` inside `ModalPage.Content` for column layouts\n- **Accessibility first**: WCAG 2.1 AA compliant with focus management, keyboard navigation, and screen reader support\n\n### Resources\n\n[React Aria Dialog Docs](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n[ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n\n## Variables\n\nGet familiar with the features.\n\n### Info page\n\nA read-only detail view. Footer is optional.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Info Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Product Details\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Product Details</ModalPage.Title>\n <ModalPage.Subtitle>View the product information</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Product information goes here.</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Form page\n\nUse when the user needs to create or edit an entity. Always include a Footer\nwith Save/Cancel actions.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Form Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page\n\nUse when content is organized into multiple sections via tab navigation.\nPlace `ModalPage.TabNav` inside the header and display the active section\nin the content area.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Sidebar summary (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked pages\n\nUse when a workflow requires drilling into a sub-task — for example, opening\n\"Add Variant\" while the \"Edit Product\" page is open. Place the second\n`ModalPage.Root` inside the first `ModalPage.Content`. Each page manages its\nown independent open state.\n\n```jsx live\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = React.useState(false);\n const [isSecondOpen, setIsSecondOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Variant\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Product\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Guidelines\n\nUse ModalPage strategically to enhance user workflow without disrupting the\nexperience.\n\n### Best practices\n\n- **Always provide a title**: Use `ModalPage.Title` so screen readers can identify the dialog\n- **Use TopBar navigation**: Always supply meaningful `previousPathLabel` and `currentPathLabel`\n- **Footer actions for forms**: Include Save/Cancel in `ModalPage.Footer` for form pages\n- **Prefer Escape key close**: Don't disable keyboard dismissal — it matches OS conventions and WCAG 2.1\n- **Backdrop click is disabled**: Users must close via back button, Cancel, or Escape to prevent accidental data loss\n\n> [!TIP]\\\n> When to use\n\n- **Complex editing workflows**: Creating or editing entities with many fields\n- **Multi-section detail views**: When content is too rich for a standard Drawer\n- **Tabular data workflows**: When content requires tabs (general, items, shipping)\n- **Full-page focused context**: When the user needs to focus entirely on the task\n\n> [!CAUTION]\\\n> When not to use\n\n- **Simple confirmation dialogs**: Use `Dialog` instead\n- **Single-form quick edits**: Use `Drawer` instead — ModalPage is for complex, multi-field scenarios\n- **Navigation overlays**: Use proper routing for section changes\n\n## Specs\n\n<PropsTable id=\"ModalPage\" />\n\n## Accessibility\n\n### Keyboard Navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Moves focus between interactive elements |\n\nFocus is trapped inside the modal while open and returns to the triggering\nelement when the modal closes.\n\n### Screen Reader Support\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- `ModalPage.Title` automatically provides the accessible name for the dialog\n- The back button has a localized label referencing the previous path (e.g. \"Go back to Products\")\n- The breadcrumb separator is hidden from assistive technology\n- The current path label is marked as the current page\n",
|
|
214
214
|
"toc": [
|
|
215
215
|
{
|
|
216
216
|
"value": "Overview",
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
]
|
|
376
376
|
},
|
|
377
377
|
"a11y": {
|
|
378
|
-
"mdx": "\n## Accessibility\n\nAccessibility ensures that digital content and functionality are usable by\neveryone, including people with disabilities, by addressing visual, auditory,\ncognitive, and physical limitations.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Modal Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>\n This modal page demonstrates accessibility features: dialog role,\n focus trap, Escape key dismissal, and breadcrumb navigation.\n </Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <
|
|
378
|
+
"mdx": "\n## Accessibility\n\nAccessibility ensures that digital content and functionality are usable by\neveryone, including people with disabilities, by addressing visual, auditory,\ncognitive, and physical limitations.\n\n```jsx live\nconst App = () => {\n const [isOpen, setIsOpen] = React.useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Modal Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>\n This modal page demonstrates accessibility features: dialog role,\n focus trap, Escape key dismissal, and breadcrumb navigation.\n </Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Accessibility standards\n\n- **Role identification**: ModalPage has `role=\"dialog\"` with proper labeling via `ModalPage.Title`.\n- **Focus management**: Focus moves to the back button on open and returns to the trigger on close.\n- **Focus containment**: Tab navigation stays within the modal while it is open.\n- **Accessible names**: `ModalPage.Title` automatically provides the dialog's accessible name via `aria-labelledby`.\n- **Accessible description**: `ModalPage.Subtitle`, when present, automatically provides the dialog's accessible description via `aria-describedby`.\n- **Modal indication**: `aria-modal=\"true\"` is set, blocking screen reader interaction with the background.\n- **State announcements**: Screen readers announce when the dialog opens and closes.\n- **Breadcrumb accessibility**: The `/` separator is `aria-hidden=\"true\"`; the current path has `aria-current=\"page\"`.\n- **Back button label**: The back button in `ModalPage.TopBar` uses a localized `aria-label` referencing the `previousPathLabel`.\n- **Breadcrumb previous path**: The `previousPathLabel` text is `aria-hidden=\"true\"` since the back button's label already conveys this information.\n- **Keyboard dismissal**: Escape key always closes the modal (WCAG 2.1 SC 2.1.2 — No Keyboard Trap).\n- **No keyboard trap**: Focus is contained but always escapable via Escape key.\n- **Focus order**: Logical focus progression through all interactive elements.\n- **Focus visible**: Clear focus indicators on all interactive elements.\n- **Contrast**: All text meets minimum contrast requirements.\n- **Non-text contrast**: Focus indicators and UI components meet contrast standards.\n- **Name, role, value**: Proper semantic markup and ARIA attributes throughout.\n\n### Resources\n\n- [W3C ARIA Authoring Practices Guide (APG) - Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)\n- [WCAG 2.1 SC 2.1.2 - No Keyboard Trap](https://www.w3.org/WAI/WCAG21/Understanding/no-keyboard-trap.html)\n",
|
|
379
379
|
"toc": [
|
|
380
380
|
{
|
|
381
381
|
"value": "Accessibility",
|
|
@@ -412,7 +412,7 @@
|
|
|
412
412
|
]
|
|
413
413
|
},
|
|
414
414
|
"dev": {
|
|
415
|
-
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { ModalPage } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nModalPage is a controlled compound component. The basic implementation shows\nhow to manage open state and compose the required sub-components:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Modal Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Usage examples\n\n### Closing the modal page\n\nThere are three ways to close a ModalPage — all route through the same\n`onClose` callback:\n\n1. **Back button in TopBar** — acts as a close trigger, calling `onClose`\n2. **Any button with `slot=\"close\"`** — automatically triggers `onClose`\n3. **Escape key** — always enabled; matches browser/OS convention and WCAG 2.1\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>\n Close via: back button in TopBar, slot=\"close\" button, or Escape key.\n </Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Summary sidebar (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Header actions\n\nUse `ModalPage.Actions` to place secondary actions (e.g. Preview, Export) in\nthe right column of the header grid:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open with Header Actions</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Last saved 2 minutes ago</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"ghost\">Preview</Button>\n <Button size=\"sm\" variant=\"outline\">Export</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Content area</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page with TabNav\n\nUse `ModalPage.TabNav` inside the header for tabular page patterns with\nroute-based tab navigation:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked modal pages\n\nWhen a workflow requires drilling into a sub-task (e.g. adding a variant\nwhile editing a product), place a second `ModalPage.Root` inside the first\n`ModalPage.Content`. Each modal page manages its own independent state.\n\nThe breadcrumb in `ModalPage.TopBar` naturally reflects the depth:\nset `previousPathLabel` to the parent page's title so the back button\nreads contextually.\n\nBehaviour that works automatically:\n- Escape closes only the topmost page\n- Focus is trapped in the topmost page; previous page is inert\n- When the second page closes, focus returns to its trigger inside the first page\n- When the first page closes, focus returns to the original page trigger\n\n```jsx live-dev\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = useState(false);\n const [isSecondOpen, setIsSecondOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Variant</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save Product</Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Component requirements\n\n### Accessible dialog label\n\nThe dialog must have an accessible name. Use `ModalPage.Title` (recommended):\n\n```tsx\n<ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n</ModalPage.Header>\n```\n\n`ModalPage.Title` automatically provides the accessible name for the dialog.\nScreen readers will announce the title text when the dialog opens.\n\n### Custom page width\n\nThe default width is near-fullscreen. Pass a `width` prop to\n`ModalPage.Root` to override:\n\n```tsx\n<ModalPage.Root isOpen={isOpen} onClose={handleClose} width=\"xl\">\n ...\n</ModalPage.Root>\n```\n\n### Footer button spacing\n\n`ModalPage.Footer` automatically spaces its children — place buttons directly\nwithout a layout wrapper:\n\n```tsx\n<ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n</ModalPage.Footer>\n```\n\n### Controlled state\n\nModalPage is controlled-only. You must always supply both `isOpen` and\n`onClose`:\n\n```tsx\nconst [isOpen, setIsOpen] = useState(false);\n\n<ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n ...\n</ModalPage.Root>\n```\n\n## Accessibility\n\nModalPage handles most accessibility requirements internally:\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- Focus is trapped inside the modal while open\n- Focus moves to the back button on open and returns to the trigger on close\n- Escape key dismissal is always active\n- Backdrop click is disabled — full-page forms should not close accidentally\n\n#### Keyboard navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Navigate focusable elements within the modal |\n\n#### ARIA attributes\n\n- `role=\"dialog\"`: Applied to the dialog container\n- `aria-labelledby`: Automatically linked to `ModalPage.Title`\n- `aria-modal=\"true\"`: Marks the overlay as modal to screen readers\n- `aria-current=\"page\"`: Applied to the current path label in `ModalPage.TopBar`\n- `aria-hidden=\"true\"`: Applied to the breadcrumb separator\n\n## API reference\n\n<PropsTable id=\"ModalPage\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using ModalPage\nin your application. As the component's internal functionality is already tested\nby Nimbus, these patterns help you verify your integration and application-specific\nlogic.\n\n### Basic usage\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Basic usage\", () => {\n it(\"opens and closes via controlled state\", () => {\n const onClose = vi.fn();\n\n const Example = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <NimbusProvider>\n <Button onPress={() => setIsOpen(true)}>Open</Button>\n <ModalPage.Root\n isOpen={isOpen}\n onClose={() => {\n onClose();\n setIsOpen(false);\n }}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>\n Update the product details\n </ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form content</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">\n Cancel\n </Button>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Save\n </Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </NimbusProvider>\n );\n };\n\n render(<Example />);\n\n // Dialog is not in DOM before opening\n expect(screen.queryByRole(\"dialog\")).not.toBeInTheDocument();\n\n // Open the modal\n fireEvent.click(screen.getByRole(\"button\", { name: \"Open\" }));\n expect(screen.getByRole(\"dialog\")).toBeInTheDocument();\n expect(\n screen.getByRole(\"heading\", { name: \"Edit Product\" })\n ).toBeInTheDocument();\n\n // Close via slot=\"close\" button\n fireEvent.click(screen.getByRole(\"button\", { name: \"Cancel\" }));\n expect(onClose).toHaveBeenCalledOnce();\n });\n});\n```\n\n### Form page with header actions and footer\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Form page\", () => {\n it(\"renders header actions and footer buttons\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">\n Preview\n </Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">\n Cancel\n </Button>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Save\n </Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\", { name: \"Preview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Cancel\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Save\" })).toBeInTheDocument();\n });\n});\n```\n\n### Multi-column layout\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Multi-column layout\", () => {\n it(\"renders a 2/1 column layout\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Summary sidebar</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Main form area\")).toBeInTheDocument();\n expect(screen.getByText(\"Summary sidebar\")).toBeInTheDocument();\n });\n});\n```\n\n### Tabular page with TabNav\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Tabular page\", () => {\n it(\"renders tab navigation inside the header\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>\n General\n </TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"heading\", { name: \"Order #12345\" })\n ).toBeInTheDocument();\n expect(screen.getByText(\"General\")).toBeInTheDocument();\n expect(screen.getByText(\"Items\")).toBeInTheDocument();\n expect(screen.getByText(\"Shipping\")).toBeInTheDocument();\n });\n});\n```\n\n### Stacked modal pages\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Stacked pages\", () => {\n it(\"renders nested modal pages independently\", () => {\n const Example = () => {\n const [isFirstOpen, setIsFirstOpen] = useState(true);\n const [isSecondOpen, setIsSecondOpen] = useState(false);\n return (\n <NimbusProvider>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Add Variant\n </Button>\n </Stack>\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n };\n\n render(<Example />);\n\n expect(screen.getByText(\"Product form content\")).toBeInTheDocument();\n\n // Open stacked page\n fireEvent.click(screen.getByRole(\"button\", { name: \"Add Variant\" }));\n expect(screen.getByText(\"Variant form content\")).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-layout-modalpage--docs)\n- [React Aria Dialog](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n- [ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n",
|
|
415
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { ModalPage } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nModalPage is a controlled compound component. The basic implementation shows\nhow to manage open state and compose the required sub-components:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Modal Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">Preview</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Usage examples\n\n### Closing the modal page\n\nThere are three ways to close a ModalPage — all route through the same\n`onClose` callback:\n\n1. **Back button in TopBar** — acts as a close trigger, calling `onClose`\n2. **Any button with `slot=\"close\"`** — automatically triggers `onClose`\n3. **Escape key** — always enabled; matches browser/OS convention and WCAG 2.1\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>\n Close via: back button in TopBar, slot=\"close\" button, or Escape key.\n </Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Multi-column content\n\nUse `PageContent.Root` with `columns` inside `ModalPage.Content` for\nside-by-side layouts:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Two-Column Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form (2/3 width)</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Summary sidebar (1/3 width, sticky)</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Header actions\n\nUse `ModalPage.Actions` to place secondary actions (e.g. Preview, Export) in\nthe right column of the header grid:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open with Header Actions</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Last saved 2 minutes ago</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"ghost\">Preview</Button>\n <Button size=\"sm\" variant=\"outline\">Export</Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Content area</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Tabular page with TabNav\n\nUse `ModalPage.TabNav` inside the header for tabular page patterns with\nroute-based tab navigation:\n\n```jsx live-dev\nconst App = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsOpen(true)}>Open Tabular Page</Button>\n <ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>General</TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n### Stacked modal pages\n\nWhen a workflow requires drilling into a sub-task (e.g. adding a variant\nwhile editing a product), place a second `ModalPage.Root` inside the first\n`ModalPage.Content`. Each modal page manages its own independent state.\n\nThe breadcrumb in `ModalPage.TopBar` naturally reflects the depth:\nset `previousPathLabel` to the parent page's title so the back button\nreads contextually.\n\nBehaviour that works automatically:\n- Escape closes only the topmost page\n- Focus is trapped in the topmost page; previous page is inert\n- When the second page closes, focus returns to its trigger inside the first page\n- When the first page closes, focus returns to the original page trigger\n\n```jsx live-dev\nconst App = () => {\n const [isFirstOpen, setIsFirstOpen] = useState(false);\n const [isSecondOpen, setIsSecondOpen] = useState(false);\n return (\n <Stack>\n <Button onPress={() => setIsFirstOpen(true)}>Open Edit Product</Button>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>Update the product details</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content goes here.</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Open Add Variant\n </Button>\n </Stack>\n\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n <ModalPage.Subtitle>Define a new product variant</ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content goes here.</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Variant\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </ModalPage.Content>\n <ModalPage.Footer>\n <FormActionBar\n saveLabel=\"Save Product\"\n onSave={() => {}}\n onCancel={() => {}}\n cancelSlot=\"close\"\n />\n </ModalPage.Footer>\n </ModalPage.Root>\n </Stack>\n );\n}\n```\n\n## Component requirements\n\n### Accessible dialog label\n\nThe dialog must have an accessible name. Use `ModalPage.Title` (recommended):\n\n```tsx\n<ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n</ModalPage.Header>\n```\n\n`ModalPage.Title` automatically provides the accessible name for the dialog.\nScreen readers will announce the title text when the dialog opens.\n\n### Custom page width\n\nThe default width is near-fullscreen. Pass a `width` prop to\n`ModalPage.Root` to override:\n\n```tsx\n<ModalPage.Root isOpen={isOpen} onClose={handleClose} width=\"xl\">\n ...\n</ModalPage.Root>\n```\n\n### Footer button spacing\n\n`ModalPage.Footer` automatically spaces its children — place buttons directly\nwithout a layout wrapper:\n\n```tsx\n<ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">Cancel</Button>\n <Button colorPalette=\"primary\" variant=\"solid\">Save</Button>\n</ModalPage.Footer>\n```\n\n### Controlled state\n\nModalPage is controlled-only. You must always supply both `isOpen` and\n`onClose`:\n\n```tsx\nconst [isOpen, setIsOpen] = useState(false);\n\n<ModalPage.Root isOpen={isOpen} onClose={() => setIsOpen(false)}>\n ...\n</ModalPage.Root>\n```\n\n## Accessibility\n\nModalPage handles most accessibility requirements internally:\n\n- The dialog has `role=\"dialog\"` and `aria-modal=\"true\"`\n- Focus is trapped inside the modal while open\n- Focus moves to the back button on open and returns to the trigger on close\n- Escape key dismissal is always active\n- Backdrop click is disabled — full-page forms should not close accidentally\n\n#### Keyboard navigation\n\n| Key | Action |\n|-----|--------|\n| `Escape` | Closes the modal page |\n| `Tab` / `Shift+Tab` | Navigate focusable elements within the modal |\n\n#### ARIA attributes\n\n- `role=\"dialog\"`: Applied to the dialog container\n- `aria-labelledby`: Automatically linked to `ModalPage.Title`\n- `aria-modal=\"true\"`: Marks the overlay as modal to screen readers\n- `aria-current=\"page\"`: Applied to the current path label in `ModalPage.TopBar`\n- `aria-hidden=\"true\"`: Applied to the breadcrumb separator\n\n## API reference\n\n<PropsTable id=\"ModalPage\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using ModalPage\nin your application. As the component's internal functionality is already tested\nby Nimbus, these patterns help you verify your integration and application-specific\nlogic.\n\n### Basic usage\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Basic usage\", () => {\n it(\"opens and closes via controlled state\", () => {\n const onClose = vi.fn();\n\n const Example = () => {\n const [isOpen, setIsOpen] = useState(false);\n return (\n <NimbusProvider>\n <Button onPress={() => setIsOpen(true)}>Open</Button>\n <ModalPage.Root\n isOpen={isOpen}\n onClose={() => {\n onClose();\n setIsOpen(false);\n }}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n <ModalPage.Subtitle>\n Update the product details\n </ModalPage.Subtitle>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form content</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">\n Cancel\n </Button>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Save\n </Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </NimbusProvider>\n );\n };\n\n render(<Example />);\n\n // Dialog is not in DOM before opening\n expect(screen.queryByRole(\"dialog\")).not.toBeInTheDocument();\n\n // Open the modal\n fireEvent.click(screen.getByRole(\"button\", { name: \"Open\" }));\n expect(screen.getByRole(\"dialog\")).toBeInTheDocument();\n expect(\n screen.getByRole(\"heading\", { name: \"Edit Product\" })\n ).toBeInTheDocument();\n\n // Close via slot=\"close\" button\n fireEvent.click(screen.getByRole(\"button\", { name: \"Cancel\" }));\n expect(onClose).toHaveBeenCalledOnce();\n });\n});\n```\n\n### Form page with header actions and footer\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Form page\", () => {\n it(\"renders header actions and footer buttons\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Add Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Product</ModalPage.Title>\n <ModalPage.Subtitle>Fill in the product details</ModalPage.Subtitle>\n <ModalPage.Actions>\n <Button size=\"sm\" variant=\"outline\">\n Preview\n </Button>\n </ModalPage.Actions>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Form fields go here</Text>\n </ModalPage.Content>\n <ModalPage.Footer>\n <Button slot=\"close\" variant=\"outline\">\n Cancel\n </Button>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Save\n </Button>\n </ModalPage.Footer>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\", { name: \"Preview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Cancel\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Save\" })).toBeInTheDocument();\n });\n});\n```\n\n### Multi-column layout\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Multi-column layout\", () => {\n it(\"renders a 2/1 column layout\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <PageContent.Root variant=\"wide\" columns=\"2/1\">\n <PageContent.Column>\n <Text>Main form area</Text>\n </PageContent.Column>\n <PageContent.Column sticky>\n <Text>Summary sidebar</Text>\n </PageContent.Column>\n </PageContent.Root>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Main form area\")).toBeInTheDocument();\n expect(screen.getByText(\"Summary sidebar\")).toBeInTheDocument();\n });\n});\n```\n\n### Tabular page with TabNav\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Tabular page\", () => {\n it(\"renders tab navigation inside the header\", () => {\n render(\n <NimbusProvider>\n <ModalPage.Root isOpen onClose={() => {}}>\n <ModalPage.TopBar\n previousPathLabel=\"Orders\"\n currentPathLabel=\"Order #12345\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Order #12345</ModalPage.Title>\n <ModalPage.Subtitle>Placed on 2024-01-15</ModalPage.Subtitle>\n <ModalPage.TabNav>\n <TabNav.Root aria-label=\"Order sections\">\n <TabNav.Item href=\"#general\" isCurrent>\n General\n </TabNav.Item>\n <TabNav.Item href=\"#items\">Items</TabNav.Item>\n <TabNav.Item href=\"#shipping\">Shipping</TabNav.Item>\n </TabNav.Root>\n </ModalPage.TabNav>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>General information</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"heading\", { name: \"Order #12345\" })\n ).toBeInTheDocument();\n expect(screen.getByText(\"General\")).toBeInTheDocument();\n expect(screen.getByText(\"Items\")).toBeInTheDocument();\n expect(screen.getByText(\"Shipping\")).toBeInTheDocument();\n });\n});\n```\n\n### Stacked modal pages\n\n```tsx\nimport { useState } from \"react\";\nimport { render, screen, fireEvent } from \"@testing-library/react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport {\n Button,\n ModalPage,\n NimbusProvider,\n PageContent,\n Stack,\n TabNav,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ModalPage - Stacked pages\", () => {\n it(\"renders nested modal pages independently\", () => {\n const Example = () => {\n const [isFirstOpen, setIsFirstOpen] = useState(true);\n const [isSecondOpen, setIsSecondOpen] = useState(false);\n return (\n <NimbusProvider>\n <ModalPage.Root\n isOpen={isFirstOpen}\n onClose={() => setIsFirstOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Products\"\n currentPathLabel=\"Edit Product\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Edit Product</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Stack>\n <Text>Product form content</Text>\n <Button onPress={() => setIsSecondOpen(true)}>\n Add Variant\n </Button>\n </Stack>\n <ModalPage.Root\n isOpen={isSecondOpen}\n onClose={() => setIsSecondOpen(false)}\n >\n <ModalPage.TopBar\n previousPathLabel=\"Edit Product\"\n currentPathLabel=\"Add Variant\"\n />\n <ModalPage.Header>\n <ModalPage.Title>Add Variant</ModalPage.Title>\n </ModalPage.Header>\n <ModalPage.Content>\n <Text>Variant form content</Text>\n </ModalPage.Content>\n </ModalPage.Root>\n </ModalPage.Content>\n </ModalPage.Root>\n </NimbusProvider>\n );\n };\n\n render(<Example />);\n\n expect(screen.getByText(\"Product form content\")).toBeInTheDocument();\n\n // Open stacked page\n fireEvent.click(screen.getByRole(\"button\", { name: \"Add Variant\" }));\n expect(screen.getByText(\"Variant form content\")).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-layout-modalpage--docs)\n- [React Aria Dialog](https://react-spectrum.adobe.com/react-aria/Dialog.html)\n- [ARIA Dialog Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)\n",
|
|
416
416
|
"toc": [
|
|
417
417
|
{
|
|
418
418
|
"value": "Getting started",
|