@commercetools/nimbus-mcp 3.2.0 → 3.3.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/README.md +9 -1
- package/data/docs/route-manifest.json +806 -41
- package/data/docs/routes/components-buttons-button.json +25 -2
- package/data/docs/routes/components-chat-chat-message-list.json +448 -0
- package/data/docs/routes/components-chat-chat-message.json +580 -0
- package/data/docs/routes/components-content-markdown.json +548 -0
- package/data/docs/routes/components-feedback-activity-indicator.json +396 -0
- package/data/docs/routes/components-feedback-tooltip.json +23 -1
- package/data/docs/routes/components-inputs-drop-zone.json +562 -0
- package/data/docs/routes/components-inputs-file-trigger.json +421 -0
- package/data/docs/routes/components-layout-splitter.json +3 -3
- package/data/docs/routes/components-media-avatar.json +16 -5
- package/data/docs/routes/components-navigation-menu.json +1 -1
- package/data/docs/routes/components-navigation-tabnav.json +80 -14
- package/data/docs/routes/components-navigation-tabs.json +44 -11
- package/data/docs/routes/components-navigation-tree.json +445 -0
- package/data/docs/routes/home-getting-started-mcp-server-overview.json +2 -2
- package/data/docs/routes/home-getting-started-mcp-server-setup.json +2 -2
- package/data/docs/routes/patterns-buttons-floating-action-button.json +392 -0
- package/data/docs/routes/patterns-buttons.json +58 -0
- package/data/docs/search-index.json +1 -1
- package/data/docs/types/AccordionContent.json +24 -24
- package/data/docs/types/AccordionHeader.json +88 -88
- package/data/docs/types/AccordionItem.json +20 -20
- package/data/docs/types/AccordionRoot.json +14 -14
- package/data/docs/types/ActivityIndicator.json +202 -0
- package/data/docs/types/AlertDescription.json +8 -8
- package/data/docs/types/AlertDismissButton.json +98 -81
- package/data/docs/types/AlertTitle.json +8 -8
- package/data/docs/types/Avatar.json +19 -2
- 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 +106 -89
- package/data/docs/types/Calendar.json +56 -56
- package/data/docs/types/Caption.json +6 -6
- package/data/docs/types/CardRoot.json +2 -2
- package/data/docs/types/Cell.json +6 -6
- package/data/docs/types/ChatMessage.json +12 -0
- package/data/docs/types/ChatMessageActions.json +42 -0
- package/data/docs/types/ChatMessageAvatar.json +140 -0
- package/data/docs/types/ChatMessageBody.json +42 -0
- package/data/docs/types/ChatMessageList.json +12 -0
- package/data/docs/types/ChatMessageListItem.json +42 -0
- package/data/docs/types/ChatMessageListRoot.json +78 -0
- package/data/docs/types/ChatMessageMeta.json +42 -0
- package/data/docs/types/ChatMessageRoot.json +119 -0
- package/data/docs/types/ChatMessageTyping.json +42 -0
- package/data/docs/types/Checkbox.json +86 -86
- package/data/docs/types/Code.json +11 -11
- 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 +70 -70
- package/data/docs/types/ComboBoxOption.json +64 -64
- package/data/docs/types/ComboBoxPopover.json +91 -70
- package/data/docs/types/ComboBoxRoot.json +8 -8
- package/data/docs/types/ComboBoxSection.json +24 -24
- package/data/docs/types/ComboBoxTrigger.json +6 -6
- package/data/docs/types/DataTable.json +2 -2
- package/data/docs/types/DataTableBody.json +20 -20
- package/data/docs/types/DataTableHeader.json +25 -25
- package/data/docs/types/DataTableRoot.json +2 -2
- package/data/docs/types/DataTableTable.json +6 -6
- package/data/docs/types/DateInput.json +73 -73
- package/data/docs/types/DatePicker.json +58 -58
- package/data/docs/types/DateRangePicker.json +86 -86
- package/data/docs/types/DateRangePickerField.json +86 -86
- package/data/docs/types/DefaultPageRoot.json +2 -2
- package/data/docs/types/DialogCloseTrigger.json +96 -79
- package/data/docs/types/DraggableListField.json +88 -60
- package/data/docs/types/DraggableListItem.json +52 -52
- package/data/docs/types/DraggableListRoot.json +88 -60
- package/data/docs/types/DrawerCloseTrigger.json +96 -79
- package/data/docs/types/DropZone.json +161 -0
- package/data/docs/types/FieldErrors.json +2 -2
- package/data/docs/types/FileTrigger.json +130 -0
- package/data/docs/types/Flex.json +22 -22
- package/data/docs/types/FloatingActionButton.json +1036 -0
- package/data/docs/types/Footer.json +6 -6
- package/data/docs/types/FormActionBar.json +1 -1
- 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 +106 -89
- package/data/docs/types/IconToggleButton.json +71 -71
- package/data/docs/types/Image.json +43 -43
- package/data/docs/types/Indicator.json +6 -6
- 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 +14 -14
- package/data/docs/types/ListIndicator.json +6 -6
- package/data/docs/types/ListItem.json +6 -6
- package/data/docs/types/ListRoot.json +11 -11
- package/data/docs/types/LoadingSpinner.json +2 -2
- package/data/docs/types/MakeElementFocusable.json +18 -18
- package/data/docs/types/Markdown.json +183 -0
- package/data/docs/types/MenuItem.json +62 -62
- package/data/docs/types/MenuRoot.json +55 -55
- package/data/docs/types/MenuSection.json +30 -30
- package/data/docs/types/MenuSubmenuTrigger.json +4 -4
- package/data/docs/types/MenuTrigger.json +88 -88
- package/data/docs/types/MultilineTextInput.json +104 -104
- package/data/docs/types/MultilineTextInputField.json +102 -102
- package/data/docs/types/NumberInput.json +86 -86
- package/data/docs/types/NumberInputField.json +82 -82
- package/data/docs/types/PageContentColumn.json +6 -6
- package/data/docs/types/PageContentRoot.json +6 -6
- package/data/docs/types/PasswordInput.json +100 -100
- package/data/docs/types/PasswordInputField.json +100 -100
- package/data/docs/types/ProgressBar.json +13 -13
- package/data/docs/types/RadioInputOption.json +56 -56
- package/data/docs/types/RadioInputRoot.json +48 -48
- package/data/docs/types/RangeCalendar.json +58 -58
- package/data/docs/types/Region.json +6 -6
- package/data/docs/types/RegionTarget.json +6 -6
- package/data/docs/types/RichTextInput.json +2 -2
- package/data/docs/types/Root.json +11 -11
- package/data/docs/types/Row.json +6 -6
- package/data/docs/types/ScrollArea.json +2 -2
- package/data/docs/types/SearchInput.json +106 -106
- package/data/docs/types/SearchInputField.json +102 -102
- package/data/docs/types/SelectOption.json +54 -54
- package/data/docs/types/SelectOptionGroup.json +18 -18
- package/data/docs/types/SelectOptions.json +64 -64
- package/data/docs/types/SelectRoot.json +92 -92
- package/data/docs/types/Separator.json +4 -4
- package/data/docs/types/SimpleGrid.json +28 -28
- package/data/docs/types/SlidingIndicatorGeometry.json +9 -0
- package/data/docs/types/SlidingIndicatorRects.json +9 -0
- package/data/docs/types/SplitButton.json +16 -20
- package/data/docs/types/Stack.json +2 -2
- package/data/docs/types/StepsRoot.json +2 -2
- package/data/docs/types/Switch.json +34 -34
- package/data/docs/types/TabNavItem.json +5 -5
- package/data/docs/types/TabNavRoot.json +5 -5
- 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 +19 -19
- package/data/docs/types/TableRow.json +6 -6
- package/data/docs/types/TableScrollArea.json +6 -6
- package/data/docs/types/TabsList.json +2 -2
- package/data/docs/types/TabsPanel.json +2 -2
- package/data/docs/types/TabsPanels.json +2 -2
- package/data/docs/types/TabsRoot.json +3 -3
- package/data/docs/types/TabsTab.json +2 -2
- package/data/docs/types/TagGroupRoot.json +24 -24
- package/data/docs/types/TagGroupTag.json +56 -56
- package/data/docs/types/TagGroupTagList.json +14 -14
- package/data/docs/types/Text.json +8 -8
- package/data/docs/types/TextInput.json +102 -102
- package/data/docs/types/TextInputField.json +100 -100
- package/data/docs/types/TimeInput.json +68 -68
- package/data/docs/types/ToggleButton.json +73 -73
- package/data/docs/types/ToggleButtonGroupButton.json +30 -30
- package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
- package/data/docs/types/Toolbar.json +9 -9
- package/data/docs/types/TooltipContent.json +28 -28
- package/data/docs/types/TooltipRoot.json +16 -16
- package/data/docs/types/Tree.json +12 -0
- package/data/docs/types/TreeIndicator.json +828 -0
- package/data/docs/types/TreeItem.json +559 -0
- package/data/docs/types/TreeItemContent.json +27 -0
- package/data/docs/types/TreeRoot.json +636 -0
- package/data/docs/types/TreeSubTree.json +40 -0
- package/data/docs/types/VisuallyHidden.json +6 -6
- package/data/docs/types/createItemsFromCsvDrop.json +4 -4
- package/data/docs/types/createItemsFromDirectoryDrop.json +4 -4
- package/data/docs/types/createItemsFromFileDrop.json +4 -4
- package/data/docs/types/createItemsFromImageDrop.json +4 -4
- package/data/docs/types/createItemsFromJsonDrop.json +4 -4
- package/data/docs/types/manifest.json +25 -0
- package/data/docs/types/useRegion.json +2 -2
- package/data/docs/types/useSlidingIndicator.json +145 -0
- package/data/docs/types/useTree.json +267 -0
- package/data/tokens.json +21 -0
- package/data/uikit-tokens.json +894 -0
- package/dist/index.js +3298 -109
- package/package.json +8 -6
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
]
|
|
376
376
|
},
|
|
377
377
|
"dev": {
|
|
378
|
-
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Button, type ButtonProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe Button component wraps React Aria's `useButton` hook to provide accessible, interactive elements.\n\n```jsx live-dev\nconst App = () => (\n <Button onPress={() => alert('Button pressed')}>\n Action\n </Button>\n)\n```\n\n## Usage examples\n\n### Size options\n\nButton supports three size variants to match different interface densities. The `md` size is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Button size=\"2xs\">2XS Button</Button>\n <Button size=\"xs\">XS Button</Button>\n <Button size=\"sm\">SM Button</Button>\n <Button size=\"md\">MD Button (Default)</Button>\n </Stack>\n)\n```\n\n### Visual variants\n\nChoose between five visual variants to match your design context. The `subtle` variant is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button variant=\"solid\">Solid</Button>\n <Button variant=\"subtle\">Subtle (Default)</Button>\n <Button variant=\"outline\">Outline</Button>\n <Button variant=\"ghost\">Ghost</Button>\n <Button variant=\"link\">Link</Button>\n </Stack>\n)\n```\n\n### Color palette options\n\nThe Button component supports all semantic color palettes to convey meaning and hierarchy. The `primary` color palette is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button colorPalette=\"primary\">Primary (Default)</Button>\n <Button colorPalette=\"neutral\">Neutral</Button>\n <Button colorPalette=\"info\">Info</Button>\n </Stack>\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button colorPalette=\"positive\">Positive</Button>\n <Button colorPalette=\"warning\">Warning</Button>\n <Button colorPalette=\"critical\">Critical</Button>\n </Stack>\n </Stack>\n)\n```\n\nYou can also override the color palette using Chakra UI style props for one-off customizations:\n\n```jsx live-dev\nconst App = () => (\n <Button backgroundColor=\"purple.9\" color=\"purple.contrast\" _hover={{ backgroundColor: \"purple.10\" }}>\n Custom Purple Button\n </Button>\n)\n```\n\n### With icons\n\nButtons automatically handle spacing between icons and text. You can place icons before or after the text.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Button>\n <Icons.Add />\n Add Item\n </Button>\n <Button variant=\"outline\">\n Next\n <Icons.ArrowForward />\n </Button>\n </Stack>\n)\n```\n\n### As link\n\nUse the `as` prop to render the button as an anchor tag while maintaining button styling. This is useful for navigation links that need to look like actions.\n\n```jsx live-dev\nconst App = () => (\n <Button as=\"a\" href=\"#\" variant=\"link\">\n Go to Home\n </Button>\n)\n```\n\n### Disabled state\n\nUse the `isDisabled` prop to prevent interaction. This visualizes the button as disabled and removes it from the tab order.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Button isDisabled variant=\"solid\">Disabled Solid</Button>\n <Button isDisabled variant=\"outline\">Disabled Outline</Button>\n </Stack>\n)\n```\n\n### Event handling\n\nUse `onPress` instead of `onClick` for consistent behavior across mouse, touch, and keyboard interactions.\n\n```jsx live-dev\nconst App = () => {\n const [count, setCount] = useState(0);\n\n return (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Button onPress={() => setCount(c => c + 1)}>\n Increment\n </Button>\n <Text>Count: {count}</Text>\n </Stack>\n );\n}\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Button component handles most accessibility requirements internally via React Aria.\n\n- **Labeling**: Ensure the button has a visible label (children) or an `aria-label` if it contains only an icon.\n- **Role**: Renders as `button` by default, or `link` (a tag) when using `as=\"a\"`.\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"submit-order-button\";\n\nexport const Example = () => (\n <Button id={PERSISTENT_ID}>Submit</Button>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab`: Focuses the button.\n- `Enter` or `Space`: Activates the button (triggers `onPress`).\n\n## API reference\n\n<PropsTable id=\"Button\" />\n\n## Common patterns\n\n### Loading state\n\nWhile the Button component doesn't have a built-in `isLoading` prop yet, you can implement a loading state by disabling the button and showing a spinner.\n\n```jsx live-dev\nconst App = () => {\n const [isLoading, setIsLoading] = useState(false);\n\n const handleClick = () => {\n setIsLoading(true);\n // Simulate async action\n setTimeout(() => setIsLoading(false), 2000);\n };\n\n return (\n <Button \n isDisabled={isLoading} \n onPress={handleClick}\n >\n {isLoading ? 'Loading...' : 'Save Changes'}\n </Button>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Button in your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Rendering Tests\n\nVerify the Button renders with expected content and attributes\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Basic rendering\", () => {\n it(\"renders with correct text\", () => {\n render(\n <NimbusProvider>\n <Button>Click me</Button>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /click me/i })\n ).toBeInTheDocument();\n });\n\n it(\"renders with custom data attributes\", () => {\n render(\n <NimbusProvider>\n <Button data-testid=\"my-button\">Action</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByTestId(\"my-button\")).toBeInTheDocument();\n });\n\n it(\"renders as an anchor element when using the as prop\", () => {\n render(\n <NimbusProvider>\n <Button as=\"a\" href=\"/home\" data-testid=\"link-button\">\n Go Home\n </Button>\n </NimbusProvider>\n );\n\n const element = screen.getByTestId(\"link-button\");\n expect(element.tagName).toBe(\"A\");\n expect(element).toHaveAttribute(\"href\", \"/home\");\n });\n});\n```\n\n### Interaction Tests\n\nTest user interactions with the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Interactions\", () => {\n it(\"calls onPress when clicked\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Action</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /action/i });\n await user.click(button);\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n\n it(\"does not call onPress when disabled\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button isDisabled onPress={handlePress}>\n Action\n </Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /action/i });\n await user.click(button);\n\n expect(handlePress).not.toHaveBeenCalled();\n });\n});\n```\n\n### Keyboard Navigation Tests\n\nTest keyboard interactions with the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Keyboard navigation\", () => {\n it(\"is focusable with Tab key\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <Button>Focus me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /focus me/i });\n await user.tab();\n\n expect(button).toHaveFocus();\n });\n\n it(\"can be activated with Enter key\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Press me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /press me/i });\n button.focus();\n\n await user.keyboard(\"{Enter}\");\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n\n it(\"can be activated with Space key\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Press me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /press me/i });\n button.focus();\n\n await user.keyboard(\" \");\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n});\n```\n\n### State Tests\n\nTest different states of the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - States\", () => {\n it(\"applies disabled state on native button\", () => {\n render(\n <NimbusProvider>\n <Button isDisabled>Disabled</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /disabled/i });\n expect(button).toBeDisabled();\n });\n\n it(\"is not focusable when disabled\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <Button isDisabled>Cannot focus</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /cannot focus/i });\n await user.tab();\n\n expect(button).not.toHaveFocus();\n });\n});\n```\n\n### Visual Variant Tests\n\nTest rendering with different visual variants and sizes\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Visual variants\", () => {\n it(\"renders with different size variants\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Button size=\"sm\">Small</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Button size=\"md\">Medium</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n });\n\n it(\"renders with different visual variants\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Button variant=\"solid\">Solid</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Button variant=\"outline\">Outline</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n });\n\n it(\"renders with color palettes\", () => {\n render(\n <NimbusProvider>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Primary\n </Button>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /primary/i })\n ).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-buttons-button--docs)\n- [React Aria Button](https://react-spectrum.adobe.com/react-aria/Button.html)\n\n",
|
|
378
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Button, type ButtonProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe Button component wraps React Aria's `useButton` hook to provide accessible, interactive elements.\n\n```jsx live-dev\nconst App = () => (\n <Button onPress={() => alert('Button pressed')}>\n Action\n </Button>\n)\n```\n\n## Usage examples\n\n### Size options\n\nButton supports three size variants to match different interface densities. The `md` size is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Button size=\"2xs\">2XS Button</Button>\n <Button size=\"xs\">XS Button</Button>\n <Button size=\"sm\">SM Button</Button>\n <Button size=\"md\">MD Button (Default)</Button>\n </Stack>\n)\n```\n\n### Visual variants\n\nChoose between five visual variants to match your design context. The `subtle` variant is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button variant=\"solid\">Solid</Button>\n <Button variant=\"subtle\">Subtle (Default)</Button>\n <Button variant=\"outline\">Outline</Button>\n <Button variant=\"ghost\">Ghost</Button>\n <Button variant=\"link\">Link</Button>\n </Stack>\n)\n```\n\n### Color palette options\n\nThe Button component supports all semantic color palettes to convey meaning and hierarchy. The `primary` color palette is the default.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button colorPalette=\"primary\">Primary (Default)</Button>\n <Button colorPalette=\"neutral\">Neutral</Button>\n <Button colorPalette=\"info\">Info</Button>\n </Stack>\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Button colorPalette=\"positive\">Positive</Button>\n <Button colorPalette=\"warning\">Warning</Button>\n <Button colorPalette=\"critical\">Critical</Button>\n </Stack>\n </Stack>\n)\n```\n\nYou can also override the color palette using Chakra UI style props for one-off customizations:\n\n```jsx live-dev\nconst App = () => (\n <Button backgroundColor=\"purple.9\" color=\"purple.contrast\" _hover={{ backgroundColor: \"purple.10\" }}>\n Custom Purple Button\n </Button>\n)\n```\n\n### With icons\n\nButtons automatically handle spacing between icons and text. You can place icons before or after the text.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Button>\n <Icons.Add />\n Add Item\n </Button>\n <Button variant=\"outline\">\n Next\n <Icons.ArrowForward />\n </Button>\n </Stack>\n)\n```\n\n### As link\n\nUse the `as` prop to render the button as an anchor tag while maintaining button styling. This is useful for navigation links that need to look like actions.\n\n```jsx live-dev\nconst App = () => (\n <Button as=\"a\" href=\"#\" variant=\"link\">\n Go to Home\n </Button>\n)\n```\n\n### Disabled state\n\nUse the `isDisabled` prop to prevent interaction. This visualizes the button as disabled and removes it from the tab order.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\">\n <Button isDisabled variant=\"solid\">Disabled Solid</Button>\n <Button isDisabled variant=\"outline\">Disabled Outline</Button>\n </Stack>\n)\n```\n\n#### Keeping a disabled button focusable (for tooltips)\n\nA natively disabled button cannot receive focus or hover, so it cannot host a\n`Tooltip` — exactly when you'd want one to explain why the action is\nunavailable. Add `allowFocusWhenDisabled` alongside `isDisabled` to keep the\nbutton focusable and hoverable while it stays disabled: it renders\n`aria-disabled` instead of the native `disabled` attribute, stays in the tab\norder, and suppresses activation (press, click, `Enter`/`Space`, form submit,\nand link navigation).\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button isDisabled allowFocusWhenDisabled>\n Publish\n </Button>\n <Tooltip.Content>\n Complete all required fields before publishing.\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\n### Event handling\n\nUse `onPress` instead of `onClick` for consistent behavior across mouse, touch, and keyboard interactions.\n\n```jsx live-dev\nconst App = () => {\n const [count, setCount] = useState(0);\n\n return (\n <Stack direction=\"row\" gap=\"400\" alignItems=\"center\">\n <Button onPress={() => setCount(c => c + 1)}>\n Increment\n </Button>\n <Text>Count: {count}</Text>\n </Stack>\n );\n}\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Button component handles most accessibility requirements internally via React Aria.\n\n- **Labeling**: Ensure the button has a visible label (children) or an `aria-label` if it contains only an icon.\n- **Role**: Renders as `button` by default, or `link` (a tag) when using `as=\"a\"`.\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"submit-order-button\";\n\nexport const Example = () => (\n <Button id={PERSISTENT_ID}>Submit</Button>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab`: Focuses the button.\n- `Enter` or `Space`: Activates the button (triggers `onPress`).\n\n## API reference\n\n<PropsTable id=\"Button\" />\n\n## Common patterns\n\n### Loading state\n\nWhile the Button component doesn't have a built-in `isLoading` prop yet, you can implement a loading state by disabling the button and showing a spinner.\n\n```jsx live-dev\nconst App = () => {\n const [isLoading, setIsLoading] = useState(false);\n\n const handleClick = () => {\n setIsLoading(true);\n // Simulate async action\n setTimeout(() => setIsLoading(false), 2000);\n };\n\n return (\n <Button \n isDisabled={isLoading} \n onPress={handleClick}\n >\n {isLoading ? 'Loading...' : 'Save Changes'}\n </Button>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Button in your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Rendering Tests\n\nVerify the Button renders with expected content and attributes\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Basic rendering\", () => {\n it(\"renders with correct text\", () => {\n render(\n <NimbusProvider>\n <Button>Click me</Button>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /click me/i })\n ).toBeInTheDocument();\n });\n\n it(\"renders with custom data attributes\", () => {\n render(\n <NimbusProvider>\n <Button data-testid=\"my-button\">Action</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByTestId(\"my-button\")).toBeInTheDocument();\n });\n\n it(\"renders as an anchor element when using the as prop\", () => {\n render(\n <NimbusProvider>\n <Button as=\"a\" href=\"/home\" data-testid=\"link-button\">\n Go Home\n </Button>\n </NimbusProvider>\n );\n\n const element = screen.getByTestId(\"link-button\");\n expect(element.tagName).toBe(\"A\");\n expect(element).toHaveAttribute(\"href\", \"/home\");\n });\n});\n```\n\n### Interaction Tests\n\nTest user interactions with the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Interactions\", () => {\n it(\"calls onPress when clicked\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Action</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /action/i });\n await user.click(button);\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n\n it(\"does not call onPress when disabled\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button isDisabled onPress={handlePress}>\n Action\n </Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /action/i });\n await user.click(button);\n\n expect(handlePress).not.toHaveBeenCalled();\n });\n});\n```\n\n### Keyboard Navigation Tests\n\nTest keyboard interactions with the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Keyboard navigation\", () => {\n it(\"is focusable with Tab key\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <Button>Focus me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /focus me/i });\n await user.tab();\n\n expect(button).toHaveFocus();\n });\n\n it(\"can be activated with Enter key\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Press me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /press me/i });\n button.focus();\n\n await user.keyboard(\"{Enter}\");\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n\n it(\"can be activated with Space key\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Button onPress={handlePress}>Press me</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /press me/i });\n button.focus();\n\n await user.keyboard(\" \");\n\n expect(handlePress).toHaveBeenCalledTimes(1);\n });\n});\n```\n\n### State Tests\n\nTest different states of the Button\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - States\", () => {\n it(\"applies disabled state on native button\", () => {\n render(\n <NimbusProvider>\n <Button isDisabled>Disabled</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /disabled/i });\n expect(button).toBeDisabled();\n });\n\n it(\"is not focusable when disabled\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <Button isDisabled>Cannot focus</Button>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /cannot focus/i });\n await user.tab();\n\n expect(button).not.toHaveFocus();\n });\n});\n```\n\n### Tooltip on a disabled Button\n\nPair `isDisabled` with `allowFocusWhenDisabled` to keep a\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Disabled with a Tooltip\", () => {\n it(\"stays focusable so a Tooltip can explain why it is disabled\", async () => {\n const user = userEvent.setup();\n const handlePress = vi.fn();\n\n render(\n <NimbusProvider>\n <Tooltip.Root delay={0} closeDelay={0}>\n <Button isDisabled allowFocusWhenDisabled onPress={handlePress}>\n Publish\n </Button>\n <Tooltip.Content>\n Complete all required fields to publish.\n </Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n const button = screen.getByRole(\"button\", { name: /publish/i });\n\n // Announced as disabled, but still reachable (no native `disabled`).\n expect(button).toHaveAttribute(\"aria-disabled\", \"true\");\n expect(button).not.toBeDisabled();\n\n // Keyboard users can focus it and the tooltip reveals the explanation.\n await user.tab();\n expect(button).toHaveFocus();\n expect(await screen.findByRole(\"tooltip\")).toHaveTextContent(\n /required fields/i\n );\n\n // ...yet the action remains suppressed.\n await user.click(button);\n expect(handlePress).not.toHaveBeenCalled();\n });\n});\n```\n\n### Visual Variant Tests\n\nTest rendering with different visual variants and sizes\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Button, NimbusProvider, Tooltip } from \"@commercetools/nimbus\";\n\ndescribe(\"Button - Visual variants\", () => {\n it(\"renders with different size variants\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Button size=\"sm\">Small</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Button size=\"md\">Medium</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n });\n\n it(\"renders with different visual variants\", () => {\n const { rerender } = render(\n <NimbusProvider>\n <Button variant=\"solid\">Solid</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n\n rerender(\n <NimbusProvider>\n <Button variant=\"outline\">Outline</Button>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\")).toBeInTheDocument();\n });\n\n it(\"renders with color palettes\", () => {\n render(\n <NimbusProvider>\n <Button colorPalette=\"primary\" variant=\"solid\">\n Primary\n </Button>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /primary/i })\n ).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-buttons-button--docs)\n- [React Aria Button](https://react-spectrum.adobe.com/react-aria/Button.html)\n\n",
|
|
379
379
|
"toc": [
|
|
380
380
|
{
|
|
381
381
|
"value": "Getting started",
|
|
@@ -485,6 +485,18 @@
|
|
|
485
485
|
],
|
|
486
486
|
"parent": "root"
|
|
487
487
|
},
|
|
488
|
+
{
|
|
489
|
+
"value": "Keeping a disabled button focusable (for tooltips)",
|
|
490
|
+
"href": "#keeping-a-disabled-button-focusable-for-tooltips",
|
|
491
|
+
"depth": 4,
|
|
492
|
+
"numbering": [
|
|
493
|
+
1,
|
|
494
|
+
2,
|
|
495
|
+
6,
|
|
496
|
+
1
|
|
497
|
+
],
|
|
498
|
+
"parent": "root"
|
|
499
|
+
},
|
|
488
500
|
{
|
|
489
501
|
"value": "Event handling",
|
|
490
502
|
"href": "#event-handling",
|
|
@@ -613,6 +625,17 @@
|
|
|
613
625
|
],
|
|
614
626
|
"parent": "root"
|
|
615
627
|
},
|
|
628
|
+
{
|
|
629
|
+
"value": "Tooltip on a disabled Button",
|
|
630
|
+
"href": "#tooltip-on-a-disabled-button",
|
|
631
|
+
"depth": 3,
|
|
632
|
+
"numbering": [
|
|
633
|
+
1,
|
|
634
|
+
7,
|
|
635
|
+
5
|
|
636
|
+
],
|
|
637
|
+
"parent": "root"
|
|
638
|
+
},
|
|
616
639
|
{
|
|
617
640
|
"value": "Visual Variant Tests",
|
|
618
641
|
"href": "#visual-variant-tests",
|
|
@@ -620,7 +643,7 @@
|
|
|
620
643
|
"numbering": [
|
|
621
644
|
1,
|
|
622
645
|
7,
|
|
623
|
-
|
|
646
|
+
6
|
|
624
647
|
],
|
|
625
648
|
"parent": "root"
|
|
626
649
|
},
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"id": "Components-ChatMessageList",
|
|
4
|
+
"title": "Chat message list",
|
|
5
|
+
"exportName": "ChatMessageList",
|
|
6
|
+
"description": "A scrollable transcript that arranges chat messages and notices, owning autoscroll, the \"jump to latest\" control, and the live region that announces new and streamed replies.",
|
|
7
|
+
"lifecycleState": "Experimental",
|
|
8
|
+
"order": 999,
|
|
9
|
+
"repoPath": "packages/nimbus/src/components/chat-message-list/chat-message-list.mdx",
|
|
10
|
+
"menu": [
|
|
11
|
+
"Components",
|
|
12
|
+
"Chat",
|
|
13
|
+
"Chat message list"
|
|
14
|
+
],
|
|
15
|
+
"route": "components/chat/chat-message-list",
|
|
16
|
+
"tags": [
|
|
17
|
+
"component",
|
|
18
|
+
"chat",
|
|
19
|
+
"message",
|
|
20
|
+
"list",
|
|
21
|
+
"transcript",
|
|
22
|
+
"ai",
|
|
23
|
+
"conversation"
|
|
24
|
+
],
|
|
25
|
+
"toc": [
|
|
26
|
+
{
|
|
27
|
+
"value": "Overview",
|
|
28
|
+
"href": "#overview",
|
|
29
|
+
"depth": 2,
|
|
30
|
+
"numbering": [
|
|
31
|
+
1,
|
|
32
|
+
1
|
|
33
|
+
],
|
|
34
|
+
"parent": "root"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"value": "Resources",
|
|
38
|
+
"href": "#resources",
|
|
39
|
+
"depth": 3,
|
|
40
|
+
"numbering": [
|
|
41
|
+
1,
|
|
42
|
+
1,
|
|
43
|
+
1
|
|
44
|
+
],
|
|
45
|
+
"parent": "root"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"value": "Variables",
|
|
49
|
+
"href": "#variables",
|
|
50
|
+
"depth": 2,
|
|
51
|
+
"numbering": [
|
|
52
|
+
1,
|
|
53
|
+
2
|
|
54
|
+
],
|
|
55
|
+
"parent": "root"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"value": "A conversation transcript",
|
|
59
|
+
"href": "#a-conversation-transcript",
|
|
60
|
+
"depth": 3,
|
|
61
|
+
"numbering": [
|
|
62
|
+
1,
|
|
63
|
+
2,
|
|
64
|
+
1
|
|
65
|
+
],
|
|
66
|
+
"parent": "root"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"value": "Empty state",
|
|
70
|
+
"href": "#empty-state",
|
|
71
|
+
"depth": 3,
|
|
72
|
+
"numbering": [
|
|
73
|
+
1,
|
|
74
|
+
2,
|
|
75
|
+
2
|
|
76
|
+
],
|
|
77
|
+
"parent": "root"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"value": "Autoscroll and \"jump to latest\"",
|
|
81
|
+
"href": "#autoscroll-and-jump-to-latest",
|
|
82
|
+
"depth": 3,
|
|
83
|
+
"numbering": [
|
|
84
|
+
1,
|
|
85
|
+
2,
|
|
86
|
+
3
|
|
87
|
+
],
|
|
88
|
+
"parent": "root"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"layout": "app-frame",
|
|
92
|
+
"tabs": [
|
|
93
|
+
{
|
|
94
|
+
"key": "overview",
|
|
95
|
+
"title": "Overview",
|
|
96
|
+
"order": 0
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"key": "dev",
|
|
100
|
+
"title": "Implementation",
|
|
101
|
+
"order": 3
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"key": "a11y",
|
|
105
|
+
"title": "Accessibility",
|
|
106
|
+
"order": 4
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"mdx": "\n## Overview\n\n`ChatMessageList` presents a **scrollable sequence of chat messages** and owns\nthe transcript-level behavior that a single `ChatMessage` deliberately does not:\nscrolling, autoscroll / stick-to-bottom, the \"jump to latest\" affordance, and\nthe single live region that announces appended and streamed replies.\n\nA list is defined by its items, so `ChatMessageList` is a compound of a `Root`\nand its `Item`s — not a box that happens to contain children:\n\n- `ChatMessageList.Root` — the scroll container. Wraps `ScrollArea`, keeps the\n view pinned to the newest message while you are at the bottom, and is the\n transcript's `role=\"log\"` live region.\n- `ChatMessageList.Item` — one list member. It is content-agnostic: it holds a\n `ChatMessage` or any other content, which is what lets a system notice be a\n peer of a message rather than a message variant.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n## Variables\n\nGet familiar with the features.\n\n### A conversation transcript\n\nWrap each turn in a `ChatMessageList.Item`. A system notice is just another\nmember — the list does not treat it specially, so a consumer renders their own\ncentered, subdued content.\n\n```jsx live\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation with the agent\" height=\"360px\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\">\n <ChatMessage.Avatar firstName=\"Ada\" lastName=\"Lovelace\" />\n <ChatMessage.Body>\n <Text>Can you summarise last week's orders?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Avatar>\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Markdown>\n {[\n \"Here's what I found:\",\n \"\",\n \"- **Revenue** is up 12% week-over-week\",\n \"- Top SKU: `ABC-123`\",\n ].join(\"\\n\")}\n </Markdown>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n\n <ChatMessageList.Item>\n <Box mx=\"auto\" textAlign=\"center\" color=\"neutral.11\" textStyle=\"sm\">\n Conversation history was cleared.\n </Box>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n### Empty state\n\nWith no items, the list renders the `emptyState` you supply — an onboarding\nprompt or a simple \"no messages yet\" line.\n\n```jsx live\nconst App = () => (\n <ChatMessageList.Root\n aria-label=\"Conversation\"\n height=\"220px\"\n emptyState={\n <Text>No messages yet — ask the agent anything to get started.</Text>\n }\n />\n);\n```\n\n### Autoscroll and \"jump to latest\"\n\n`Root` keeps the newest message in view while you are at the bottom, including\nwhile a reply streams. Scroll up to read history and the pin releases, revealing\na \"jump to latest\" control that returns you to the bottom and re-engages the\npin. Set `autoScroll={false}` to turn automatic scrolling off and drive it\nyourself via the ref's `scrollToBottom()`.\n",
|
|
111
|
+
"views": {
|
|
112
|
+
"overview": {
|
|
113
|
+
"mdx": "\n## Overview\n\n`ChatMessageList` presents a **scrollable sequence of chat messages** and owns\nthe transcript-level behavior that a single `ChatMessage` deliberately does not:\nscrolling, autoscroll / stick-to-bottom, the \"jump to latest\" affordance, and\nthe single live region that announces appended and streamed replies.\n\nA list is defined by its items, so `ChatMessageList` is a compound of a `Root`\nand its `Item`s — not a box that happens to contain children:\n\n- `ChatMessageList.Root` — the scroll container. Wraps `ScrollArea`, keeps the\n view pinned to the newest message while you are at the bottom, and is the\n transcript's `role=\"log\"` live region.\n- `ChatMessageList.Item` — one list member. It is content-agnostic: it holds a\n `ChatMessage` or any other content, which is what lets a system notice be a\n peer of a message rather than a message variant.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n## Variables\n\nGet familiar with the features.\n\n### A conversation transcript\n\nWrap each turn in a `ChatMessageList.Item`. A system notice is just another\nmember — the list does not treat it specially, so a consumer renders their own\ncentered, subdued content.\n\n```jsx live\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation with the agent\" height=\"360px\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\">\n <ChatMessage.Avatar firstName=\"Ada\" lastName=\"Lovelace\" />\n <ChatMessage.Body>\n <Text>Can you summarise last week's orders?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Avatar>\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Markdown>\n {[\n \"Here's what I found:\",\n \"\",\n \"- **Revenue** is up 12% week-over-week\",\n \"- Top SKU: `ABC-123`\",\n ].join(\"\\n\")}\n </Markdown>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n\n <ChatMessageList.Item>\n <Box mx=\"auto\" textAlign=\"center\" color=\"neutral.11\" textStyle=\"sm\">\n Conversation history was cleared.\n </Box>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n### Empty state\n\nWith no items, the list renders the `emptyState` you supply — an onboarding\nprompt or a simple \"no messages yet\" line.\n\n```jsx live\nconst App = () => (\n <ChatMessageList.Root\n aria-label=\"Conversation\"\n height=\"220px\"\n emptyState={\n <Text>No messages yet — ask the agent anything to get started.</Text>\n }\n />\n);\n```\n\n### Autoscroll and \"jump to latest\"\n\n`Root` keeps the newest message in view while you are at the bottom, including\nwhile a reply streams. Scroll up to read history and the pin releases, revealing\na \"jump to latest\" control that returns you to the bottom and re-engages the\npin. Set `autoScroll={false}` to turn automatic scrolling off and drive it\nyourself via the ref's `scrollToBottom()`.\n",
|
|
114
|
+
"toc": [
|
|
115
|
+
{
|
|
116
|
+
"value": "Overview",
|
|
117
|
+
"href": "#overview",
|
|
118
|
+
"depth": 2,
|
|
119
|
+
"numbering": [
|
|
120
|
+
1,
|
|
121
|
+
1
|
|
122
|
+
],
|
|
123
|
+
"parent": "root"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"value": "Resources",
|
|
127
|
+
"href": "#resources",
|
|
128
|
+
"depth": 3,
|
|
129
|
+
"numbering": [
|
|
130
|
+
1,
|
|
131
|
+
1,
|
|
132
|
+
1
|
|
133
|
+
],
|
|
134
|
+
"parent": "root"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"value": "Variables",
|
|
138
|
+
"href": "#variables",
|
|
139
|
+
"depth": 2,
|
|
140
|
+
"numbering": [
|
|
141
|
+
1,
|
|
142
|
+
2
|
|
143
|
+
],
|
|
144
|
+
"parent": "root"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"value": "A conversation transcript",
|
|
148
|
+
"href": "#a-conversation-transcript",
|
|
149
|
+
"depth": 3,
|
|
150
|
+
"numbering": [
|
|
151
|
+
1,
|
|
152
|
+
2,
|
|
153
|
+
1
|
|
154
|
+
],
|
|
155
|
+
"parent": "root"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"value": "Empty state",
|
|
159
|
+
"href": "#empty-state",
|
|
160
|
+
"depth": 3,
|
|
161
|
+
"numbering": [
|
|
162
|
+
1,
|
|
163
|
+
2,
|
|
164
|
+
2
|
|
165
|
+
],
|
|
166
|
+
"parent": "root"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"value": "Autoscroll and \"jump to latest\"",
|
|
170
|
+
"href": "#autoscroll-and-jump-to-latest",
|
|
171
|
+
"depth": 3,
|
|
172
|
+
"numbering": [
|
|
173
|
+
1,
|
|
174
|
+
2,
|
|
175
|
+
3
|
|
176
|
+
],
|
|
177
|
+
"parent": "root"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"a11y": {
|
|
182
|
+
"mdx": "\n## Accessibility\n\n`ChatMessageList` owns the half of chat accessibility that a single\n`ChatMessage` intentionally leaves to the transcript: the container's live\nregion and the announcement of new content. This page covers what the list does\nfor you and the small amount you are responsible for.\n\n```jsx live\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation with the agent\" height=\"260px\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\" aria-label=\"Message from Ada Lovelace\">\n <ChatMessage.Avatar firstName=\"Ada\" lastName=\"Lovelace\" />\n <ChatMessage.Body>\n <Text>Can you summarise last week's orders?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\" aria-label=\"Message from the agent\">\n <ChatMessage.Avatar aria-label=\"Agent\">\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Text>Revenue is up 12% week-over-week.</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n### Naming the transcript\n\n`ChatMessageList.Root` renders a `role=\"log\"` region that needs an accessible\nname. Provide one with `aria-label` or `aria-labelledby`. If you supply neither,\nthe list falls back to a localized default (\"Conversation\") so the region is\nnever anonymous — but a name specific to the conversation is better.\n\n### Why `log`, not `feed`\n\nTwo ARIA container roles fit a message transcript, and they are not\ninterchangeable:\n\n- **`role=\"log\"` with `aria-live=\"polite\"`** (the default here) — a live region\n that announces appended content, including streamed replies, without\n re-announcing the whole history on every token. It has **no constraint on\n child roles**, so it validly contains `<article>` messages, centered notices,\n date separators, or anything else the transcript needs.\n- **`role=\"feed\"`** — the ARIA APG's dedicated pattern for scrollable article\n collections, with built-in \"read next/previous article\" keyboard support. Its\n trade-off: `feed` has an `aria-required-children` constraint — **every** child\n must have role `article`. A centered notice or a divider inside a `feed` is an\n accessibility violation, not just a lint warning.\n\nBecause a real transcript mixes messages with notices and dividers,\n`ChatMessageList` defaults to `log`. If you are specifically building\narticle-to-article keyboard navigation, you can compose a `role=\"feed\"` yourself\nfrom message `article`s only — but then you cannot mix in non-article members.\n\n### One announcer during streaming\n\nStreaming has a single announcer. The list's `log` region announces\nmessage-level changes (a message appended, a reply arriving). The streamed\nmessage itself only sets `aria-busy` via `isStreaming` on its\n`ChatMessage.Root`; it does **not** create its own live region — a region\nmounted at the same moment as its content is not reliably announced, and two\noverlapping live regions would double-announce.\n\nHow much of the streamed text is announced depends on how you render it.\n`aria-busy=\"true\"` asks assistive technology to defer announcing changes inside\nthe message until the stream settles and the flag clears — which keeps a\ncompliant screen reader from reading every token — but support for that deferral\nvaries between screen readers. The recommended path is to render the body with\n`<Markdown isStreaming>`: it owns a single, coalesced completion announcement\ninstead of emitting text token by token. If you stream raw growing text into the\n`log` with neither `aria-busy` nor a component that coalesces its own\nannouncement, expect some screen readers to announce intermediate updates.\n\n### The list is always mounted\n\nThe `log` region persists across messages — it is present before content\nchanges, which is what makes appended and streamed content announce reliably.\nRender `ChatMessageList.Root` for the life of the conversation rather than\nmounting it alongside the first message.\n\n### `Item` does not assert `listitem`\n\n`ChatMessageList.Item` provides DOM order and a styling hook but deliberately\ndoes not set `role=\"listitem\"`, which would require a `list`/`log` parent\nrelationship that conflicts with the live-region semantics. Members keep their\nown roles (a message `article`, a notice) unchanged.\n\n### Accessibility standards\n\n- **Status messages:** `Root` is `role=\"log\"` `aria-live=\"polite\"` so new and\n streamed messages are announced without the consumer building custom\n live-region logic.\n- **Name, role, value:** name the `log` region with `aria-label` /\n `aria-labelledby`; a localized default applies only as a fallback.\n- **Info and relationships:** a mixed transcript stays valid because `log`\n imposes no required-children constraint — messages, notices, and dividers can\n coexist.\n- **Focus order / no keyboard trap:** the scroll-to-bottom control is a single\n focusable button that appears only while the pin is released; it never traps\n focus.\n\n### Resources\n\n- [WAI-ARIA: `log` role](https://www.w3.org/TR/wai-aria-1.2/#log)\n- [ARIA APG: Feed Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/feed/)\n- [WCAG 2.1: Status Messages (4.1.3)](https://www.w3.org/WAI/WCAG21/Understanding/status-messages.html)\n",
|
|
183
|
+
"toc": [
|
|
184
|
+
{
|
|
185
|
+
"value": "Accessibility",
|
|
186
|
+
"href": "#accessibility",
|
|
187
|
+
"depth": 2,
|
|
188
|
+
"numbering": [
|
|
189
|
+
1,
|
|
190
|
+
1
|
|
191
|
+
],
|
|
192
|
+
"parent": "root"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"value": "Naming the transcript",
|
|
196
|
+
"href": "#naming-the-transcript",
|
|
197
|
+
"depth": 3,
|
|
198
|
+
"numbering": [
|
|
199
|
+
1,
|
|
200
|
+
1,
|
|
201
|
+
1
|
|
202
|
+
],
|
|
203
|
+
"parent": "root"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"value": "Why log, not feed",
|
|
207
|
+
"href": "#why-log-not-feed",
|
|
208
|
+
"depth": 3,
|
|
209
|
+
"numbering": [
|
|
210
|
+
1,
|
|
211
|
+
1,
|
|
212
|
+
2
|
|
213
|
+
],
|
|
214
|
+
"parent": "root"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"value": "One announcer during streaming",
|
|
218
|
+
"href": "#one-announcer-during-streaming",
|
|
219
|
+
"depth": 3,
|
|
220
|
+
"numbering": [
|
|
221
|
+
1,
|
|
222
|
+
1,
|
|
223
|
+
3
|
|
224
|
+
],
|
|
225
|
+
"parent": "root"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"value": "The list is always mounted",
|
|
229
|
+
"href": "#the-list-is-always-mounted",
|
|
230
|
+
"depth": 3,
|
|
231
|
+
"numbering": [
|
|
232
|
+
1,
|
|
233
|
+
1,
|
|
234
|
+
4
|
|
235
|
+
],
|
|
236
|
+
"parent": "root"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"value": "Item does not assert listitem",
|
|
240
|
+
"href": "#item-does-not-assert-listitem",
|
|
241
|
+
"depth": 3,
|
|
242
|
+
"numbering": [
|
|
243
|
+
1,
|
|
244
|
+
1,
|
|
245
|
+
5
|
|
246
|
+
],
|
|
247
|
+
"parent": "root"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"value": "Accessibility standards",
|
|
251
|
+
"href": "#accessibility-standards",
|
|
252
|
+
"depth": 3,
|
|
253
|
+
"numbering": [
|
|
254
|
+
1,
|
|
255
|
+
1,
|
|
256
|
+
6
|
|
257
|
+
],
|
|
258
|
+
"parent": "root"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"value": "Resources",
|
|
262
|
+
"href": "#resources",
|
|
263
|
+
"depth": 3,
|
|
264
|
+
"numbering": [
|
|
265
|
+
1,
|
|
266
|
+
1,
|
|
267
|
+
7
|
|
268
|
+
],
|
|
269
|
+
"parent": "root"
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
"dev": {
|
|
274
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport {\n ChatMessageList,\n type ChatMessageListHandle,\n} from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\n`ChatMessageList` is a compound component: wrap each turn in a\n`ChatMessageList.Item` inside `ChatMessageList.Root`. Give `Root` a bounded\nheight (directly, or by placing it in a flex/grid parent) so it scrolls\ninternally instead of growing the page.\n\n```jsx live-dev\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation\" height=\"320px\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\">\n <ChatMessage.Avatar firstName=\"Ada\" lastName=\"Lovelace\" />\n <ChatMessage.Body>\n <Text>Can you summarise last week's orders?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Avatar>\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Text>Revenue is up 12% week-over-week.</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n## Usage examples\n\n### Items are content-agnostic\n\nA `ChatMessageList.Item` is the list-membership socket; it does not care what\nplugs into it. The same `Item` holds a `ChatMessage` or any other content, so a\nsystem notice is a peer of a message rather than a `sender` variant.\n\n```jsx live-dev\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation\" height=\"260px\">\n <ChatMessageList.Item>\n <Box mx=\"auto\" textAlign=\"center\" color=\"neutral.11\" textStyle=\"sm\">\n Today\n </Box>\n </ChatMessageList.Item>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Avatar>\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Text>How can I help you today?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n### Empty state\n\nWhen the list has no items, `Root` renders the `emptyState` node.\n\n```jsx live-dev\nconst App = () => (\n <ChatMessageList.Root\n aria-label=\"Conversation\"\n height=\"220px\"\n emptyState={<Text>No messages yet.</Text>}\n />\n);\n```\n\n### Autoscroll and streaming\n\n`autoScroll` (default `true`) keeps the view pinned to the newest message while\nthe user is at the bottom, including while a reply streams and grows. The\nmessage being streamed sets `isStreaming` on its `ChatMessage.Root` (which sets\n`aria-busy`) and renders its text with `<Markdown isStreaming>`; the list owns\nthe announcement, the message owns the busy flag. Scrolling up releases the pin\nand reveals a \"jump to latest\" control.\n\n```jsx live-dev\nconst App = () => (\n <ChatMessageList.Root aria-label=\"Conversation\" height=\"300px\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\">\n <ChatMessage.Body>\n <Text>Tell me about last week's revenue.</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\" isStreaming>\n <ChatMessage.Avatar>\n <Icons.AutoAwesome />\n </ChatMessage.Avatar>\n <ChatMessage.Body>\n <Markdown isStreaming>\n {\"Revenue is up **12%** week-over-week, driven by\"}\n </Markdown>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n);\n```\n\n### Imperative scroll control\n\n`Root` exposes a `scrollToBottom()` handle on its `ref` — useful after\nprepending history, or to jump to the newest message from a keyboard shortcut.\nIt re-engages stick-to-bottom and honors `prefers-reduced-motion`.\n\n```tsx\nconst ref = useRef<ChatMessageListHandle>(null);\n\n// later, e.g. after loading older messages:\nref.current?.scrollToBottom(\"smooth\");\n\nreturn (\n <ChatMessageList.Root ref={ref} aria-label=\"Conversation\">\n {/* items */}\n </ChatMessageList.Root>\n);\n```\n\n## Component requirements\n\n- Give `ChatMessageList.Root` a bounded height (directly or via a flex/grid\n parent) so it scrolls internally.\n- Wrap every transcript member — a `ChatMessage` or any other content — in its\n own `ChatMessageList.Item`.\n- Name the transcript with `aria-label` (or `aria-labelledby`); `Root` provides\n a localized default if you omit both, but a specific name is better.\n- v1 renders items directly (no windowing); very long transcripts render every\n item. Virtualization is a planned fast-follow.\n\n## Accessibility\n\n`ChatMessageList.Root` is the transcript's single, persistent `role=\"log\"`\n`aria-live=\"polite\"` region — appended and streamed messages are announced\nwithout per-token spam, and mixed children (message `article`s plus notices) are\nvalid because `log` imposes no `aria-required-children` constraint. See the\n**Accessibility tab** for the full `log`-vs-`feed` rationale and how streaming\nannouncement ownership is split between the list and the message.\n\n## API reference\n\n<PropsTable id=\"ChatMessageList\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using\nChatMessageList within your application. As the component's internal\nfunctionality is already tested by Nimbus, these patterns help you verify your\nintegration and application-specific logic.\n\n### Rendering a transcript\n\nWrap each turn in a ChatMessageList.Item inside ChatMessageList.Root.\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport {\n ChatMessageList,\n ChatMessage,\n NimbusProvider,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ChatMessageList - Rendering a transcript\", () => {\n it(\"renders each item's member as a vertical sequence\", () => {\n render(\n <NimbusProvider>\n <ChatMessageList.Root aria-label=\"Conversation\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"user\">\n <ChatMessage.Body>\n <Text>Summarise last week's orders?</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Body>\n <Text>Revenue is up 12%.</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByText(\"Summarise last week's orders?\")\n ).toBeInTheDocument();\n expect(screen.getByText(\"Revenue is up 12%.\")).toBeInTheDocument();\n });\n\n it(\"holds arbitrary content as a member, not just messages\", () => {\n render(\n <NimbusProvider>\n <ChatMessageList.Root aria-label=\"Conversation\">\n <ChatMessageList.Item>\n {/* The list is content-agnostic — an Item can hold any content,\n e.g. a consumer-rendered notice. */}\n <Text>Conversation history was cleared.</Text>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByText(\"Conversation history was cleared.\")\n ).toBeInTheDocument();\n });\n});\n```\n\n### Accessible live region\n\nRoot is a named role=\"log\" aria-live=\"polite\" region for the transcript.\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport {\n ChatMessageList,\n ChatMessage,\n NimbusProvider,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ChatMessageList - Accessible live region\", () => {\n it(\"exposes a polite log region named by aria-label\", () => {\n render(\n <NimbusProvider>\n <ChatMessageList.Root aria-label=\"Conversation with the agent\">\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Body>\n <Text>Hello!</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n </NimbusProvider>\n );\n\n // Scope by name: under the unit project's shared JSDOM (isolate:false),\n // React Aria's global LiveAnnouncer leaves nameless role=\"log\" nodes on\n // document.body that an unscoped query would also match.\n const log = screen.getByRole(\"log\", {\n name: \"Conversation with the agent\",\n });\n expect(log).toHaveAttribute(\"aria-live\", \"polite\");\n expect(log).toHaveAccessibleName(\"Conversation with the agent\");\n });\n\n it(\"falls back to a localized name when none is provided\", () => {\n render(\n <NimbusProvider>\n <ChatMessageList.Root>\n <ChatMessageList.Item>\n <ChatMessage.Root sender=\"agent\">\n <ChatMessage.Body>\n <Text>Hello!</Text>\n </ChatMessage.Body>\n </ChatMessage.Root>\n </ChatMessageList.Item>\n </ChatMessageList.Root>\n </NimbusProvider>\n );\n\n // Scope by name (see note above) to exclude LiveAnnouncer's nameless logs.\n expect(\n screen.getByRole(\"log\", { name: \"Conversation\" })\n ).toHaveAccessibleName(\"Conversation\");\n });\n});\n```\n\n### Empty state\n\nRoot renders emptyState when there are no items.\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport {\n ChatMessageList,\n ChatMessage,\n NimbusProvider,\n Text,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"ChatMessageList - Empty state\", () => {\n it(\"renders the empty state when there are no items\", () => {\n render(\n <NimbusProvider>\n <ChatMessageList.Root\n aria-label=\"Conversation\"\n emptyState={<Text>No messages yet.</Text>}\n />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"No messages yet.\")).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-chatmessagelist--docs)\n",
|
|
275
|
+
"toc": [
|
|
276
|
+
{
|
|
277
|
+
"value": "Getting started",
|
|
278
|
+
"href": "#getting-started",
|
|
279
|
+
"depth": 2,
|
|
280
|
+
"numbering": [
|
|
281
|
+
1,
|
|
282
|
+
1
|
|
283
|
+
],
|
|
284
|
+
"parent": "root"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"value": "Import",
|
|
288
|
+
"href": "#import",
|
|
289
|
+
"depth": 3,
|
|
290
|
+
"numbering": [
|
|
291
|
+
1,
|
|
292
|
+
1,
|
|
293
|
+
1
|
|
294
|
+
],
|
|
295
|
+
"parent": "root"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"value": "Basic usage",
|
|
299
|
+
"href": "#basic-usage",
|
|
300
|
+
"depth": 3,
|
|
301
|
+
"numbering": [
|
|
302
|
+
1,
|
|
303
|
+
1,
|
|
304
|
+
2
|
|
305
|
+
],
|
|
306
|
+
"parent": "root"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"value": "Usage examples",
|
|
310
|
+
"href": "#usage-examples",
|
|
311
|
+
"depth": 2,
|
|
312
|
+
"numbering": [
|
|
313
|
+
1,
|
|
314
|
+
2
|
|
315
|
+
],
|
|
316
|
+
"parent": "root"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"value": "Items are content-agnostic",
|
|
320
|
+
"href": "#items-are-content-agnostic",
|
|
321
|
+
"depth": 3,
|
|
322
|
+
"numbering": [
|
|
323
|
+
1,
|
|
324
|
+
2,
|
|
325
|
+
1
|
|
326
|
+
],
|
|
327
|
+
"parent": "root"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"value": "Empty state",
|
|
331
|
+
"href": "#empty-state",
|
|
332
|
+
"depth": 3,
|
|
333
|
+
"numbering": [
|
|
334
|
+
1,
|
|
335
|
+
2,
|
|
336
|
+
2
|
|
337
|
+
],
|
|
338
|
+
"parent": "root"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"value": "Autoscroll and streaming",
|
|
342
|
+
"href": "#autoscroll-and-streaming",
|
|
343
|
+
"depth": 3,
|
|
344
|
+
"numbering": [
|
|
345
|
+
1,
|
|
346
|
+
2,
|
|
347
|
+
3
|
|
348
|
+
],
|
|
349
|
+
"parent": "root"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"value": "Imperative scroll control",
|
|
353
|
+
"href": "#imperative-scroll-control",
|
|
354
|
+
"depth": 3,
|
|
355
|
+
"numbering": [
|
|
356
|
+
1,
|
|
357
|
+
2,
|
|
358
|
+
4
|
|
359
|
+
],
|
|
360
|
+
"parent": "root"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"value": "Component requirements",
|
|
364
|
+
"href": "#component-requirements",
|
|
365
|
+
"depth": 2,
|
|
366
|
+
"numbering": [
|
|
367
|
+
1,
|
|
368
|
+
3
|
|
369
|
+
],
|
|
370
|
+
"parent": "root"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"value": "Accessibility",
|
|
374
|
+
"href": "#accessibility",
|
|
375
|
+
"depth": 2,
|
|
376
|
+
"numbering": [
|
|
377
|
+
1,
|
|
378
|
+
4
|
|
379
|
+
],
|
|
380
|
+
"parent": "root"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"value": "API reference",
|
|
384
|
+
"href": "#api-reference",
|
|
385
|
+
"depth": 2,
|
|
386
|
+
"numbering": [
|
|
387
|
+
1,
|
|
388
|
+
5
|
|
389
|
+
],
|
|
390
|
+
"parent": "root"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"value": "Testing your implementation",
|
|
394
|
+
"href": "#testing-your-implementation",
|
|
395
|
+
"depth": 2,
|
|
396
|
+
"numbering": [
|
|
397
|
+
1,
|
|
398
|
+
6
|
|
399
|
+
],
|
|
400
|
+
"parent": "root"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"value": "Rendering a transcript",
|
|
404
|
+
"href": "#rendering-a-transcript",
|
|
405
|
+
"depth": 3,
|
|
406
|
+
"numbering": [
|
|
407
|
+
1,
|
|
408
|
+
6,
|
|
409
|
+
1
|
|
410
|
+
],
|
|
411
|
+
"parent": "root"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"value": "Accessible live region",
|
|
415
|
+
"href": "#accessible-live-region",
|
|
416
|
+
"depth": 3,
|
|
417
|
+
"numbering": [
|
|
418
|
+
1,
|
|
419
|
+
6,
|
|
420
|
+
2
|
|
421
|
+
],
|
|
422
|
+
"parent": "root"
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"value": "Empty state",
|
|
426
|
+
"href": "#empty-state-1",
|
|
427
|
+
"depth": 3,
|
|
428
|
+
"numbering": [
|
|
429
|
+
1,
|
|
430
|
+
6,
|
|
431
|
+
3
|
|
432
|
+
],
|
|
433
|
+
"parent": "root"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"value": "Resources",
|
|
437
|
+
"href": "#resources",
|
|
438
|
+
"depth": 2,
|
|
439
|
+
"numbering": [
|
|
440
|
+
1,
|
|
441
|
+
7
|
|
442
|
+
],
|
|
443
|
+
"parent": "root"
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|