@commercetools/nimbus-mcp 2.11.0 → 3.1.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 +1421 -605
- package/data/docs/routes/components-data-display-card.json +71 -5
- package/data/docs/routes/components-data-display-data-table.json +66 -34
- package/data/docs/routes/components-data-display-draggable-list.json +62 -7
- package/data/docs/routes/components-feedback-toast.json +1 -1
- package/data/docs/routes/components-inputs-checkbox.json +2 -2
- 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 +3 -3
- package/data/docs/routes/components-layout-splitter.json +654 -0
- package/data/docs/routes/components-media-avatar.json +24 -2
- package/data/docs/routes/components-utilities-region.json +265 -0
- package/data/docs/routes/home-getting-started-bundler-plugins.json +248 -0
- package/data/docs/routes/hooks-usedraganddrop.json +310 -0
- package/data/docs/routes/patterns-actions-form-action-bar.json +412 -0
- package/data/docs/routes/patterns-actions.json +78 -0
- package/data/docs/routes/patterns-dialogs-confirmation-dialog.json +391 -0
- package/data/docs/routes/patterns-dialogs-form-dialog.json +358 -0
- package/data/docs/routes/patterns-dialogs-info-dialog.json +315 -0
- package/data/docs/routes/patterns-dialogs.json +78 -0
- package/data/docs/routes/patterns-pages-public-page-layout.json +371 -0
- package/data/docs/routes/patterns-pages.json +78 -0
- package/data/docs/search-index.json +1 -1
- package/data/docs/types/AccordionContent.json +32 -32
- package/data/docs/types/AccordionHeader.json +102 -102
- package/data/docs/types/AccordionItem.json +28 -28
- package/data/docs/types/AccordionRoot.json +15 -15
- package/data/docs/types/AlertDescription.json +8 -8
- package/data/docs/types/AlertDismissButton.json +89 -89
- 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 +97 -97
- package/data/docs/types/Calendar.json +111 -65
- 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 +20 -20
- package/data/docs/types/Checkbox.json +99 -99
- package/data/docs/types/Code.json +10 -10
- package/data/docs/types/CollapsibleMotionContent.json +2 -2
- package/data/docs/types/CollapsibleMotionRoot.json +2 -2
- package/data/docs/types/CollapsibleMotionTrigger.json +4 -4
- package/data/docs/types/Column.json +8 -8
- package/data/docs/types/ColumnGroup.json +8 -8
- package/data/docs/types/ColumnHeader.json +18 -18
- package/data/docs/types/ComboBoxListBox.json +80 -80
- package/data/docs/types/ComboBoxOption.json +77 -77
- package/data/docs/types/ComboBoxPopover.json +77 -77
- package/data/docs/types/ComboBoxRoot.json +8 -8
- package/data/docs/types/ComboBoxSection.json +29 -29
- package/data/docs/types/ComboBoxTrigger.json +6 -6
- package/data/docs/types/ConfirmationDialog.json +224 -0
- package/data/docs/types/Content.json +2 -2
- package/data/docs/types/DataTable.json +17 -2
- package/data/docs/types/DataTableBody.json +24 -24
- package/data/docs/types/DataTableHeader.json +31 -31
- package/data/docs/types/DataTableRoot.json +17 -2
- package/data/docs/types/DataTableTable.json +35 -20
- package/data/docs/types/DateInput.json +84 -84
- package/data/docs/types/DatePicker.json +65 -65
- package/data/docs/types/DateRangePicker.json +99 -99
- package/data/docs/types/DateRangePickerField.json +99 -99
- package/data/docs/types/DefaultPageBackLink.json +16 -16
- package/data/docs/types/DefaultPageRoot.json +2 -2
- package/data/docs/types/DialogCloseTrigger.json +87 -87
- package/data/docs/types/DialogTrigger.json +2 -2
- package/data/docs/types/DragAndDropItemData.json +9 -0
- package/data/docs/types/DragAndDropProps.json +9 -0
- package/data/docs/types/DraggableListField.json +159 -70
- package/data/docs/types/DraggableListItem.json +63 -63
- package/data/docs/types/DraggableListRoot.json +159 -70
- package/data/docs/types/DrawerCloseTrigger.json +87 -87
- package/data/docs/types/DrawerTrigger.json +2 -2
- 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/FormDialog.json +198 -0
- package/data/docs/types/FormFieldRoot.json +2 -2
- package/data/docs/types/Grid.json +24 -24
- package/data/docs/types/Group.json +12 -12
- 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 +97 -97
- package/data/docs/types/IconToggleButton.json +84 -84
- package/data/docs/types/Image.json +30 -30
- 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 +31 -31
- 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/MakeElementFocusable.json +19 -19
- package/data/docs/types/MenuItem.json +75 -75
- package/data/docs/types/MenuRoot.json +63 -63
- package/data/docs/types/MenuSection.json +35 -54
- package/data/docs/types/MenuSubmenuTrigger.json +5 -5
- package/data/docs/types/MenuTrigger.json +102 -102
- package/data/docs/types/MultilineTextInput.json +134 -134
- package/data/docs/types/MultilineTextInputField.json +131 -131
- package/data/docs/types/NumberInput.json +100 -100
- package/data/docs/types/NumberInputField.json +95 -95
- package/data/docs/types/PageContentColumn.json +6 -6
- package/data/docs/types/PageContentRoot.json +6 -6
- package/data/docs/types/PasswordInput.json +129 -129
- package/data/docs/types/PasswordInputField.json +129 -129
- package/data/docs/types/ProgressBar.json +14 -14
- package/data/docs/types/PublicPageLayout.json +99 -0
- package/data/docs/types/RadioInputOption.json +64 -64
- package/data/docs/types/RadioInputRoot.json +55 -55
- package/data/docs/types/RangeCalendar.json +90 -71
- package/data/docs/types/Region.json +114 -0
- package/data/docs/types/RegionProvider.json +25 -0
- package/data/docs/types/RegionTarget.json +112 -0
- 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/SPLITTER_SIZE_TOKENS.json +9 -0
- package/data/docs/types/ScrollArea.json +6 -6
- package/data/docs/types/SearchInput.json +136 -136
- package/data/docs/types/SearchInputField.json +131 -131
- package/data/docs/types/SelectOption.json +66 -66
- package/data/docs/types/SelectOptionGroup.json +22 -22
- package/data/docs/types/SelectOptions.json +74 -74
- package/data/docs/types/SelectRoot.json +102 -102
- package/data/docs/types/Separator.json +4 -4
- package/data/docs/types/SimpleGrid.json +28 -28
- package/data/docs/types/SplitButton.json +12 -12
- package/data/docs/types/Splitter.json +12 -0
- package/data/docs/types/SplitterAside.json +27 -0
- package/data/docs/types/SplitterHandle.json +27 -0
- package/data/docs/types/SplitterMain.json +27 -0
- package/data/docs/types/SplitterRoot.json +271 -0
- package/data/docs/types/SplitterSizeToken.json +9 -0
- package/data/docs/types/Stack.json +2 -2
- package/data/docs/types/StepsNextTrigger.json +2 -2
- package/data/docs/types/StepsPrevTrigger.json +2 -2
- package/data/docs/types/StepsRoot.json +2 -2
- package/data/docs/types/StepsTrigger.json +2 -2
- package/data/docs/types/Switch.json +38 -38
- package/data/docs/types/TabNavItem.json +18 -18
- package/data/docs/types/TabNavRoot.json +2 -2
- package/data/docs/types/TableBody.json +6 -6
- package/data/docs/types/TableCaption.json +6 -6
- package/data/docs/types/TableCell.json +20 -20
- package/data/docs/types/TableColumn.json +8 -8
- package/data/docs/types/TableColumnGroup.json +8 -8
- package/data/docs/types/TableColumnHeader.json +18 -18
- package/data/docs/types/TableFooter.json +6 -6
- package/data/docs/types/TableHeader.json +6 -6
- package/data/docs/types/TableRoot.json +32 -32
- package/data/docs/types/TableRow.json +6 -6
- package/data/docs/types/TableScrollArea.json +6 -6
- package/data/docs/types/TabsTab.json +2 -2
- package/data/docs/types/TagGroupRoot.json +27 -27
- package/data/docs/types/TagGroupTag.json +68 -68
- package/data/docs/types/TagGroupTagList.json +18 -18
- package/data/docs/types/Text.json +8 -8
- package/data/docs/types/TextInput.json +132 -132
- package/data/docs/types/TextInputField.json +129 -129
- package/data/docs/types/TimeInput.json +78 -78
- package/data/docs/types/ToggleButton.json +86 -86
- package/data/docs/types/ToggleButtonGroupButton.json +33 -33
- package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
- package/data/docs/types/Toolbar.json +12 -12
- package/data/docs/types/TooltipContent.json +31 -31
- package/data/docs/types/TooltipRoot.json +18 -18
- package/data/docs/types/Trigger.json +4 -4
- package/data/docs/types/UseDragAndDropOptions.json +9 -0
- package/data/docs/types/VisuallyHidden.json +7 -7
- package/data/docs/types/createArrayHandlers.json +12 -0
- package/data/docs/types/createItemsFromCsvDrop.json +833 -0
- package/data/docs/types/createItemsFromDirectoryDrop.json +833 -0
- package/data/docs/types/createItemsFromFileDrop.json +833 -0
- package/data/docs/types/createItemsFromImageDrop.json +833 -0
- package/data/docs/types/createItemsFromJsonDrop.json +833 -0
- package/data/docs/types/createItemsFromTextDrop.json +12 -0
- package/data/docs/types/createListDataHandlers.json +102 -0
- package/data/docs/types/manifest.json +32 -2
- package/data/docs/types/toast.json +0 -15
- package/data/docs/types/useDragAndDrop.json +174 -0
- package/data/docs/types/useRegion.json +1052 -0
- package/data/docs/types/useResponsiveSplitterSizes.json +143 -0
- package/package.json +7 -7
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
]
|
|
181
181
|
},
|
|
182
182
|
"a11y": {
|
|
183
|
-
"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 <Avatar firstName=\"Adam\" lastName=\"Vadam\" src=\"https://thispersondoesnotexist.com/ \" />\n)\n```\n\n### Accessibility standards\n\n- **Non-text content:** Avatars must have a text alternative. Use the `alt`\n attribute on the `<img>` tag (if the avatar is an image) or through `ARIA` if\n the avatar is more complex. The alt text should describe the avatar's purpose\n or what it represents (e.g., \"User profile picture,\" \"John Doe's avatar,\"\n \"Company logo\"). If the avatar is purely decorative and doesn't convey any\n meaningful information, the alt attribute should be empty (`alt=\"\"`).\n- **Info and relationships:** If the avatar is associated with other information\n (e.g., a username, profile link), the relationship should be programmatically\n determinable. This can be achieved using semantic `HTML` (e.g., placing the\n avatar and username within a single element) or ARIA attributes (e.g.,\n `aria-labelledby` or `aria-describedby`).\n- **Use of color:** Don't rely on color alone to convey information about the\n avatar (e.g., using a colored border to indicate status). Any information\n conveyed by color must also be available through other means.\n- **Color contrast:** If the avatar contains any text (which is less common),\n that text must meet minimum contrast ratio requirements.\n- **Keyboard accessibility:** If the avatar is interactive (e.g., clicking it\n opens a profile), it must be operable using the keyboard. Users should be able\n to navigate to the avatar using the Tab key and activate it using the Enter or\n Space key.\n- **Link purpose:** If the avatar is a link, its purpose should be clear from\n the surrounding context or from the avatar's alt text. Avoid generic link text\n like \"Click here.\"\n- **Focus visible:** If the avatar is interactive, it should have a visible\n focus indicator when it receives keyboard focus.\n- **On focus:** If the avatar is interactive and its appearance changes on\n focus, the change should not be disorienting or unexpected.\n- **Name, role, value:** Assistive technologies should be able to correctly\n interpret the role of the avatar (e.g., `image`, `link`) and its name (from\n the alt text).\n",
|
|
183
|
+
"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 <Avatar firstName=\"Adam\" lastName=\"Vadam\" src=\"https://thispersondoesnotexist.com/ \" />\n)\n```\n\n### Accessibility standards\n\n- **Non-text content:** Avatars must have a text alternative. Use the `alt`\n attribute on the `<img>` tag (if the avatar is an image) or through `ARIA` if\n the avatar is more complex. The alt text should describe the avatar's purpose\n or what it represents (e.g., \"User profile picture,\" \"John Doe's avatar,\"\n \"Company logo\"). If the avatar is purely decorative and doesn't convey any\n meaningful information, the alt attribute should be empty (`alt=\"\"`).\n- **Generic label fallback:** When `firstName` and `lastName` are both\n missing, empty, or whitespace-only, the Avatar renders a generic\n `Person` icon and applies a localized generic `aria-label`\n (`\"User avatar\"` in English, with translations for all supported\n locales). This guarantees screen readers always announce a meaningful\n label even when user identity data is incomplete. Consumers can still\n override with an explicit `aria-label` prop when more specific context\n is available.\n- **Info and relationships:** If the avatar is associated with other information\n (e.g., a username, profile link), the relationship should be programmatically\n determinable. This can be achieved using semantic `HTML` (e.g., placing the\n avatar and username within a single element) or ARIA attributes (e.g.,\n `aria-labelledby` or `aria-describedby`).\n- **Use of color:** Don't rely on color alone to convey information about the\n avatar (e.g., using a colored border to indicate status). Any information\n conveyed by color must also be available through other means.\n- **Color contrast:** If the avatar contains any text (which is less common),\n that text must meet minimum contrast ratio requirements.\n- **Keyboard accessibility:** If the avatar is interactive (e.g., clicking it\n opens a profile), it must be operable using the keyboard. Users should be able\n to navigate to the avatar using the Tab key and activate it using the Enter or\n Space key.\n- **Link purpose:** If the avatar is a link, its purpose should be clear from\n the surrounding context or from the avatar's alt text. Avoid generic link text\n like \"Click here.\"\n- **Focus visible:** If the avatar is interactive, it should have a visible\n focus indicator when it receives keyboard focus.\n- **On focus:** If the avatar is interactive and its appearance changes on\n focus, the change should not be disorienting or unexpected.\n- **Name, role, value:** Assistive technologies should be able to correctly\n interpret the role of the avatar (e.g., `image`, `link`) and its name (from\n the alt text).\n",
|
|
184
184
|
"toc": [
|
|
185
185
|
{
|
|
186
186
|
"value": "Accessibility",
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
]
|
|
207
207
|
},
|
|
208
208
|
"dev": {
|
|
209
|
-
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Avatar, type AvatarProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nThe simplest implementation displays an avatar with initials derived from first\nand last names:\n\n```jsx live-dev\nconst App = () => (\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n)\n```\n\n## Usage examples\n\n### Size options\n\nThe `2xs`, `xs`, and `md` size variants are available to match your interface\ndensity:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"2xs\" />\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"xs\" />\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"md\" />\n </Stack>\n)\n```\n\n### Color palettes\n\nAvatars support different color palettes to convey semantic meaning or match\nyour design theme:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar firstName=\"John\" lastName=\"Doe\" colorPalette=\"primary\" />\n <Avatar firstName=\"Jane\" lastName=\"Smith\" colorPalette=\"positive\" />\n <Avatar firstName=\"Alex\" lastName=\"Chen\" colorPalette=\"info\" />\n <Avatar firstName=\"Maria\" lastName=\"Garcia\" colorPalette=\"critical\" />\n <Avatar firstName=\"Sam\" lastName=\"Wilson\" colorPalette=\"cyan\" />\n </Stack>\n)\n```\n\n### With image\n\nWhen a `src` prop is provided, the Avatar displays the image. If the image fails\nto load, it automatically falls back to initials:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar\n firstName=\"Jane\"\n lastName=\"Smith\"\n src=\"https://i.pravatar.cc/150?img=1\"\n alt=\"Jane Smith's profile picture\"\n />\n <Avatar\n firstName=\"Alex\"\n lastName=\"Johnson\"\n src=\"https://i.pravatar.cc/150?img=2\"\n alt=\"Alex Johnson's profile picture\"\n />\n </Stack>\n)\n```\n\n### Initials fallback\n\nThe Avatar automatically extracts and displays initials from the provided first\nand last names. This serves as a fallback when no image is available or when the\nimage fails to load:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Avatar\n firstName=\"Maria\"\n lastName=\"Garcia\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n <Avatar\n firstName=\"Chen\"\n lastName=\"Wei\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n <Avatar\n firstName=\"Aisha\"\n lastName=\"Patel\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n </Stack>\n)\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Avatar component handles most accessibility requirements internally,\nincluding automatic labeling with the user's full name.\n\n- **Labeling**: The component automatically generates an internationalized\n accessible label from the `firstName` and `lastName` props. When providing an\n image via the `src` prop, always include an `alt` attribute for screen\n readers.\n- **Role**: Renders as a `<figure>` element with an `aria-label` that includes\n the full name (e.g., \"Avatar image for John Doe\").\n\nIf your use case requires tracking and analytics for this component, it is good\npractice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"example-avatar\";\n\nexport const Example = () => (\n <Avatar id={PERSISTENT_ID} firstName=\"John\" lastName=\"Doe\" />\n);\n```\n\n#### Keyboard navigation\n\nAvatars are non-interactive elements and do not receive focus or support\nkeyboard interaction by default. If you need to make an avatar interactive\n(e.g., clickable), wrap it in a button or interactive element:\n\n```tsx\n<button onClick={handleClick} aria-label=\"View profile\">\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n</button>\n```\n\n## API reference\n\n<PropsTable id=\"Avatar\" />\n\n## Common patterns\n\n### User profile header\n\nCombine Avatar with text components to create a user profile header:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar\n firstName=\"Sarah\"\n lastName=\"Johnson\"\n src=\"https://i.pravatar.cc/150?img=5\"\n size=\"md\"\n />\n <Stack direction=\"column\" gap=\"100\">\n <Text fontSize=\"md\" fontWeight=\"600\">Sarah Johnson</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Product Manager</Text>\n </Stack>\n </Stack>\n)\n```\n\n### Comment or message list\n\nDisplay avatars alongside user content in comments or messages:\n\n```jsx live-dev\nconst App = () => {\n const comments = [\n { id: 1, author: { firstName: \"Alex\", lastName: \"Chen\" }, text: \"Great work on this feature!\", time: \"2 hours ago\" },\n { id: 2, author: { firstName: \"Maria\", lastName: \"Garcia\" }, text: \"I agree, this is really helpful.\", time: \"1 hour ago\" },\n { id: 3, author: { firstName: \"James\", lastName: \"Wilson\" }, text: \"Looking forward to the next update.\", time: \"30 minutes ago\" },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n {comments.map((comment) => (\n <Stack key={comment.id} direction=\"row\" gap=\"400\">\n <Avatar\n firstName={comment.author.firstName}\n lastName={comment.author.lastName}\n size=\"xs\"\n />\n <Stack direction=\"column\" gap=\"100\" flex=\"1\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"baseline\">\n <Text fontSize=\"sm\" fontWeight=\"600\">\n {comment.author.firstName} {comment.author.lastName}\n </Text>\n <Text fontSize=\"xs\" color=\"neutral.11\">{comment.time}</Text>\n </Stack>\n <Text fontSize=\"sm\">{comment.text}</Text>\n </Stack>\n </Stack>\n ))}\n </Stack>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Avatar\nwithin your application. As the component's internal functionality is already\ntested by Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic rendering tests\n\nVerify the Avatar renders with expected elements and labels\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Basic rendering\", () => {\n it(\"renders avatar with initials\", () => {\n render(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"figure\")).toBeInTheDocument();\n expect(screen.getByLabelText(/John Doe/i)).toBeInTheDocument();\n expect(screen.getByText(\"JD\")).toBeInTheDocument();\n });\n\n it(\"renders avatar with image\", () => {\n render(\n <NimbusProvider>\n <Avatar\n firstName=\"Jane\"\n lastName=\"Smith\"\n src=\"https://example.com/avatar.jpg\"\n alt=\"Jane Smith profile\"\n />\n </NimbusProvider>\n );\n\n const image = screen.getByAltText(\"Jane Smith profile\");\n expect(image).toBeInTheDocument();\n expect(image).toHaveAttribute(\"src\", \"https://example.com/avatar.jpg\");\n });\n});\n```\n\n### Size variant tests\n\nTest different size options\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Size variants\", () => {\n it(\"renders different sizes correctly\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"2xs\" />\n </NimbusProvider>\n );\n\n let avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"xs\" />\n </NimbusProvider>\n );\n\n avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"md\" />\n </NimbusProvider>\n );\n\n avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n });\n});\n```\n\n### Accessibility tests\n\nVerify accessibility attributes and labeling\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Accessibility\", () => {\n it(\"has correct aria-label with full name\", () => {\n render(\n <NimbusProvider>\n <Avatar firstName=\"Maria\" lastName=\"Garcia\" />\n </NimbusProvider>\n );\n\n const avatar = screen.getByLabelText(/Maria Garcia/i);\n expect(avatar).toBeInTheDocument();\n expect(avatar.tagName).toBe(\"FIGURE\");\n });\n\n it(\"applies custom id for tracking\", () => {\n const PERSISTENT_ID = \"test-avatar-id\";\n\n render(\n <NimbusProvider>\n <Avatar id={PERSISTENT_ID} firstName=\"John\" lastName=\"Doe\" />\n </NimbusProvider>\n );\n\n const avatar = screen.getByRole(\"figure\");\n expect(avatar).toHaveAttribute(\"id\", PERSISTENT_ID);\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-avatar--docs)\n",
|
|
209
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Avatar, type AvatarProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nThe simplest implementation displays an avatar with initials derived from first\nand last names:\n\n```jsx live-dev\nconst App = () => (\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n)\n```\n\n## Usage examples\n\n### Size options\n\nThe `2xs`, `xs`, and `md` size variants are available to match your interface\ndensity:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"2xs\" />\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"xs\" />\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"md\" />\n </Stack>\n)\n```\n\n### Color palettes\n\nAvatars support different color palettes to convey semantic meaning or match\nyour design theme:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar firstName=\"John\" lastName=\"Doe\" colorPalette=\"primary\" />\n <Avatar firstName=\"Jane\" lastName=\"Smith\" colorPalette=\"positive\" />\n <Avatar firstName=\"Alex\" lastName=\"Chen\" colorPalette=\"info\" />\n <Avatar firstName=\"Maria\" lastName=\"Garcia\" colorPalette=\"critical\" />\n <Avatar firstName=\"Sam\" lastName=\"Wilson\" colorPalette=\"cyan\" />\n </Stack>\n)\n```\n\n### With image\n\nWhen a `src` prop is provided, the Avatar displays the image. If the image fails\nto load, it automatically falls back to initials:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar\n firstName=\"Jane\"\n lastName=\"Smith\"\n src=\"https://i.pravatar.cc/150?img=1\"\n alt=\"Jane Smith's profile picture\"\n />\n <Avatar\n firstName=\"Alex\"\n lastName=\"Johnson\"\n src=\"https://i.pravatar.cc/150?img=2\"\n alt=\"Alex Johnson's profile picture\"\n />\n </Stack>\n)\n```\n\n### Initials fallback\n\nThe Avatar automatically extracts and displays initials from the provided first\nand last names. This serves as a fallback when no image is available or when the\nimage fails to load:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Avatar\n firstName=\"Maria\"\n lastName=\"Garcia\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n <Avatar\n firstName=\"Chen\"\n lastName=\"Wei\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n <Avatar\n firstName=\"Aisha\"\n lastName=\"Patel\"\n src=\"https://www.gravatar.com/avatar/thisWill404?s=200&d=404\" />\n </Stack>\n)\n```\n\n### Missing or partial names\n\n`firstName` and `lastName` are both **optional**. The Avatar handles missing,\nempty, and whitespace-only values defensively:\n\n- If only one name is usable (after trimming whitespace), a single initial\n is rendered.\n- If neither name yields a usable character, a generic `Person` icon is\n rendered as the fallback and a localized generic `aria-label`\n (`\"User avatar\"`) is applied.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar firstName=\"John\" /> {/* renders \"J\" */}\n <Avatar lastName=\"Doe\" /> {/* renders \"D\" */}\n <Avatar firstName=\"\" lastName=\"\" /> {/* renders the Person icon */}\n <Avatar /> {/* renders the Person icon */}\n </Stack>\n)\n```\n\nThis is intended for cases where user records have incomplete name data —\ncommon in legacy systems and partial profiles. The component's TypeScript\ncontract permits `undefined` so call sites do not need non-null assertions\nor empty-string fallbacks.\n\nInitials extraction is also Unicode codepoint-safe (emoji and astral-plane\ncharacters are not split mid-surrogate) and trim-aware (leading/trailing\nwhitespace is discarded before extracting the first character).\n\n## Component requirements\n\n## Accessibility\n\nThe Avatar component handles most accessibility requirements internally,\nincluding automatic labeling with the user's full name.\n\n- **Labeling**: The component automatically generates an internationalized\n accessible label from the `firstName` and `lastName` props. When providing an\n image via the `src` prop, always include an `alt` attribute for screen\n readers.\n- **Role**: Renders as a `<figure>` element with an `aria-label` that includes\n the full name (e.g., \"Avatar image for John Doe\").\n\nIf your use case requires tracking and analytics for this component, it is good\npractice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"example-avatar\";\n\nexport const Example = () => (\n <Avatar id={PERSISTENT_ID} firstName=\"John\" lastName=\"Doe\" />\n);\n```\n\n#### Keyboard navigation\n\nAvatars are non-interactive elements and do not receive focus or support\nkeyboard interaction by default. If you need to make an avatar interactive\n(e.g., clickable), wrap it in a button or interactive element:\n\n```tsx\n<button onClick={handleClick} aria-label=\"View profile\">\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n</button>\n```\n\n## API reference\n\n<PropsTable id=\"Avatar\" />\n\n## Common patterns\n\n### User profile header\n\nCombine Avatar with text components to create a user profile header:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Avatar\n firstName=\"Sarah\"\n lastName=\"Johnson\"\n src=\"https://i.pravatar.cc/150?img=5\"\n size=\"md\"\n />\n <Stack direction=\"column\" gap=\"100\">\n <Text fontSize=\"md\" fontWeight=\"600\">Sarah Johnson</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Product Manager</Text>\n </Stack>\n </Stack>\n)\n```\n\n### Comment or message list\n\nDisplay avatars alongside user content in comments or messages:\n\n```jsx live-dev\nconst App = () => {\n const comments = [\n { id: 1, author: { firstName: \"Alex\", lastName: \"Chen\" }, text: \"Great work on this feature!\", time: \"2 hours ago\" },\n { id: 2, author: { firstName: \"Maria\", lastName: \"Garcia\" }, text: \"I agree, this is really helpful.\", time: \"1 hour ago\" },\n { id: 3, author: { firstName: \"James\", lastName: \"Wilson\" }, text: \"Looking forward to the next update.\", time: \"30 minutes ago\" },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n {comments.map((comment) => (\n <Stack key={comment.id} direction=\"row\" gap=\"400\">\n <Avatar\n firstName={comment.author.firstName}\n lastName={comment.author.lastName}\n size=\"xs\"\n />\n <Stack direction=\"column\" gap=\"100\" flex=\"1\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"baseline\">\n <Text fontSize=\"sm\" fontWeight=\"600\">\n {comment.author.firstName} {comment.author.lastName}\n </Text>\n <Text fontSize=\"xs\" color=\"neutral.11\">{comment.time}</Text>\n </Stack>\n <Text fontSize=\"sm\">{comment.text}</Text>\n </Stack>\n </Stack>\n ))}\n </Stack>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Avatar\nwithin your application. As the component's internal functionality is already\ntested by Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic rendering tests\n\nVerify the Avatar renders with expected elements and labels\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Basic rendering\", () => {\n it(\"renders avatar with initials\", () => {\n render(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"figure\")).toBeInTheDocument();\n expect(screen.getByLabelText(/John Doe/i)).toBeInTheDocument();\n expect(screen.getByText(\"JD\")).toBeInTheDocument();\n });\n\n it(\"renders avatar with image\", () => {\n render(\n <NimbusProvider>\n <Avatar\n firstName=\"Jane\"\n lastName=\"Smith\"\n src=\"https://example.com/avatar.jpg\"\n alt=\"Jane Smith profile\"\n />\n </NimbusProvider>\n );\n\n const image = screen.getByAltText(\"Jane Smith profile\");\n expect(image).toBeInTheDocument();\n expect(image).toHaveAttribute(\"src\", \"https://example.com/avatar.jpg\");\n });\n});\n```\n\n### Size variant tests\n\nTest different size options\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Size variants\", () => {\n it(\"renders different sizes correctly\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"2xs\" />\n </NimbusProvider>\n );\n\n let avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"xs\" />\n </NimbusProvider>\n );\n\n avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Avatar firstName=\"John\" lastName=\"Doe\" size=\"md\" />\n </NimbusProvider>\n );\n\n avatar = screen.getByRole(\"figure\");\n expect(avatar).toBeInTheDocument();\n });\n});\n```\n\n### Accessibility tests\n\nVerify accessibility attributes and labeling\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Accessibility\", () => {\n it(\"has correct aria-label with full name\", () => {\n render(\n <NimbusProvider>\n <Avatar firstName=\"Maria\" lastName=\"Garcia\" />\n </NimbusProvider>\n );\n\n const avatar = screen.getByLabelText(/Maria Garcia/i);\n expect(avatar).toBeInTheDocument();\n expect(avatar.tagName).toBe(\"FIGURE\");\n });\n\n it(\"applies custom id for tracking\", () => {\n const PERSISTENT_ID = \"test-avatar-id\";\n\n render(\n <NimbusProvider>\n <Avatar id={PERSISTENT_ID} firstName=\"John\" lastName=\"Doe\" />\n </NimbusProvider>\n );\n\n const avatar = screen.getByRole(\"figure\");\n expect(avatar).toHaveAttribute(\"id\", PERSISTENT_ID);\n });\n});\n```\n\n### Missing-name fallback tests\n\nVerify the Avatar renders a generic icon and label when\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Avatar, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Avatar - Missing names\", () => {\n it(\"renders the Person icon and generic aria-label when both names are missing\", () => {\n render(\n <NimbusProvider>\n <Avatar />\n </NimbusProvider>\n );\n\n const avatar = screen.getByRole(\"figure\");\n // Generic localized aria-label (\"Generic user avatar\" in English)\n expect(avatar).toHaveAttribute(\"aria-label\", \"Generic user avatar\");\n // Person icon is rendered as the visual fallback\n expect(avatar.querySelector(\"svg\")).not.toBeNull();\n // No initials text is rendered\n expect(avatar.textContent?.trim()).toBe(\"\");\n });\n\n it(\"renders a single initial when only firstName is provided\", () => {\n render(\n <NimbusProvider>\n <Avatar firstName=\"John\" />\n </NimbusProvider>\n );\n\n const avatar = screen.getByRole(\"figure\");\n expect(avatar.textContent?.trim()).toBe(\"J\");\n expect(avatar.querySelector(\"svg\")).toBeNull();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-avatar--docs)\n",
|
|
210
210
|
"toc": [
|
|
211
211
|
{
|
|
212
212
|
"value": "Getting started",
|
|
@@ -294,6 +294,17 @@
|
|
|
294
294
|
],
|
|
295
295
|
"parent": "root"
|
|
296
296
|
},
|
|
297
|
+
{
|
|
298
|
+
"value": "Missing or partial names",
|
|
299
|
+
"href": "#missing-or-partial-names",
|
|
300
|
+
"depth": 3,
|
|
301
|
+
"numbering": [
|
|
302
|
+
1,
|
|
303
|
+
2,
|
|
304
|
+
5
|
|
305
|
+
],
|
|
306
|
+
"parent": "root"
|
|
307
|
+
},
|
|
297
308
|
{
|
|
298
309
|
"value": "Component requirements",
|
|
299
310
|
"href": "#component-requirements",
|
|
@@ -411,6 +422,17 @@
|
|
|
411
422
|
],
|
|
412
423
|
"parent": "root"
|
|
413
424
|
},
|
|
425
|
+
{
|
|
426
|
+
"value": "Missing-name fallback tests",
|
|
427
|
+
"href": "#missing-name-fallback-tests",
|
|
428
|
+
"depth": 3,
|
|
429
|
+
"numbering": [
|
|
430
|
+
1,
|
|
431
|
+
7,
|
|
432
|
+
4
|
|
433
|
+
],
|
|
434
|
+
"parent": "root"
|
|
435
|
+
},
|
|
414
436
|
{
|
|
415
437
|
"value": "Resources",
|
|
416
438
|
"href": "#resources",
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"id": "Components-Region",
|
|
4
|
+
"title": "Region",
|
|
5
|
+
"exportName": "Region",
|
|
6
|
+
"description": "A headless primitive for named content projection — render content into a named region elsewhere in the layout while it stays in your React tree.",
|
|
7
|
+
"lifecycleState": "Beta",
|
|
8
|
+
"order": 999,
|
|
9
|
+
"repoPath": "packages/nimbus/src/components/region/region.mdx",
|
|
10
|
+
"menu": [
|
|
11
|
+
"Components",
|
|
12
|
+
"Utilities",
|
|
13
|
+
"Region"
|
|
14
|
+
],
|
|
15
|
+
"route": "components/utilities/region",
|
|
16
|
+
"tags": [
|
|
17
|
+
"component",
|
|
18
|
+
"utility",
|
|
19
|
+
"portal",
|
|
20
|
+
"layout",
|
|
21
|
+
"provider"
|
|
22
|
+
],
|
|
23
|
+
"toc": [
|
|
24
|
+
{
|
|
25
|
+
"value": "Overview",
|
|
26
|
+
"href": "#overview",
|
|
27
|
+
"depth": 2,
|
|
28
|
+
"numbering": [
|
|
29
|
+
1,
|
|
30
|
+
1
|
|
31
|
+
],
|
|
32
|
+
"parent": "root"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"value": "When to use",
|
|
36
|
+
"href": "#when-to-use",
|
|
37
|
+
"depth": 2,
|
|
38
|
+
"numbering": [
|
|
39
|
+
1,
|
|
40
|
+
2
|
|
41
|
+
],
|
|
42
|
+
"parent": "root"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"value": "When not to use",
|
|
46
|
+
"href": "#when-not-to-use",
|
|
47
|
+
"depth": 2,
|
|
48
|
+
"numbering": [
|
|
49
|
+
1,
|
|
50
|
+
3
|
|
51
|
+
],
|
|
52
|
+
"parent": "root"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"value": "Anatomy",
|
|
56
|
+
"href": "#anatomy",
|
|
57
|
+
"depth": 2,
|
|
58
|
+
"numbering": [
|
|
59
|
+
1,
|
|
60
|
+
4
|
|
61
|
+
],
|
|
62
|
+
"parent": "root"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"value": "Example",
|
|
66
|
+
"href": "#example",
|
|
67
|
+
"depth": 2,
|
|
68
|
+
"numbering": [
|
|
69
|
+
1,
|
|
70
|
+
5
|
|
71
|
+
],
|
|
72
|
+
"parent": "root"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"layout": "app-frame",
|
|
76
|
+
"tabs": [
|
|
77
|
+
{
|
|
78
|
+
"key": "overview",
|
|
79
|
+
"title": "Overview",
|
|
80
|
+
"order": 0
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"key": "dev",
|
|
84
|
+
"title": "Implementation",
|
|
85
|
+
"order": 3
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"mdx": "\n## Overview\n\nRegion is a headless primitive for **named content projection**: render content\ninto a slot somewhere else in the layout — an aside, a toolbar, a detail pane —\nfrom a component that lives somewhere else entirely, even a different package.\n\nIt is the structured form of `createPortal`. You **place a target** with\n`<Region name=\"…\" />` to mark where a region renders, and **fill it** from\nanywhere with `useRegion(name)`. Projected content paints at the target but stays\na child of its author in the React tree, so all ancestor context — routing, intl,\ndata clients, permissions — is preserved. A target can also publish a **value**\n(typically control callbacks plus state), so its host can hand consumers a way to\ndrive it.\n\nThe scope, `Region.Provider`, is mounted **ambiently inside `NimbusProvider`**, so\nin a Nimbus app you place targets and call `useRegion` without ever wrapping a\nprovider yourself.\n\nRegion renders no visual chrome of its own; it is a coordination primitive, not\na styled component.\n\n## When to use\n\n- A shell or layout owns a slot (a side panel, a header action area) that a\n feature elsewhere needs to fill.\n- Content must be **authored** deep in the tree (to keep its context) but\n **rendered** at a fixed location.\n- A target's host wants to expose controls (open/close, select) to whoever fills\n it, without prop-drilling.\n\nFor a user-resizable two-pane layout, compose Region with\n[Splitter](/components/layout/splitter): mount the splitter, put a\n`<Region name=\"…\" />` in the aside, and let a consumer fill it and toggle it — see\nthe implementation tab.\n\n## When not to use\n\n- You own both the content and its location and they're in the same place — just\n render it directly.\n- You need overlay portalling (modals, tooltips, popovers) — those are handled\n by the overlay components, not Region.\n\n## Anatomy\n\n- **`<Region name=\"…\" />`** — the **target**: marks where a named region renders\n and registers its DOM node. It is layout-transparent (`display: contents`), so\n projected content lays out as a direct child of the target's parent — no extra\n wrapper box. Backed by a `chakra.div`, so it accepts style props; pass\n `display=\"block\"` (etc.) if you want it to be a real box. Can publish a `value`.\n- **`useRegion(name)`** — returns a stable `Region` portal component to fill the\n named region, plus the `value` its target published. Resolves safely (`null`\n value, portal renders nothing) before a target mounts.\n- **`Region.Provider`** — the scope. Mounted ambiently by `NimbusProvider`;\n reuses an ancestor's scope when nested, so you rarely write it directly.\n\nNote the two sides share the name `Region`: the top-level `<Region name>` is the\n**target**, while the `Region` you get from `useRegion` is the **filler**. Place\nwith the former, fill with the latter.\n\n## Example\n\n```jsx live-dev\nconst Sidebar = () => {\n // fill the region — `Region` here is the filler from the hook\n const { Region: Filler } = useRegion(\"docs-sidebar\");\n return (\n <Filler>\n <Box p=\"300\" bg=\"teal.3\">\n Authored over here, painted in the target over there.\n </Box>\n </Filler>\n );\n};\n\nconst App = () => (\n // No Region.Provider needed — NimbusProvider supplies the scope ambiently.\n <Stack direction=\"row\" gap=\"400\">\n <Box flexGrow=\"1\" p=\"300\" bg=\"amber.3\">\n <Text>Content area</Text>\n <Sidebar />\n </Box>\n <Box width=\"240px\" minHeight=\"120px\" bg=\"neutral.3\" p=\"200\">\n <Region name=\"docs-sidebar\" />\n </Box>\n </Stack>\n);\n```\n",
|
|
90
|
+
"views": {
|
|
91
|
+
"overview": {
|
|
92
|
+
"mdx": "\n## Overview\n\nRegion is a headless primitive for **named content projection**: render content\ninto a slot somewhere else in the layout — an aside, a toolbar, a detail pane —\nfrom a component that lives somewhere else entirely, even a different package.\n\nIt is the structured form of `createPortal`. You **place a target** with\n`<Region name=\"…\" />` to mark where a region renders, and **fill it** from\nanywhere with `useRegion(name)`. Projected content paints at the target but stays\na child of its author in the React tree, so all ancestor context — routing, intl,\ndata clients, permissions — is preserved. A target can also publish a **value**\n(typically control callbacks plus state), so its host can hand consumers a way to\ndrive it.\n\nThe scope, `Region.Provider`, is mounted **ambiently inside `NimbusProvider`**, so\nin a Nimbus app you place targets and call `useRegion` without ever wrapping a\nprovider yourself.\n\nRegion renders no visual chrome of its own; it is a coordination primitive, not\na styled component.\n\n## When to use\n\n- A shell or layout owns a slot (a side panel, a header action area) that a\n feature elsewhere needs to fill.\n- Content must be **authored** deep in the tree (to keep its context) but\n **rendered** at a fixed location.\n- A target's host wants to expose controls (open/close, select) to whoever fills\n it, without prop-drilling.\n\nFor a user-resizable two-pane layout, compose Region with\n[Splitter](/components/layout/splitter): mount the splitter, put a\n`<Region name=\"…\" />` in the aside, and let a consumer fill it and toggle it — see\nthe implementation tab.\n\n## When not to use\n\n- You own both the content and its location and they're in the same place — just\n render it directly.\n- You need overlay portalling (modals, tooltips, popovers) — those are handled\n by the overlay components, not Region.\n\n## Anatomy\n\n- **`<Region name=\"…\" />`** — the **target**: marks where a named region renders\n and registers its DOM node. It is layout-transparent (`display: contents`), so\n projected content lays out as a direct child of the target's parent — no extra\n wrapper box. Backed by a `chakra.div`, so it accepts style props; pass\n `display=\"block\"` (etc.) if you want it to be a real box. Can publish a `value`.\n- **`useRegion(name)`** — returns a stable `Region` portal component to fill the\n named region, plus the `value` its target published. Resolves safely (`null`\n value, portal renders nothing) before a target mounts.\n- **`Region.Provider`** — the scope. Mounted ambiently by `NimbusProvider`;\n reuses an ancestor's scope when nested, so you rarely write it directly.\n\nNote the two sides share the name `Region`: the top-level `<Region name>` is the\n**target**, while the `Region` you get from `useRegion` is the **filler**. Place\nwith the former, fill with the latter.\n\n## Example\n\n```jsx live-dev\nconst Sidebar = () => {\n // fill the region — `Region` here is the filler from the hook\n const { Region: Filler } = useRegion(\"docs-sidebar\");\n return (\n <Filler>\n <Box p=\"300\" bg=\"teal.3\">\n Authored over here, painted in the target over there.\n </Box>\n </Filler>\n );\n};\n\nconst App = () => (\n // No Region.Provider needed — NimbusProvider supplies the scope ambiently.\n <Stack direction=\"row\" gap=\"400\">\n <Box flexGrow=\"1\" p=\"300\" bg=\"amber.3\">\n <Text>Content area</Text>\n <Sidebar />\n </Box>\n <Box width=\"240px\" minHeight=\"120px\" bg=\"neutral.3\" p=\"200\">\n <Region name=\"docs-sidebar\" />\n </Box>\n </Stack>\n);\n```\n",
|
|
93
|
+
"toc": [
|
|
94
|
+
{
|
|
95
|
+
"value": "Overview",
|
|
96
|
+
"href": "#overview",
|
|
97
|
+
"depth": 2,
|
|
98
|
+
"numbering": [
|
|
99
|
+
1,
|
|
100
|
+
1
|
|
101
|
+
],
|
|
102
|
+
"parent": "root"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"value": "When to use",
|
|
106
|
+
"href": "#when-to-use",
|
|
107
|
+
"depth": 2,
|
|
108
|
+
"numbering": [
|
|
109
|
+
1,
|
|
110
|
+
2
|
|
111
|
+
],
|
|
112
|
+
"parent": "root"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"value": "When not to use",
|
|
116
|
+
"href": "#when-not-to-use",
|
|
117
|
+
"depth": 2,
|
|
118
|
+
"numbering": [
|
|
119
|
+
1,
|
|
120
|
+
3
|
|
121
|
+
],
|
|
122
|
+
"parent": "root"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"value": "Anatomy",
|
|
126
|
+
"href": "#anatomy",
|
|
127
|
+
"depth": 2,
|
|
128
|
+
"numbering": [
|
|
129
|
+
1,
|
|
130
|
+
4
|
|
131
|
+
],
|
|
132
|
+
"parent": "root"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"value": "Example",
|
|
136
|
+
"href": "#example",
|
|
137
|
+
"depth": 2,
|
|
138
|
+
"numbering": [
|
|
139
|
+
1,
|
|
140
|
+
5
|
|
141
|
+
],
|
|
142
|
+
"parent": "root"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"dev": {
|
|
147
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Region, useRegion, type RegionProps } from \"@commercetools/nimbus\";\n```\n\n### Mental model\n\n`Region` is the structured form of `createPortal`. Three pieces:\n\n- **`<Region name=\"…\" />`** — the **target**: marks where a named region renders.\n It registers its DOM node under `name` and can publish a `value` (control\n callbacks + state) for consumers.\n- **`useRegion(name)`** — returns `{ Region, value }`: a stable `Region` portal\n component to **fill** the target, and the published `value`.\n- **`Region.Provider`** — the scope. **Mounted ambiently by `NimbusProvider`**, so\n in a Nimbus app you never write it: place targets and call `useRegion`\n directly. It is still exported for standalone use (tests, non-Nimbus roots).\n\nRegions are addressed by **name**, not by tree position — a consumer reaches a\nregion by name regardless of how deeply nested it is. `useRegion` resolves to a\n`null` value (and a portal that renders nothing) before a target mounts, so it is\nalways safe to call.\n\n> **One name, two sides.** The top-level `<Region name>` is the *target*; the\n> `Region` returned by `useRegion` is the *filler*. They can't share a binding in\n> one scope — alias the hook's: `const { Region: Sidebar } = useRegion(\"x\")`.\n\n> **Single occupancy.** A region name is one-to-one on both sides: **exactly one\n> target** and **at most one active filler** at a time. Two targets sharing a name\n> orphan one another (only the last to mount holds the slot); two fillers stack\n> their content into the one target in an undefined order. Both are bugs, so in\n> development each logs a `console.error` naming the region. The fix is always the\n> same — give each its own name. (The registry also releases a target's slot\n> owner-checked, so a stale duplicate unmounting never wipes the live region.)\n\n### Basic usage\n\nContent rendered through the `Region` filler paints at the target but stays a\nchild of its author in the React tree, so all ancestor context (router, intl,\ndata client, permissions) is preserved.\n\n```jsx live-dev\nconst Sidebar = () => {\n const { Region: Filler } = useRegion(\"getting-started-sidebar\");\n return (\n <Filler>\n <Box p=\"300\" bg=\"teal.3\">\n Authored here, painted in the target.\n </Box>\n </Filler>\n );\n};\n\n// No Region.Provider — NimbusProvider supplies the scope ambiently.\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Box flexGrow=\"1\" p=\"300\" bg=\"amber.3\">\n <Text>Content area</Text>\n <Sidebar />\n </Box>\n <Box width=\"240px\" minHeight=\"120px\" bg=\"neutral.3\" p=\"200\">\n <Region name=\"getting-started-sidebar\" />\n </Box>\n </Stack>\n);\n```\n\n## Layout: the target renders no box (`display: contents`)\n\nA portal needs a real DOM node as its container — there is no API to portal \"into\na position.\" So `<Region name>` must render *an* element. But it renders that\nelement with **`display: contents`**, which makes the element generate **no box of\nits own**: its children participate in the layout of the target's *parent*, as if\nthe wrapper were not there. Since projected content are DOM children of that\nelement, they lay out exactly where the target sits — a flex/grid parent treats\nthe projected root as a direct child. You get precise layout with no extra wrapper.\n\nConsequences worth knowing:\n\n- A `display: contents` element can't carry its own visual box — no background,\n padding, border, `position: relative`, or scroll container. Put those on the\n **projected content** (or the target's parent) instead.\n- The target is a `chakra.div`, so it accepts style props. To make it a real box\n again, override the display: `<Region name=\"x\" display=\"block\" p=\"400\" />`.\n\n## Publishing a value\n\nA target can publish a `value` so its host hands consumers a way to drive it —\ntypically control callbacks plus reactive state. Consumers read it from\n`useRegion(name).value`.\n\n```tsx\n// host: publish controls on the target\n<Region name=\"detail\" value={{ isOpen, open, close }} />;\n\n// consumer: read them\nconst { value } = useRegion<DetailControls>(\"detail\");\nvalue?.open();\n```\n\nType the value at the consumer boundary with `useRegion<T>` — the registry treats\nit as opaque, so the generic is where the shape is declared.\n\n## Composition: a shell-owned side panel (with Splitter)\n\n`Region` composes with [Splitter](/components/layout/splitter) to build a\nshell-owned, remotely-controlled side panel — **without the Splitter depending on\nRegion**. The shell mounts the splitter, puts a `<Region name>` in the aside that\npublishes the collapse controls, and drives the splitter through its existing\ncontrolled `collapsed` / `onCollapsedChange`. A consumer anywhere fills the pane\nand opens/closes it via `useRegion`.\n\n```tsx\nconst ShellSidePanel = ({ children }) => {\n const [open, setOpen] = useState(false);\n\n // Stable callbacks; value memoized on the reactive bit (see Performance below).\n const commands = useRef({\n expand: () => setOpen(true),\n collapse: () => setOpen(false),\n toggle: () => setOpen((o) => !o),\n }).current;\n const controller = useMemo(\n () => ({ isCollapsed: !open, ...commands }),\n [open, commands]\n );\n\n return (\n <Splitter.Root collapsible collapsed={!open} onCollapsedChange={(c) => setOpen(!c)}>\n <Splitter.Main>{children}</Splitter.Main>\n <Splitter.Handle />\n <Splitter.Aside>\n <Region name=\"app-side-panel\" value={controller} />\n </Splitter.Aside>\n </Splitter.Root>\n );\n};\n\n// A consumer that owns no splitter markup:\nconst SidePanel = ({ panel }) => {\n const { Region: Aside, value } = useRegion(\"app-side-panel\");\n const expand = value?.expand;\n const collapse = value?.collapse;\n useEffect(() => {\n expand?.();\n return () => collapse?.();\n }, [expand, collapse]);\n return <Aside>{panel}</Aside>;\n};\n```\n\n## Performance\n\n`Region.Provider` wraps the entire application (it's inside `NimbusProvider`), so\nfilling a region must not re-render the app. That holds because of four things —\none in the primitive, three at your call site:\n\n1. **External store (primitive).** The registry is read via\n `useSyncExternalStore`. Publishing a node or value notifies only that name's\n consumers; `Region.Provider` itself never re-renders.\n2. **Stable `children` (host).** Pass the app through as a `children` prop so the\n host's own state changes (e.g. a collapse toggle) bail out of the app subtree\n rather than re-rendering it.\n3. **Stable callbacks + memoized value (host).** Keep published callbacks\n ref-backed so their identity never changes, and `useMemo` the value object on\n its reactive field. Registering then only churns when something meaningful\n changes.\n4. **Depend on the callbacks, not the value object (consumer).** A published\n value object changes identity when its reactive state changes. In effects,\n depend on the stable callbacks (`value?.expand`), not the whole `value` —\n otherwise an expand-on-mount effect re-runs on every state change and can\n ping-pong.\n\n## SSR\n\n`useRegion` and the `Region` filler use a server snapshot of `null`: there are no\ntargets during server rendering and `createPortal` is client-only. A region\ntherefore resolves to nothing on the server and hydrates on the client. Author\ncontent unconditionally and let the portal render it once the target mounts.\n\n## Nested providers share one namespace\n\n`Region.Provider` reuses an ancestor's registry when nested — it does not create\nan isolated scope. Two targets with the same name collide even if they live under\ndifferent `Region.Provider` wrappers. If you need logical isolation between\nsubsystems, use a naming convention (e.g. `\"app-shell:sidebar\"` vs\n`\"admin:sidebar\"`).\n\n## When not to use\n\n- You own both the content and its location and they're co-located — render it\n directly.\n- You need overlay portalling (modals, tooltips, popovers) — use the overlay\n components, not `Region`.\n",
|
|
148
|
+
"toc": [
|
|
149
|
+
{
|
|
150
|
+
"value": "Getting started",
|
|
151
|
+
"href": "#getting-started",
|
|
152
|
+
"depth": 2,
|
|
153
|
+
"numbering": [
|
|
154
|
+
1,
|
|
155
|
+
1
|
|
156
|
+
],
|
|
157
|
+
"parent": "root"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"value": "Import",
|
|
161
|
+
"href": "#import",
|
|
162
|
+
"depth": 3,
|
|
163
|
+
"numbering": [
|
|
164
|
+
1,
|
|
165
|
+
1,
|
|
166
|
+
1
|
|
167
|
+
],
|
|
168
|
+
"parent": "root"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"value": "Mental model",
|
|
172
|
+
"href": "#mental-model",
|
|
173
|
+
"depth": 3,
|
|
174
|
+
"numbering": [
|
|
175
|
+
1,
|
|
176
|
+
1,
|
|
177
|
+
2
|
|
178
|
+
],
|
|
179
|
+
"parent": "root"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"value": "Basic usage",
|
|
183
|
+
"href": "#basic-usage",
|
|
184
|
+
"depth": 3,
|
|
185
|
+
"numbering": [
|
|
186
|
+
1,
|
|
187
|
+
1,
|
|
188
|
+
3
|
|
189
|
+
],
|
|
190
|
+
"parent": "root"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"value": "Layout: the target renders no box (display: contents)",
|
|
194
|
+
"href": "#layout-the-target-renders-no-box-display-contents",
|
|
195
|
+
"depth": 2,
|
|
196
|
+
"numbering": [
|
|
197
|
+
1,
|
|
198
|
+
2
|
|
199
|
+
],
|
|
200
|
+
"parent": "root"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"value": "Publishing a value",
|
|
204
|
+
"href": "#publishing-a-value",
|
|
205
|
+
"depth": 2,
|
|
206
|
+
"numbering": [
|
|
207
|
+
1,
|
|
208
|
+
3
|
|
209
|
+
],
|
|
210
|
+
"parent": "root"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"value": "Composition: a shell-owned side panel (with Splitter)",
|
|
214
|
+
"href": "#composition-a-shell-owned-side-panel-with-splitter",
|
|
215
|
+
"depth": 2,
|
|
216
|
+
"numbering": [
|
|
217
|
+
1,
|
|
218
|
+
4
|
|
219
|
+
],
|
|
220
|
+
"parent": "root"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"value": "Performance",
|
|
224
|
+
"href": "#performance",
|
|
225
|
+
"depth": 2,
|
|
226
|
+
"numbering": [
|
|
227
|
+
1,
|
|
228
|
+
5
|
|
229
|
+
],
|
|
230
|
+
"parent": "root"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"value": "SSR",
|
|
234
|
+
"href": "#ssr",
|
|
235
|
+
"depth": 2,
|
|
236
|
+
"numbering": [
|
|
237
|
+
1,
|
|
238
|
+
6
|
|
239
|
+
],
|
|
240
|
+
"parent": "root"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"value": "Nested providers share one namespace",
|
|
244
|
+
"href": "#nested-providers-share-one-namespace",
|
|
245
|
+
"depth": 2,
|
|
246
|
+
"numbering": [
|
|
247
|
+
1,
|
|
248
|
+
7
|
|
249
|
+
],
|
|
250
|
+
"parent": "root"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"value": "When not to use",
|
|
254
|
+
"href": "#when-not-to-use",
|
|
255
|
+
"depth": 2,
|
|
256
|
+
"numbering": [
|
|
257
|
+
1,
|
|
258
|
+
8
|
|
259
|
+
],
|
|
260
|
+
"parent": "root"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|