@commercetools/nimbus-mcp 3.5.0 → 3.6.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.
Files changed (119) hide show
  1. package/data/docs/route-manifest.json +275 -9
  2. package/data/docs/routes/components-buttons-toggle-button-group.json +143 -18
  3. package/data/docs/routes/components-buttons-toggle-button.json +72 -15
  4. package/data/docs/routes/components-chat.json +36 -0
  5. package/data/docs/routes/components-content.json +36 -0
  6. package/data/docs/routes/components-data-display-data-table.json +1 -1
  7. package/data/docs/routes/components-feedback-popover.json +643 -0
  8. package/data/docs/routes/components-inputs-combo-box.json +33 -11
  9. package/data/docs/routes/components-inputs-search-input.json +29 -7
  10. package/data/docs/routes/components-inputs-select-input.json +23 -1
  11. package/data/docs/routes/components-layout-stack.json +1 -1
  12. package/data/docs/routes/components-navigation-toolbar.json +1 -1
  13. package/data/docs/routes/components-navigation-tree.json +2 -2
  14. package/data/docs/routes/home-contribute.json +29 -8
  15. package/data/docs/routes/home-getting-started-collaboration.json +657 -0
  16. package/data/docs/search-index.json +1 -1
  17. package/data/docs/types/AccordionContent.json +12 -12
  18. package/data/docs/types/AccordionHeader.json +46 -46
  19. package/data/docs/types/AccordionItem.json +20 -20
  20. package/data/docs/types/AccordionRoot.json +12 -12
  21. package/data/docs/types/ActivityIndicator.json +3 -2
  22. package/data/docs/types/AlertDismissButton.json +42 -42
  23. package/data/docs/types/Button.json +47 -46
  24. package/data/docs/types/Calendar.json +43 -42
  25. package/data/docs/types/Checkbox.json +30 -29
  26. package/data/docs/types/Code.json +2 -1
  27. package/data/docs/types/ComboBoxListBox.json +34 -34
  28. package/data/docs/types/ComboBoxOption.json +20 -20
  29. package/data/docs/types/ComboBoxPopover.json +67 -86
  30. package/data/docs/types/ComboBoxRoot.json +15 -0
  31. package/data/docs/types/ComboBoxSection.json +14 -14
  32. package/data/docs/types/DataTableBody.json +10 -10
  33. package/data/docs/types/DataTableCell.json +18 -18
  34. package/data/docs/types/DataTableColumn.json +28 -28
  35. package/data/docs/types/DataTableHeader.json +10 -10
  36. package/data/docs/types/DataTableManager.json +3 -5
  37. package/data/docs/types/DataTableRow.json +24 -24
  38. package/data/docs/types/DateInput.json +33 -32
  39. package/data/docs/types/DatePicker.json +32 -32
  40. package/data/docs/types/DateRangePicker.json +46 -46
  41. package/data/docs/types/DateRangePickerField.json +46 -46
  42. package/data/docs/types/DialogCloseTrigger.json +42 -42
  43. package/data/docs/types/DraggableListField.json +28 -28
  44. package/data/docs/types/DraggableListItem.json +18 -18
  45. package/data/docs/types/DraggableListRoot.json +30 -30
  46. package/data/docs/types/DrawerCloseTrigger.json +42 -42
  47. package/data/docs/types/DropZone.json +14 -14
  48. package/data/docs/types/FloatingActionButton.json +44 -44
  49. package/data/docs/types/Group.json +9 -7
  50. package/data/docs/types/Heading.json +3 -2
  51. package/data/docs/types/Icon.json +3 -2
  52. package/data/docs/types/IconButton.json +47 -46
  53. package/data/docs/types/IconToggleButton.json +71 -35
  54. package/data/docs/types/Link.json +3 -2
  55. package/data/docs/types/LoadingSpinner.json +3 -2
  56. package/data/docs/types/LocalizedField.json +3 -2
  57. package/data/docs/types/MakeElementFocusable.json +2 -2
  58. package/data/docs/types/MenuItem.json +22 -22
  59. package/data/docs/types/MenuRoot.json +31 -31
  60. package/data/docs/types/MenuSection.json +16 -16
  61. package/data/docs/types/MenuSubmenuTrigger.json +4 -4
  62. package/data/docs/types/MenuTrigger.json +46 -46
  63. package/data/docs/types/MoneyInput.json +4 -2
  64. package/data/docs/types/MultilineTextInput.json +27 -26
  65. package/data/docs/types/MultilineTextInputField.json +27 -26
  66. package/data/docs/types/NumberInput.json +14 -12
  67. package/data/docs/types/NumberInputField.json +10 -10
  68. package/data/docs/types/PasswordInput.json +24 -24
  69. package/data/docs/types/PasswordInputField.json +24 -24
  70. package/data/docs/types/Popover.json +13 -0
  71. package/data/docs/types/PopoverContent.json +42 -0
  72. package/data/docs/types/PopoverRoot.json +651 -0
  73. package/data/docs/types/PopoverTrigger.json +914 -0
  74. package/data/docs/types/ProgressBar.json +17 -15
  75. package/data/docs/types/RadioInputOption.json +17 -17
  76. package/data/docs/types/RadioInputRoot.json +10 -10
  77. package/data/docs/types/RangeCalendar.json +42 -42
  78. package/data/docs/types/RangeSlider.json +14 -14
  79. package/data/docs/types/Region.json +3 -2
  80. package/data/docs/types/RichTextInput.json +4 -2
  81. package/data/docs/types/SearchInput.json +60 -28
  82. package/data/docs/types/SearchInputField.json +60 -28
  83. package/data/docs/types/SelectOption.json +20 -20
  84. package/data/docs/types/SelectOptionGroup.json +14 -14
  85. package/data/docs/types/SelectOptions.json +34 -34
  86. package/data/docs/types/SelectRoot.json +67 -52
  87. package/data/docs/types/Separator.json +4 -2
  88. package/data/docs/types/Slider.json +18 -16
  89. package/data/docs/types/SplitButton.json +12 -10
  90. package/data/docs/types/Switch.json +11 -10
  91. package/data/docs/types/TagGroupRoot.json +12 -12
  92. package/data/docs/types/TagGroupTag.json +16 -16
  93. package/data/docs/types/TagGroupTagList.json +12 -12
  94. package/data/docs/types/TextInput.json +27 -26
  95. package/data/docs/types/TextInputField.json +27 -26
  96. package/data/docs/types/TimeInput.json +30 -28
  97. package/data/docs/types/ToastOutlet.json +4 -2
  98. package/data/docs/types/ToggleButton.json +74 -37
  99. package/data/docs/types/ToggleButtonGroup.json +4 -2
  100. package/data/docs/types/ToggleButtonGroupButton.json +547 -55
  101. package/data/docs/types/ToggleButtonGroupRoot.json +48 -16
  102. package/data/docs/types/Toolbar.json +11 -9
  103. package/data/docs/types/Tooltip.json +3 -2
  104. package/data/docs/types/TooltipContent.json +26 -26
  105. package/data/docs/types/TooltipRoot.json +16 -16
  106. package/data/docs/types/TreeIndicator.json +40 -40
  107. package/data/docs/types/TreeItem.json +20 -20
  108. package/data/docs/types/TreeRoot.json +26 -26
  109. package/data/docs/types/VisuallyHidden.json +4 -4
  110. package/data/docs/types/createItemsFromCsvDrop.json +2 -2
  111. package/data/docs/types/createItemsFromDirectoryDrop.json +2 -2
  112. package/data/docs/types/createItemsFromFileDrop.json +2 -2
  113. package/data/docs/types/createItemsFromImageDrop.json +2 -2
  114. package/data/docs/types/createItemsFromJsonDrop.json +2 -2
  115. package/data/docs/types/manifest.json +4 -0
  116. package/data/docs/types/useTree.json +8 -8
  117. package/data/style-props-summary.json +408 -0
  118. package/dist/index.js +156 -43
  119. package/package.json +8 -8
@@ -482,7 +482,7 @@
482
482
  ]
483
483
  },
484
484
  "dev": {
485
- "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Select, type SelectProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation uses uncontrolled mode:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options placeholder=\"Select an option\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n## Working with selection values\n\nThe Select component uses React Aria's selection pattern, which uses keys (strings or numbers) to identify selected options.\n\n### Selection value types\n\nThe component accepts and returns selection keys:\n\n```tsx\n// String keys \n<Select.Option id=\"apple\">Apple</Select.Option>\n\n// Number keys\n<Select.Option id={1}>Option 1</Select.Option>\n```\n\n\n### Selection structure\n\n```tsx\n// Uncontrolled mode\n<Select.Root\n defaultSelectedKey=\"apple\"\n onSelectionChange={(key) => {}}\n>\n <Select.Options>\n <Select.Option id=\"apple\">Apple</Select.Option>\n </Select.Options>\n</Select.Root>\n\n// Controlled mode\nconst [selectedKey, setSelectedKey] = useState<Key | null>(null);\n\n<Select.Root\n selectedKey={selectedKey}\n onSelectionChange={setSelectedKey}\n>\n <Select.Options>\n <Select.Option id=\"apple\">Apple</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\n## Usage examples\n\n### Size options\n\nThe `sm` and `md` size variants are available to match your interface density:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root size=\"sm\" aria-label=\"Small select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n <Select.Root size=\"md\" aria-label=\"Medium select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </Stack>\n)\n```\n\n### Disabled state\n\nUse `isDisabled` to disable the select:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root isDisabled aria-label=\"Disabled select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n### Visual variants\n\nChoose between `outline` and `ghost` variants to match your design context:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root variant=\"outline\" aria-label=\"Outline select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n <Select.Root variant=\"ghost\" aria-label=\"Ghost select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </Stack>\n)\n```\n\n### Uncontrolled mode\n\nFor simpler use cases, use uncontrolled mode with `defaultSelectedKey` and `onSelectionChange`:\n\n```jsx live-dev\nconst App = () => {\n const [selectedValue, setSelectedValue] = useState<string>('Selected: banana');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n defaultSelectedKey=\"banana\"\n onSelectionChange={(key) => {\n setSelectedValue(key ? `Selected: ${key}` : 'No selection');\n }}\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">{selectedValue}</Text>\n </Stack>\n );\n}\n```\n\n\n### Controlled mode\n\n\nFor scenarios requiring programmatic control or coordination with other components, use controlled mode:\n\n```jsx live-dev\nconst App = () => {\n const [selectedKey, setSelectedKey] = useState<Key | null>(null);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={selectedKey}\n onSelectionChange={setSelectedKey}\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">\n {selectedKey ? `Selected: ${selectedKey}` : 'No selection'}\n </Text>\n </Stack>\n );\n}\n```\n\n### Option groups\n\nGroup related options with `Select.OptionGroup`:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a food\">\n <Select.Options placeholder=\"Choose a food\">\n <Select.OptionGroup label=\"Fruits\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.OptionGroup>\n <Select.OptionGroup label=\"Vegetables\">\n <Select.Option id=\"carrot\">Carrot</Select.Option>\n <Select.Option id=\"broccoli\">Broccoli</Select.Option>\n <Select.Option id=\"spinach\">Spinach</Select.Option>\n </Select.OptionGroup>\n </Select.Options>\n </Select.Root>\n)\n```\n\n### Dynamic options with items\n\n`Select.Options` supports the `items` prop, a pattern unique to React Aria Components (inherited from React Aria's `ListBox` component). This pattern provides an efficient way to render dynamic or large lists of options.\n\n**When to use `items` vs static children:**\n\n- **Use `items` when:**\n - Rendering options from dynamic data (API responses, filtered lists, computed arrays)\n - Working with large lists (better performance through virtualization support)\n - Options change frequently based on user input or state\n - You need to map over data structures\n\n- **Use static children when:**\n - Options are known at compile time\n - You have a small, fixed set of options\n - You prefer the simpler JSX syntax for readability\n\n**Static children example:**\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n**Dynamic items example:**\n\nWhen using `items`, children must be a render function that receives each item and returns a `Select.Option`. This example demonstrates using `items` with data that could come from an API, state, or computed values:\n\n```jsx live-dev\nconst App = () => {\n // In a real app, this data might come from an API, props, or state.\n const fruits = [\n { id: 'apple', name: 'Apple' },\n { id: 'banana', name: 'Banana' },\n { id: 'orange', name: 'Orange' },\n ];\n\n return (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options items={fruits} placeholder=\"Choose a fruit\">\n {(item) => (\n <Select.Option id={item.id}>{item.name}</Select.Option>\n )}\n </Select.Options>\n </Select.Root>\n );\n}\n```\n\nThe `items` pattern enables React Aria to optimize rendering and provides better support for features like virtualization, which can improve performance with large option lists.\n\n> [!TIP]\\\n> See [React Aria's ListBox documentation](https://react-spectrum.adobe.com/react-aria/ListBox.html#sections) for complete API reference and advanced usage, including static and dynamic items.\n\n### Clearable selection\n\nUse `isClearable` to show a clear button:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root\n isClearable\n defaultSelectedKey=\"banana\"\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n\n### Leading element\n\nUse `leadingElement` to add an icon or element at the start of the trigger:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root\n leadingElement={<Icon as={Icons.SentimentSatisfied} />}\n aria-label=\"Select an option\"\n >\n <Select.Options placeholder=\"Choose...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n\n\n## Component requirements\n\n### Selection value types\n\nSelection values **must** be keys (strings or numbers) that match the `id` prop of `Select.Option` components.\n\n## Accessibility\n\nThe Select handles most accessibility requirements internally. However, you must always associate an internationalized label with the component. Visual labels are preferable, and can be set by:\n\n- Associating a `<label>` element with the `Select.Root` using `aria-labelledby`:\n\n```tsx\n<label id=\"label-id\">\n {msg.format(labelMessage)}\n</label>\n<Select.Root aria-labelledby=\"label-id\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\n- Associating a `<label>` element with the `Select.Root` using `htmlFor`:\n\n```tsx\n<label htmlFor=\"select-id\">\n {msg.format(labelMessage)}\n</label>\n<Select.Root id=\"select-id\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\nFor hidden labels, use `aria-label`:\n\n```tsx\n<Select.Root aria-label={msg.format(labelMessage)}>\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\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 = \"example-select\";\n\nexport const Example = () => (\n <Select.Root id={PERSISTENT_ID} aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab` / `Shift+Tab`: Navigate to/from the select\n- `Enter` / `Space`: Open the dropdown\n- `Arrow keys`: Navigate through options when open\n- `Enter`: Select the focused option\n- `Escape`: Close the dropdown\n- `Home` / `End`: Jump to first/last option\n- Type to search: Type characters to jump to matching options\n\n## API reference\n\n<PropsTable id=\"Select\" />\n\n## Common patterns\n\n### Filtering data by selection\n\nA common use case is filtering lists or tables by a selected option:\n\n```jsx live-dev\nconst App = () => {\n const [selectedCategory, setSelectedCategory] = useState<Key | null>(null);\n\n // Filter data based on selectedCategory\n const appliedFilter = selectedCategory\n ? `Showing items in category: ${selectedCategory}`\n : 'No filter applied';\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={selectedCategory}\n onSelectionChange={setSelectedCategory}\n aria-label=\"Filter by category\"\n >\n <Select.Options placeholder=\"All categories\">\n <Select.Option id=\"electronics\">Electronics</Select.Option>\n <Select.Option id=\"clothing\">Clothing</Select.Option>\n <Select.Option id=\"books\">Books</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">{appliedFilter}</Text>\n </Stack>\n );\n}\n```\n\n### Country/region selection\n\nExample for selecting a country, region, or category:\n\n```jsx live-dev\nconst App = () => {\n const [country, setCountry] = useState<Key | null>(null);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={country}\n onSelectionChange={setCountry}\n aria-label=\"Select country\"\n isRequired\n >\n <Select.Options placeholder=\"Choose a country\">\n <Select.Option id=\"us\">United States</Select.Option>\n <Select.Option id=\"uk\">United Kingdom</Select.Option>\n <Select.Option id=\"ca\">Canada</Select.Option>\n <Select.Option id=\"de\">Germany</Select.Option>\n </Select.Options>\n </Select.Root>\n {country && (\n <Text fontSize=\"sm\">\n Selected country: {country}\n </Text>\n )}\n </Stack>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Select within 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 component renders with expected elements\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Basic rendering\", () => {\n it(\"renders select trigger button\", () => {\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /Select an option/ })\n ).toBeInTheDocument();\n });\n\n it(\"displays default placeholder text\", () => {\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n // Default placeholder is \"Select an item\"\n expect(screen.getByText(\"Select an item\")).toBeInTheDocument();\n });\n});\n```\n\n### Interaction Tests\n\nTest user interactions with the component\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Interactions\", () => {\n it(\"opens dropdown when button is clicked\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n // The popover is rendered via a react portal outside of the select root\n // and can only be found by querying the document directly\n await waitFor(() => {\n const listbox = document.querySelector('[role=\"listbox\"]');\n expect(listbox).toBeInTheDocument();\n });\n });\n\n it(\"selects an option when clicked\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n render(\n <NimbusProvider>\n <Select.Root\n aria-label=\"Select an option\"\n onSelectionChange={handleSelectionChange}\n >\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n // Options are in the portal, query the document directly\n await waitFor(() => {\n const options = document.querySelectorAll('[role=\"option\"]');\n expect(options.length).toBeGreaterThan(0);\n });\n const options = document.querySelectorAll('[role=\"option\"]');\n await user.click(options[1]);\n\n expect(handleSelectionChange).toHaveBeenCalledWith(\"2\");\n });\n});\n```\n\n### Controlled State Tests\n\nTest controlled selection state\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Controlled state\", () => {\n it(\"displays selected value\", () => {\n const TestComponent = () => {\n const [selectedKey] = useState<Key | null>(\"2\");\n return (\n <NimbusProvider>\n <Select.Root\n selectedKey={selectedKey}\n onSelectionChange={() => {}}\n aria-label=\"Select an option\"\n >\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n };\n\n render(<TestComponent />);\n\n // The selected value appears in the button label\n const button = screen.getByRole(\"button\", { name: /Select an option/ });\n expect(button).toHaveTextContent(\"Option 2\");\n });\n});\n```\n\n### Option Group Tests\n\nTest rendering with option groups\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Option groups\", () => {\n it(\"renders grouped options\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.OptionGroup label=\"Fruits\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n </Select.OptionGroup>\n <Select.OptionGroup label=\"Vegetables\">\n <Select.Option id=\"carrot\">Carrot</Select.Option>\n </Select.OptionGroup>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n await waitFor(() => {\n expect(screen.getByText(\"Fruits\")).toBeInTheDocument();\n expect(screen.getByText(\"Vegetables\")).toBeInTheDocument();\n });\n });\n});\n```\n\n### Disabled State Tests\n\nTest that the select is properly disabled\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Disabled state\", () => {\n it(\"renders disabled select\", () => {\n render(\n <NimbusProvider>\n <Select.Root isDisabled aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n expect(select).toBeDisabled();\n });\n});\n```\n\n### Invalid State Tests\n\nTest that the select is properly marked as invalid\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Select - Invalid state\", () => {\n it(\"renders invalid select\", () => {\n render(\n <NimbusProvider>\n <Select.Root isInvalid aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n // Invalid state is on the root element.\n const button = screen.getByRole(\"button\", { name: /Select an option/ });\n const selectRoot = button.closest('[data-invalid=\"true\"]');\n expect(selectRoot).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-select--docs)\n- [React Aria Select](https://react-spectrum.adobe.com/react-aria/Select.html)\n- [ARIA Listbox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/)",
485
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Select, type SelectProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation uses uncontrolled mode:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options placeholder=\"Select an option\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n## Working with selection values\n\nThe Select component uses React Aria's selection pattern, which uses keys (strings or numbers) to identify selected options.\n\n### Selection value types\n\nThe component accepts and returns selection keys:\n\n```tsx\n// String keys \n<Select.Option id=\"apple\">Apple</Select.Option>\n\n// Number keys\n<Select.Option id={1}>Option 1</Select.Option>\n```\n\n\n### Selection structure\n\n```tsx\n// Uncontrolled mode\n<Select.Root\n defaultSelectedKey=\"apple\"\n onSelectionChange={(key) => {}}\n>\n <Select.Options>\n <Select.Option id=\"apple\">Apple</Select.Option>\n </Select.Options>\n</Select.Root>\n\n// Controlled mode\nconst [selectedKey, setSelectedKey] = useState<Key | null>(null);\n\n<Select.Root\n selectedKey={selectedKey}\n onSelectionChange={setSelectedKey}\n>\n <Select.Options>\n <Select.Option id=\"apple\">Apple</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\n## Usage examples\n\n### Size options\n\nThe `sm` and `md` size variants are available to match your interface density:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root size=\"sm\" aria-label=\"Small select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n <Select.Root size=\"md\" aria-label=\"Medium select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </Stack>\n)\n```\n\n### Disabled state\n\nUse `isDisabled` to disable the select:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root isDisabled aria-label=\"Disabled select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n### Visual variants\n\nChoose between `outline` and `ghost` variants to match your design context:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root variant=\"outline\" aria-label=\"Outline select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n <Select.Root variant=\"ghost\" aria-label=\"Ghost select\">\n <Select.Options placeholder=\"Select...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </Stack>\n)\n```\n\n### Uncontrolled mode\n\nFor simpler use cases, use uncontrolled mode with `defaultSelectedKey` and `onSelectionChange`:\n\n```jsx live-dev\nconst App = () => {\n const [selectedValue, setSelectedValue] = useState<string>('Selected: banana');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n defaultSelectedKey=\"banana\"\n onSelectionChange={(key) => {\n setSelectedValue(key ? `Selected: ${key}` : 'No selection');\n }}\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">{selectedValue}</Text>\n </Stack>\n );\n}\n```\n\n\n### Controlled mode\n\n\nFor scenarios requiring programmatic control or coordination with other components, use controlled mode:\n\n```jsx live-dev\nconst App = () => {\n const [selectedKey, setSelectedKey] = useState<Key | null>(null);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={selectedKey}\n onSelectionChange={setSelectedKey}\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">\n {selectedKey ? `Selected: ${selectedKey}` : 'No selection'}\n </Text>\n </Stack>\n );\n}\n```\n\n### Option groups\n\nGroup related options with `Select.OptionGroup`:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a food\">\n <Select.Options placeholder=\"Choose a food\">\n <Select.OptionGroup label=\"Fruits\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.OptionGroup>\n <Select.OptionGroup label=\"Vegetables\">\n <Select.Option id=\"carrot\">Carrot</Select.Option>\n <Select.Option id=\"broccoli\">Broccoli</Select.Option>\n <Select.Option id=\"spinach\">Spinach</Select.Option>\n </Select.OptionGroup>\n </Select.Options>\n </Select.Root>\n)\n```\n\n### Dynamic options with items\n\n`Select.Options` supports the `items` prop, a pattern unique to React Aria Components (inherited from React Aria's `ListBox` component). This pattern provides an efficient way to render dynamic or large lists of options.\n\n**When to use `items` vs static children:**\n\n- **Use `items` when:**\n - Rendering options from dynamic data (API responses, filtered lists, computed arrays)\n - Working with large lists (better performance through virtualization support)\n - Options change frequently based on user input or state\n - You need to map over data structures\n\n- **Use static children when:**\n - Options are known at compile time\n - You have a small, fixed set of options\n - You prefer the simpler JSX syntax for readability\n\n**Static children example:**\n\n```jsx live-dev\nconst App = () => (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n**Dynamic items example:**\n\nWhen using `items`, children must be a render function that receives each item and returns a `Select.Option`. This example demonstrates using `items` with data that could come from an API, state, or computed values:\n\n```jsx live-dev\nconst App = () => {\n // In a real app, this data might come from an API, props, or state.\n const fruits = [\n { id: 'apple', name: 'Apple' },\n { id: 'banana', name: 'Banana' },\n { id: 'orange', name: 'Orange' },\n ];\n\n return (\n <Select.Root aria-label=\"Select a fruit\">\n <Select.Options items={fruits} placeholder=\"Choose a fruit\">\n {(item) => (\n <Select.Option id={item.id}>{item.name}</Select.Option>\n )}\n </Select.Options>\n </Select.Root>\n );\n}\n```\n\nThe `items` pattern enables React Aria to optimize rendering and provides better support for features like virtualization, which can improve performance with large option lists.\n\n> [!TIP]\\\n> See [React Aria's ListBox documentation](https://react-spectrum.adobe.com/react-aria/ListBox.html#sections) for complete API reference and advanced usage, including static and dynamic items.\n\n### Clearable selection\n\nUse `isClearable` to show a clear button:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root\n isClearable\n defaultSelectedKey=\"banana\"\n aria-label=\"Select a fruit\"\n >\n <Select.Options placeholder=\"Choose a fruit\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n <Select.Option id=\"orange\">Orange</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n\n### Leading element\n\nUse `leadingElement` to add an icon or element at the start of the trigger:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root\n leadingElement={<Icon as={Icons.SentimentSatisfied} />}\n aria-label=\"Select an option\"\n >\n <Select.Options placeholder=\"Choose...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\n### Trailing element\n\nUse `trailingElement` to add content after the selected value, before the clear\nbutton and chevron. It renders beside the trigger button rather than inside it,\nso interactive content is valid here and keeps its own behaviour — pressing a\ntrailing button does not open the listbox:\n\n```jsx live-dev\nconst App = () => (\n <Select.Root\n leadingElement={<Icon as={Icons.Search} />}\n trailingElement={\n <IconButton\n size=\"xs\"\n variant=\"ghost\"\n colorPalette=\"primary\"\n aria-label=\"Filter results\"\n onPress={() => {}}\n >\n <Icons.Tune />\n </IconButton>\n }\n aria-label=\"Select an option\"\n >\n <Select.Options placeholder=\"Choose...\">\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n)\n```\n\nWhen space runs short the selected value truncates with an ellipsis rather than\nrunning underneath the trailing element. Mark decorative content\n`aria-hidden=\"true\"`; interactive content needs its own `aria-label`.\n\nKeep the trigger readable — a leading icon, a trailing control, the clear button\nand the chevron can all appear at once, so add only what earns its place.\n\n## Component requirements\n\n### Selection value types\n\nSelection values **must** be keys (strings or numbers) that match the `id` prop of `Select.Option` components.\n\n## Accessibility\n\nThe Select handles most accessibility requirements internally. However, you must always associate an internationalized label with the component. Visual labels are preferable, and can be set by:\n\n- Associating a `<label>` element with the `Select.Root` using `aria-labelledby`:\n\n```tsx\n<label id=\"label-id\">\n {msg.format(labelMessage)}\n</label>\n<Select.Root aria-labelledby=\"label-id\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\n- Associating a `<label>` element with the `Select.Root` using `htmlFor`:\n\n```tsx\n<label htmlFor=\"select-id\">\n {msg.format(labelMessage)}\n</label>\n<Select.Root id=\"select-id\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\n\nFor hidden labels, use `aria-label`:\n\n```tsx\n<Select.Root aria-label={msg.format(labelMessage)}>\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n</Select.Root>\n```\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 = \"example-select\";\n\nexport const Example = () => (\n <Select.Root id={PERSISTENT_ID} aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab` / `Shift+Tab`: Navigate to/from the select\n- `Enter` / `Space`: Open the dropdown\n- `Arrow keys`: Navigate through options when open\n- `Enter`: Select the focused option\n- `Escape`: Close the dropdown\n- `Home` / `End`: Jump to first/last option\n- Type to search: Type characters to jump to matching options\n\n## API reference\n\n<PropsTable id=\"Select\" />\n\n## Common patterns\n\n### Filtering data by selection\n\nA common use case is filtering lists or tables by a selected option:\n\n```jsx live-dev\nconst App = () => {\n const [selectedCategory, setSelectedCategory] = useState<Key | null>(null);\n\n // Filter data based on selectedCategory\n const appliedFilter = selectedCategory\n ? `Showing items in category: ${selectedCategory}`\n : 'No filter applied';\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={selectedCategory}\n onSelectionChange={setSelectedCategory}\n aria-label=\"Filter by category\"\n >\n <Select.Options placeholder=\"All categories\">\n <Select.Option id=\"electronics\">Electronics</Select.Option>\n <Select.Option id=\"clothing\">Clothing</Select.Option>\n <Select.Option id=\"books\">Books</Select.Option>\n </Select.Options>\n </Select.Root>\n <Text fontSize=\"sm\">{appliedFilter}</Text>\n </Stack>\n );\n}\n```\n\n### Country/region selection\n\nExample for selecting a country, region, or category:\n\n```jsx live-dev\nconst App = () => {\n const [country, setCountry] = useState<Key | null>(null);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Select.Root\n selectedKey={country}\n onSelectionChange={setCountry}\n aria-label=\"Select country\"\n isRequired\n >\n <Select.Options placeholder=\"Choose a country\">\n <Select.Option id=\"us\">United States</Select.Option>\n <Select.Option id=\"uk\">United Kingdom</Select.Option>\n <Select.Option id=\"ca\">Canada</Select.Option>\n <Select.Option id=\"de\">Germany</Select.Option>\n </Select.Options>\n </Select.Root>\n {country && (\n <Text fontSize=\"sm\">\n Selected country: {country}\n </Text>\n )}\n </Stack>\n );\n}\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Select within 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 component renders with expected elements\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Basic rendering\", () => {\n it(\"renders select trigger button\", () => {\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /Select an option/ })\n ).toBeInTheDocument();\n });\n\n it(\"displays default placeholder text\", () => {\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n // Default placeholder is \"Select an item\"\n expect(screen.getByText(\"Select an item\")).toBeInTheDocument();\n });\n});\n```\n\n### Interaction Tests\n\nTest user interactions with the component\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Interactions\", () => {\n it(\"opens dropdown when button is clicked\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n // The popover is rendered via a react portal outside of the select root\n // and can only be found by querying the document directly\n await waitFor(() => {\n const listbox = document.querySelector('[role=\"listbox\"]');\n expect(listbox).toBeInTheDocument();\n });\n });\n\n it(\"selects an option when clicked\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n render(\n <NimbusProvider>\n <Select.Root\n aria-label=\"Select an option\"\n onSelectionChange={handleSelectionChange}\n >\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n // Options are in the portal, query the document directly\n await waitFor(() => {\n const options = document.querySelectorAll('[role=\"option\"]');\n expect(options.length).toBeGreaterThan(0);\n });\n const options = document.querySelectorAll('[role=\"option\"]');\n await user.click(options[1]);\n\n expect(handleSelectionChange).toHaveBeenCalledWith(\"2\");\n });\n});\n```\n\n### Controlled State Tests\n\nTest controlled selection state\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Controlled state\", () => {\n it(\"displays selected value\", () => {\n const TestComponent = () => {\n const [selectedKey] = useState<Key | null>(\"2\");\n return (\n <NimbusProvider>\n <Select.Root\n selectedKey={selectedKey}\n onSelectionChange={() => {}}\n aria-label=\"Select an option\"\n >\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n <Select.Option id=\"2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n };\n\n render(<TestComponent />);\n\n // The selected value appears in the button label\n const button = screen.getByRole(\"button\", { name: /Select an option/ });\n expect(button).toHaveTextContent(\"Option 2\");\n });\n});\n```\n\n### Option Group Tests\n\nTest rendering with option groups\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Option groups\", () => {\n it(\"renders grouped options\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Select.Root aria-label=\"Select an option\">\n <Select.Options>\n <Select.OptionGroup label=\"Fruits\">\n <Select.Option id=\"apple\">Apple</Select.Option>\n <Select.Option id=\"banana\">Banana</Select.Option>\n </Select.OptionGroup>\n <Select.OptionGroup label=\"Vegetables\">\n <Select.Option id=\"carrot\">Carrot</Select.Option>\n </Select.OptionGroup>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n await user.click(select);\n\n await waitFor(() => {\n expect(screen.getByText(\"Fruits\")).toBeInTheDocument();\n expect(screen.getByText(\"Vegetables\")).toBeInTheDocument();\n });\n });\n});\n```\n\n### Disabled State Tests\n\nTest that the select is properly disabled\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Disabled state\", () => {\n it(\"renders disabled select\", () => {\n render(\n <NimbusProvider>\n <Select.Root isDisabled aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n const select = screen.getByRole(\"button\", { name: /Select an option/ });\n expect(select).toBeDisabled();\n });\n});\n```\n\n### Invalid State Tests\n\nTest that the select is properly marked as invalid\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Invalid state\", () => {\n it(\"renders invalid select\", () => {\n render(\n <NimbusProvider>\n <Select.Root isInvalid aria-label=\"Select an option\">\n <Select.Options>\n <Select.Option id=\"1\">Option 1</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n // Invalid state is on the root element.\n const button = screen.getByRole(\"button\", { name: /Select an option/ });\n const selectRoot = button.closest('[data-invalid=\"true\"]');\n expect(selectRoot).toBeInTheDocument();\n });\n});\n```\n\n### Trailing Element Tests\n\nVerify trailing element rendering and that it owns its own clicks\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { useState } from \"react\";\nimport type { Key } from \"react-aria-components\";\nimport { Select, IconButton, NimbusProvider } from \"@commercetools/nimbus\";\nimport { Tune } from \"@commercetools/nimbus-icons\";\n\ndescribe(\"Select - Trailing element\", () => {\n const renderWithTrailing = (trailingElement: React.ReactNode) =>\n render(\n <NimbusProvider>\n <Select.Root\n aria-label=\"Choose an option\"\n defaultSelectedKey=\"option1\"\n data-testid=\"select\"\n trailingElement={trailingElement}\n >\n <Select.Options>\n <Select.Option id=\"option1\">Option 1</Select.Option>\n <Select.Option id=\"option2\">Option 2</Select.Option>\n </Select.Options>\n </Select.Root>\n </NimbusProvider>\n );\n\n it(\"renders the trailing element in the trailingElement slot\", () => {\n renderWithTrailing(<Tune data-testid=\"trailing-icon\" />);\n\n expect(screen.getByTestId(\"trailing-icon\").parentElement).toHaveClass(\n \"nimbus-select__trailingElement\"\n );\n });\n\n it(\"renders a trailing button outside the trigger button\", () => {\n renderWithTrailing(\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Filter results\">\n <Tune />\n </IconButton>\n );\n\n const trigger = screen.getByTestId(\"select\").querySelector(\"button\");\n const filterButton = screen.getByRole(\"button\", { name: \"Filter results\" });\n\n expect(trigger).not.toContainElement(filterButton);\n });\n\n it(\"runs a trailing button's handler without opening the listbox\", async () => {\n const user = userEvent.setup();\n const onFilter = vi.fn();\n\n renderWithTrailing(\n <IconButton\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Filter results\"\n onPress={onFilter}\n >\n <Tune />\n </IconButton>\n );\n\n await user.click(screen.getByRole(\"button\", { name: \"Filter results\" }));\n\n expect(onFilter).toHaveBeenCalledTimes(1);\n expect(screen.queryByRole(\"listbox\")).not.toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-select--docs)\n- [React Aria Select](https://react-spectrum.adobe.com/react-aria/Select.html)\n- [ARIA Listbox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/)",
486
486
  "toc": [
487
487
  {
488
488
  "value": "Getting started",
@@ -657,6 +657,17 @@
657
657
  ],
658
658
  "parent": "root"
659
659
  },
660
+ {
661
+ "value": "Trailing element",
662
+ "href": "#trailing-element",
663
+ "depth": 3,
664
+ "numbering": [
665
+ 1,
666
+ 3,
667
+ 10
668
+ ],
669
+ "parent": "root"
670
+ },
660
671
  {
661
672
  "value": "Component requirements",
662
673
  "href": "#component-requirements",
@@ -818,6 +829,17 @@
818
829
  ],
819
830
  "parent": "root"
820
831
  },
832
+ {
833
+ "value": "Trailing Element Tests",
834
+ "href": "#trailing-element-tests",
835
+ "depth": 3,
836
+ "numbering": [
837
+ 1,
838
+ 8,
839
+ 7
840
+ ],
841
+ "parent": "root"
842
+ },
821
843
  {
822
844
  "value": "Resources",
823
845
  "href": "#resources",
@@ -172,7 +172,7 @@
172
172
  ]
173
173
  },
174
174
  "dev": {
175
- "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Stack, type StackProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nBy default, Stack arranges children vertically with no gap:\n\n```jsx live-dev\nconst App = () => (\n <Stack>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 1\n </Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 2\n </Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 3\n </Box>\n </Stack>\n)\n```\n\n## Usage examples\n\n### Direction variants\n\nControl the layout direction with the `direction` prop. Supports `column` (default), `row`, `column-reverse`, and `row-reverse`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\">\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 1</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 2</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 3</Box>\n </Stack>\n \n <Stack direction=\"column\" gap=\"200\">\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 1</Box>\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 2</Box>\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 3</Box>\n </Stack>\n \n <Stack direction=\"row-reverse\" gap=\"200\">\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 1</Box>\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 2</Box>\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 3</Box>\n </Stack>\n </Stack>\n)\n```\n\n### Gap and spacing\n\nUse the `gap` prop to control spacing between items. Accepts Nimbus spacing tokens:\n\n```jsx live-dev\nconst App = () => {\n const [gap, setGap] = useState<StackProps[\"gap\"]>(\"200\");\n \n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" align=\"center\">\n <Text fontWeight=\"600\">Gap:</Text>\n <Button size=\"2xs\" onClick={() => setGap(\"100\")}>Small (100)</Button>\n <Button size=\"2xs\" onClick={() => setGap(\"300\")}>Medium (300)</Button>\n <Button size=\"2xs\" onClick={() => setGap(\"600\")}>Large (600)</Button>\n </Stack>\n \n <Stack direction=\"row\" gap={gap}>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 1</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 2</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 3</Box>\n </Stack>\n \n <Text fontSize=\"sm\" color=\"neutral.11\">\n Current gap: {gap}\n </Text>\n </Stack>\n );\n}\n```\n\n### With separator\n\nAdd visual separators between items using the `separator` prop:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Stack \n direction=\"column\" \n separator={<Box height=\"25\" bg=\"neutral.6\" />}\n >\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 1</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for first section</Text>\n </Box>\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 2</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for second section</Text>\n </Box>\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 3</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for third section</Text>\n </Box>\n </Stack>\n \n <Stack \n direction=\"row\" \n separator={<Box width=\"25\" bg=\"neutral.6\" height=\"full\" />}\n >\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Left</Text>\n </Box>\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Center</Text>\n </Box>\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Right</Text>\n </Box>\n </Stack>\n </Stack>\n)\n```\n\n### Alignment options\n\nControl alignment with `align` and `justify` props:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Horizontal alignment (align):</Text>\n <Stack direction=\"row\" gap=\"400\">\n <Stack direction=\"column\" gap=\"200\" align=\"flex-start\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">Start</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n <Stack direction=\"column\" gap=\"200\" align=\"center\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">Center</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n <Stack direction=\"column\" gap=\"200\" align=\"flex-end\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">End</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Vertical alignment (justify):</Text>\n <Stack direction=\"row\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" justify=\"flex-start\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Start</Box>\n </Stack>\n <Stack direction=\"row\" gap=\"200\" justify=\"center\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Center</Box>\n </Stack>\n <Stack direction=\"row\" gap=\"200\" justify=\"space-between\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Between</Box>\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Between</Box>\n </Stack>\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Wrapping behavior\n\nControl how items wrap with the `wrap` prop:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Wrap (default):</Text>\n <Stack direction=\"row\" wrap=\"wrap\" gap=\"200\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\">\n {Array.from({ length: 12 }, (_, i) => (\n <Box key={i} p=\"300\" bg=\"primary.7\" borderRadius=\"200\" minWidth=\"1600\">\n Item {i + 1}\n </Box>\n ))}\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"200\">No wrap (overflow):</Text>\n <Stack direction=\"row\" wrap=\"nowrap\" gap=\"200\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" overflow=\"auto\">\n {Array.from({ length: 12 }, (_, i) => (\n <Box key={i} p=\"300\" bg=\"info.7\" borderRadius=\"200\" minWidth=\"1600\">\n Item {i + 1}\n </Box>\n ))}\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Responsive layouts\n\nStack supports responsive prop values for different breakpoints:\n\n```jsx live-dev\nconst App = () => (\n <Stack \n direction={{ base: \"column\", md: \"row\" }}\n gap={{ base: \"300\", md: \"600\" }}\n align={{ base: \"stretch\", md: \"center\" }}\n >\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 1</Text>\n <Text fontSize=\"sm\">Stacks vertically on mobile, horizontally on desktop</Text>\n </Box>\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 2</Text>\n <Text fontSize=\"sm\">Resize the window to see the layout change</Text>\n </Box>\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 3</Text>\n <Text fontSize=\"sm\">Gap also adjusts responsively</Text>\n </Box>\n </Stack>\n)\n```\n\n## Component requirements\n\n### Usage context\n\nStack should be used when you need to:\n- Arrange multiple elements in a single direction (horizontal or vertical)\n- Apply consistent spacing between elements\n- Add separators between items\n- Create responsive layouts that change direction based on viewport\n\nFor more complex grid-based layouts, consider using the `Grid` component. For simple flex containers with more control over individual item properties, consider using `Flex` or `Box`.\n\n## Accessibility\n\nStack is a layout component with no inherent accessibility requirements. However:\n\n- Ensure child elements have appropriate accessibility attributes\n- Use semantic HTML elements within Stack when appropriate\n- Consider reading order when using `row-reverse` or `column-reverse`\n- Test keyboard navigation flows through stacked elements\n\n#### Persistent ID\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\n<Stack id=\"main-navigation-stack\">\n {/* children */}\n</Stack>\n```\n\n## API reference\n\n<PropsTable id=\"Stack\" />\n\n## Common patterns\n\n### Navigation bar with actions\n\nUse Stack to create navigation layouts with items on opposite ends:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" align=\"center\" gap=\"400\" p=\"400\" bg=\"neutral.2\" borderRadius=\"200\">\n <Text fontWeight=\"600\" fontSize=\"lg\">Logo</Text>\n <Spacer />\n <Stack direction=\"row\" gap=\"200\">\n <Button size=\"xs\" variant=\"ghost\">Home</Button>\n <Button size=\"xs\" variant=\"ghost\">Products</Button>\n <Button size=\"xs\" variant=\"ghost\">About</Button>\n <Button size=\"xs\" variant=\"outline\">Sign In</Button>\n </Stack>\n </Stack>\n)\n```\n\n### Form sections with separators\n\nOrganize form sections with visual separators:\n\n```jsx live-dev\nconst App = () => (\n <Stack \n direction=\"column\" \n separator={<Box height=\"25\" bg=\"neutral.6\" />}\n divideY=\"1px\"\n >\n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Personal Information</Text>\n <TextInput label=\"Full Name\" />\n <TextInput label=\"Email Address\" />\n </Stack>\n \n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Shipping Address</Text>\n <TextInput label=\"Street Address\" />\n <Stack direction=\"row\" gap=\"200\">\n <TextInput label=\"City\" />\n <TextInput label=\"Postal Code\" />\n </Stack>\n </Stack>\n \n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Payment Method</Text>\n <TextInput label=\"Card Number\" />\n <Stack direction=\"row\" gap=\"200\">\n <TextInput label=\"Expiry Date\" />\n <TextInput label=\"CVV\" />\n </Stack>\n </Stack>\n </Stack>\n)\n```\n\n### Action button groups\n\nCreate consistent button groups with proper spacing:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\" align=\"stretch\">\n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Primary/Secondary Actions:</Text>\n <Stack direction=\"row\" gap=\"200\" justify=\"flex-end\">\n <Button variant=\"ghost\">Cancel</Button>\n <Button variant=\"solid\" colorPalette=\"primary\">Save</Button>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Multiple Actions:</Text>\n <Stack direction=\"row\" gap=\"200\" justify=\"space-between\">\n <Button variant=\"outline\" colorPalette=\"critical\">Delete</Button>\n <Stack direction=\"row\" gap=\"200\">\n <Button variant=\"ghost\">Cancel</Button>\n <Button variant=\"outline\">Save Draft</Button>\n <Button variant=\"solid\" colorPalette=\"primary\">Publish</Button>\n </Stack>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Icon Buttons:</Text>\n <Stack direction=\"row\" gap=\"100\">\n <IconButton aria-label=\"Edit\" size=\"xs\" variant=\"ghost\">\n <Icons.Edit />\n </IconButton>\n <IconButton aria-label=\"Copy\" size=\"xs\" variant=\"ghost\">\n <Icons.ContentCopy />\n </IconButton>\n <IconButton aria-label=\"Share\" size=\"xs\" variant=\"ghost\">\n <Icons.Share />\n </IconButton>\n <IconButton aria-label=\"Delete\" size=\"xs\" variant=\"ghost\" colorPalette=\"critical\">\n <Icons.Delete />\n </IconButton>\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Card content layouts\n\nStructure card content with proper spacing:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Card.Root>\n <Card.Content>\n <Stack direction=\"column\" gap=\"300\">\n <Stack direction=\"row\" justify=\"space-between\" align=\"center\">\n <Text fontWeight=\"600\" fontSize=\"lg\">Product Details</Text>\n <IconButton aria-label=\"More options\" size=\"xs\" variant=\"ghost\">\n <Icons.MoreVert />\n </IconButton>\n </Stack>\n \n <Stack direction=\"column\" gap=\"200\">\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">SKU:</Text>\n <Text fontWeight=\"500\">ABC-12345</Text>\n </Stack>\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">Price:</Text>\n <Text fontWeight=\"500\">$99.99</Text>\n </Stack>\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">Stock:</Text>\n <Text fontWeight=\"500\" color=\"positive.11\">In Stock</Text>\n </Stack>\n </Stack>\n \n <Stack direction=\"row\" gap=\"200\" justify=\"flex-end\">\n <Button size=\"xs\" variant=\"outline\">Edit</Button>\n <Button size=\"xs\" variant=\"solid\" colorPalette=\"primary\">View Details</Button>\n </Stack>\n </Stack>\n </Card.Content>\n </Card.Root>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Stack within 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 Stack component renders with expected structure and children\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Basic rendering\", () => {\n it(\"renders Stack with children\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"test-stack\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n <Box>Item 3</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"test-stack\");\n expect(stack).toBeInTheDocument();\n expect(stack).toHaveTextContent(\"Item 1\");\n expect(stack).toHaveTextContent(\"Item 2\");\n expect(stack).toHaveTextContent(\"Item 3\");\n });\n\n it(\"applies direction prop correctly\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"row-stack\" direction=\"row\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"row-stack\");\n expect(stack).toHaveStyle({ flexDirection: \"row\" });\n });\n\n it(\"applies gap prop for spacing\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"spaced-stack\" gap=\"400\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"spaced-stack\");\n expect(stack).toHaveStyle({ gap: \"var(--nimbus-spacing-400)\" });\n });\n});\n```\n\n### Layout Behavior Tests\n\nTest different layout configurations and responsive behavior\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Layout behavior\", () => {\n it(\"renders with column direction by default\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"default-stack\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"default-stack\");\n // Chakra Stack defaults to column direction\n expect(stack).toHaveStyle({ display: \"flex\" });\n });\n\n it(\"handles horizontal layout with row direction\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"horizontal-stack\" direction=\"row\" gap=\"200\">\n <Button>Left</Button>\n <Button>Right</Button>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"horizontal-stack\");\n expect(stack).toHaveStyle({ flexDirection: \"row\" });\n expect(screen.getByRole(\"button\", { name: \"Left\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Right\" })).toBeInTheDocument();\n });\n\n it(\"applies alignment props correctly\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"aligned-stack\"\n direction=\"row\"\n align=\"center\"\n justify=\"space-between\"\n >\n <Box>Left</Box>\n <Box>Right</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"aligned-stack\");\n expect(stack).toHaveStyle({\n alignItems: \"center\",\n justifyContent: \"space-between\",\n });\n });\n\n it(\"applies wrap prop for multi-row layouts\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"wrapped-stack\" direction=\"row\" wrap=\"wrap\">\n {Array.from({ length: 10 }, (_, i) => (\n <Box key={i}>Item {i + 1}</Box>\n ))}\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"wrapped-stack\");\n expect(stack).toHaveStyle({ flexWrap: \"wrap\" });\n });\n});\n```\n\n### Separator Tests\n\nTest rendering of separators between stack items\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Separators\", () => {\n it(\"renders separator elements between children\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"separated-stack\"\n separator={<Box data-testid=\"separator\">---</Box>}\n >\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n <Box>Item 3</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const separators = screen.getAllByTestId(\"separator\");\n // Should have n-1 separators for n items\n expect(separators).toHaveLength(2);\n });\n\n it(\"renders custom separator components\", () => {\n render(\n <NimbusProvider>\n <Stack\n separator={<Box height=\"25\" bg=\"neutral.6\" data-testid=\"divider\" />}\n >\n <Box>Section 1</Box>\n <Box>Section 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const dividers = screen.getAllByTestId(\"divider\");\n expect(dividers).toHaveLength(1);\n });\n});\n```\n\n### Style Props Tests\n\nTest that Stack accepts and applies Chakra style props\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Style props\", () => {\n it(\"applies background color prop\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"styled-stack\" bg=\"primary.7\" p=\"400\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"styled-stack\");\n expect(stack).toHaveStyle({\n padding: \"var(--nimbus-spacing-400)\",\n });\n // Background color is applied via colorPalette system\n });\n\n it(\"applies border radius and border props\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"bordered-stack\"\n borderRadius=\"200\"\n borderWidth=\"25\"\n borderColor=\"neutral.6\"\n >\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"bordered-stack\");\n // Border styles are applied (checking presence, not exact CSS vars)\n expect(stack).toBeInTheDocument();\n });\n\n it(\"applies width and height props\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"sized-stack\" width=\"6400\" height=\"3200\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"sized-stack\");\n expect(stack).toHaveStyle({\n width: \"var(--nimbus-sizes-6400)\",\n height: \"var(--nimbus-sizes-3200)\",\n });\n });\n\n it(\"applies overflow props\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"overflow-stack\" overflow=\"auto\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"overflow-stack\");\n expect(stack).toHaveStyle({ overflow: \"auto\" });\n });\n});\n```\n\n### Props Forwarding Tests\n\nVerify that Stack forwards standard HTML and ARIA attributes\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Props forwarding\", () => {\n it(\"forwards data attributes\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"data-stack\" data-custom=\"custom-value\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"data-stack\");\n expect(stack).toHaveAttribute(\"data-custom\", \"custom-value\");\n });\n\n it(\"forwards className prop\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"class-stack\" className=\"custom-class\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"class-stack\");\n expect(stack).toHaveClass(\"custom-class\");\n });\n\n it(\"forwards id prop\", () => {\n render(\n <NimbusProvider>\n <Stack id=\"unique-stack-id\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = document.getElementById(\"unique-stack-id\");\n expect(stack).toBeInTheDocument();\n });\n\n it(\"forwards ARIA attributes\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"aria-stack\"\n aria-label=\"Navigation stack\"\n role=\"navigation\"\n >\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"aria-stack\");\n expect(stack).toHaveAttribute(\"aria-label\", \"Navigation stack\");\n expect(stack).toHaveAttribute(\"role\", \"navigation\");\n });\n});\n```\n\n### Ref Forwarding Tests\n\nVerify that Stack properly forwards refs to the underlying DOM element\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Ref forwarding\", () => {\n it(\"forwards ref to underlying div element\", () => {\n const ref = { current: null as HTMLDivElement | null };\n\n render(\n <NimbusProvider>\n <Stack ref={ref}>\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n expect(ref.current).toBeInstanceOf(HTMLDivElement);\n });\n\n it(\"allows access to DOM methods through ref\", () => {\n const ref = { current: null as HTMLDivElement | null };\n\n render(\n <NimbusProvider>\n <Stack ref={ref}>\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n expect(ref.current).not.toBeNull();\n expect(typeof ref.current?.getBoundingClientRect).toBe(\"function\");\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-stack--docs)\n- [Chakra UI Stack Documentation](https://www.chakra-ui.com/docs/components/stack)\n- [Box Component](/components/layout/box)\n- [Flex Component](/components/layout/flex)\n- [Spacer Component](/components/layout/spacer)\n- [Grid Component](/components/layout/grid)\n",
175
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Stack, type StackProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nBy default, Stack arranges children vertically with no gap:\n\n```jsx live-dev\nconst App = () => (\n <Stack>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 1\n </Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 2\n </Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">\n Item 3\n </Box>\n </Stack>\n)\n```\n\n## Usage examples\n\n### Direction variants\n\nControl the layout direction with the `direction` prop. Supports `column` (default), `row`, `column-reverse`, and `row-reverse`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\">\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 1</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 2</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">Row Item 3</Box>\n </Stack>\n \n <Stack direction=\"column\" gap=\"200\">\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 1</Box>\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 2</Box>\n <Box p=\"400\" bg=\"info.7\" borderRadius=\"200\">Column Item 3</Box>\n </Stack>\n \n <Stack direction=\"row-reverse\" gap=\"200\">\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 1</Box>\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 2</Box>\n <Box p=\"400\" bg=\"positive.7\" borderRadius=\"200\">Reverse 3</Box>\n </Stack>\n </Stack>\n)\n```\n\n### Gap and spacing\n\nUse the `gap` prop to control spacing between items. Accepts Nimbus spacing tokens:\n\n```jsx live-dev\nconst App = () => {\n const [gap, setGap] = useState<StackProps[\"gap\"]>(\"200\");\n \n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" align=\"center\">\n <Text fontWeight=\"600\">Gap:</Text>\n <Button size=\"2xs\" onClick={() => setGap(\"100\")}>Small (100)</Button>\n <Button size=\"2xs\" onClick={() => setGap(\"300\")}>Medium (300)</Button>\n <Button size=\"2xs\" onClick={() => setGap(\"600\")}>Large (600)</Button>\n </Stack>\n \n <Stack direction=\"row\" gap={gap}>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 1</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 2</Box>\n <Box p=\"400\" bg=\"primary.7\" borderRadius=\"200\">Item 3</Box>\n </Stack>\n \n <Text fontSize=\"sm\" color=\"neutral.11\">\n Current gap: {gap}\n </Text>\n </Stack>\n );\n}\n```\n\n### With separator\n\nAdd visual separators between items using the `separator` prop:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Stack \n direction=\"column\" \n separator={<Box height=\"25\" bg=\"neutral.6\" />}\n >\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 1</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for first section</Text>\n </Box>\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 2</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for second section</Text>\n </Box>\n <Box p=\"400\">\n <Text fontWeight=\"600\">Section 3</Text>\n <Text fontSize=\"sm\" color=\"neutral.11\">Content for third section</Text>\n </Box>\n </Stack>\n \n <Stack \n direction=\"row\" \n separator={<Box width=\"25\" bg=\"neutral.6\" height=\"full\" />}\n >\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Left</Text>\n </Box>\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Center</Text>\n </Box>\n <Box p=\"400\" flex=\"1\">\n <Text fontWeight=\"600\">Right</Text>\n </Box>\n </Stack>\n </Stack>\n)\n```\n\n### Alignment options\n\nControl alignment with `align` and `justify` props:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Horizontal alignment (align):</Text>\n <Stack direction=\"row\" gap=\"400\">\n <Stack direction=\"column\" gap=\"200\" align=\"flex-start\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">Start</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n <Stack direction=\"column\" gap=\"200\" align=\"center\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">Center</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n <Stack direction=\"column\" gap=\"200\" align=\"flex-end\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\">\n <Text fontSize=\"sm\" fontWeight=\"600\">End</Text>\n <Box p=\"200\" bg=\"primary.7\" borderRadius=\"200\">Item</Box>\n </Stack>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Vertical alignment (justify):</Text>\n <Stack direction=\"row\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" justify=\"flex-start\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Start</Box>\n </Stack>\n <Stack direction=\"row\" gap=\"200\" justify=\"center\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Center</Box>\n </Stack>\n <Stack direction=\"row\" gap=\"200\" justify=\"space-between\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" flex=\"1\" height=\"2000\">\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Between</Box>\n <Box p=\"200\" bg=\"info.7\" borderRadius=\"200\">Between</Box>\n </Stack>\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Wrapping behavior\n\nControl how items wrap with the `wrap` prop:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text fontWeight=\"600\" mb=\"200\">Wrap (default):</Text>\n <Stack direction=\"row\" wrap=\"wrap\" gap=\"200\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\">\n {Array.from({ length: 12 }, (_, i) => (\n <Box key={i} p=\"300\" bg=\"primary.7\" borderRadius=\"200\" minWidth=\"1600\">\n Item {i + 1}\n </Box>\n ))}\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"200\">No wrap (overflow):</Text>\n <Stack direction=\"row\" wrap=\"nowrap\" gap=\"200\" bg=\"neutral.3\" p=\"400\" borderRadius=\"200\" overflow=\"auto\">\n {Array.from({ length: 12 }, (_, i) => (\n <Box key={i} p=\"300\" bg=\"info.7\" borderRadius=\"200\" minWidth=\"1600\">\n Item {i + 1}\n </Box>\n ))}\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Responsive layouts\n\nStack supports responsive prop values for different breakpoints:\n\n```jsx live-dev\nconst App = () => (\n <Stack \n direction={{ base: \"column\", md: \"row\" }}\n gap={{ base: \"300\", md: \"600\" }}\n align={{ base: \"stretch\", md: \"center\" }}\n >\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 1</Text>\n <Text fontSize=\"sm\">Stacks vertically on mobile, horizontally on desktop</Text>\n </Box>\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 2</Text>\n <Text fontSize=\"sm\">Resize the window to see the layout change</Text>\n </Box>\n <Box p=\"600\" bg=\"primary.7\" borderRadius=\"200\" flex=\"1\">\n <Text fontWeight=\"600\" mb=\"200\">Card 3</Text>\n <Text fontSize=\"sm\">Gap also adjusts responsively</Text>\n </Box>\n </Stack>\n)\n```\n\n## Component requirements\n\n### Usage context\n\nStack should be used when you need to:\n- Arrange multiple elements in a single direction (horizontal or vertical)\n- Apply consistent spacing between elements\n- Add separators between items\n- Create responsive layouts that change direction based on viewport\n\nFor more complex grid-based layouts, consider using the `Grid` component. For simple flex containers with more control over individual item properties, consider using `Flex` or `Box`.\n\n## Accessibility\n\nStack is a layout component with no inherent accessibility requirements. However:\n\n- Ensure child elements have appropriate accessibility attributes\n- Use semantic HTML elements within Stack when appropriate\n- Consider reading order when using `row-reverse` or `column-reverse`\n- Test keyboard navigation flows through stacked elements\n\n#### Persistent ID\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\n<Stack id=\"main-navigation-stack\">\n {/* children */}\n</Stack>\n```\n\n## API reference\n\n<PropsTable id=\"Stack\" />\n\n## Common patterns\n\n### Navigation bar with actions\n\nUse Stack to create navigation layouts with items on opposite ends:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" align=\"center\" gap=\"400\" p=\"400\" bg=\"neutral.2\" borderRadius=\"200\">\n <Text fontWeight=\"600\" fontSize=\"lg\">Logo</Text>\n <Spacer />\n <Stack direction=\"row\" gap=\"200\">\n <Button size=\"xs\" variant=\"ghost\">Home</Button>\n <Button size=\"xs\" variant=\"ghost\">Products</Button>\n <Button size=\"xs\" variant=\"ghost\">About</Button>\n <Button size=\"xs\" variant=\"outline\">Sign In</Button>\n </Stack>\n </Stack>\n)\n```\n\n### Form sections with separators\n\nOrganize form sections with visual separators:\n\n```jsx live-dev\nconst App = () => (\n <Stack \n direction=\"column\" \n separator={<Box height=\"25\" bg=\"neutral.6\" />}\n divideY=\"1px\"\n >\n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Personal Information</Text>\n <TextInput label=\"Full Name\" />\n <TextInput label=\"Email Address\" />\n </Stack>\n \n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Shipping Address</Text>\n <TextInput label=\"Street Address\" />\n <Stack direction=\"row\" gap=\"200\">\n <TextInput label=\"City\" />\n <TextInput label=\"Postal Code\" />\n </Stack>\n </Stack>\n \n <Stack direction=\"column\" gap=\"300\" p=\"400\">\n <Text fontWeight=\"600\">Payment Method</Text>\n <TextInput label=\"Card Number\" />\n <Stack direction=\"row\" gap=\"200\">\n <TextInput label=\"Expiry Date\" />\n <TextInput label=\"CVV\" />\n </Stack>\n </Stack>\n </Stack>\n)\n```\n\n### Action button groups\n\nCreate consistent button groups with proper spacing:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\" align=\"stretch\">\n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Primary/Secondary Actions:</Text>\n <Stack direction=\"row\" gap=\"200\" justify=\"flex-end\">\n <Button variant=\"ghost\">Cancel</Button>\n <Button variant=\"solid\" colorPalette=\"primary\">Save</Button>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Multiple Actions:</Text>\n <Stack direction=\"row\" gap=\"200\" justify=\"space-between\">\n <Button variant=\"outline\" colorPalette=\"critical\">Delete</Button>\n <Stack direction=\"row\" gap=\"200\">\n <Button variant=\"ghost\">Cancel</Button>\n <Button variant=\"outline\">Save Draft</Button>\n <Button variant=\"solid\" colorPalette=\"primary\">Publish</Button>\n </Stack>\n </Stack>\n </Box>\n \n <Box>\n <Text fontWeight=\"600\" mb=\"300\">Icon Buttons:</Text>\n <Stack direction=\"row\" gap=\"100\">\n <IconButton aria-label=\"Edit\" size=\"xs\" variant=\"ghost\">\n <Icons.Edit />\n </IconButton>\n <IconButton aria-label=\"Copy\" size=\"xs\" variant=\"ghost\">\n <Icons.ContentCopy />\n </IconButton>\n <IconButton aria-label=\"Share\" size=\"xs\" variant=\"ghost\">\n <Icons.Share />\n </IconButton>\n <IconButton aria-label=\"Delete\" size=\"xs\" variant=\"ghost\" colorPalette=\"critical\">\n <Icons.Delete />\n </IconButton>\n </Stack>\n </Box>\n </Stack>\n)\n```\n\n### Card content layouts\n\nStructure card content with proper spacing:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Card.Root>\n <Card.Content>\n <Stack direction=\"column\" gap=\"300\">\n <Stack direction=\"row\" justify=\"space-between\" align=\"center\">\n <Text fontWeight=\"600\" fontSize=\"lg\">Product Details</Text>\n <IconButton aria-label=\"More options\" size=\"xs\" variant=\"ghost\">\n <Icons.MoreVert />\n </IconButton>\n </Stack>\n \n <Stack direction=\"column\" gap=\"200\">\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">SKU:</Text>\n <Text fontWeight=\"500\">ABC-12345</Text>\n </Stack>\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">Price:</Text>\n <Text fontWeight=\"500\">$99.99</Text>\n </Stack>\n <Stack direction=\"row\" justify=\"space-between\">\n <Text color=\"neutral.11\">Stock:</Text>\n <Text fontWeight=\"500\" color=\"positive.11\">In Stock</Text>\n </Stack>\n </Stack>\n \n <Stack direction=\"row\" gap=\"200\" justify=\"flex-end\">\n <Button size=\"xs\" variant=\"outline\">Edit</Button>\n <Button size=\"xs\" variant=\"solid\" colorPalette=\"primary\">View Details</Button>\n </Stack>\n </Stack>\n </Card.Content>\n </Card.Root>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Stack within 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 Stack component renders with expected structure and children\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Basic rendering\", () => {\n it(\"renders Stack with children\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"test-stack\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n <Box>Item 3</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"test-stack\");\n expect(stack).toBeInTheDocument();\n expect(stack).toHaveTextContent(\"Item 1\");\n expect(stack).toHaveTextContent(\"Item 2\");\n expect(stack).toHaveTextContent(\"Item 3\");\n });\n\n it(\"applies direction prop correctly\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"row-stack\" direction=\"row\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"row-stack\");\n expect(stack).toHaveStyle({ flexDirection: \"row\" });\n });\n\n it(\"applies gap prop for spacing\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"spaced-stack\" gap=\"400\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"spaced-stack\");\n expect(stack).toHaveStyle({ gap: \"var(--nimbus-spacing-400)\" });\n });\n});\n```\n\n### Layout Behavior Tests\n\nTest different layout configurations and responsive behavior\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Layout behavior\", () => {\n it(\"renders with column direction by default\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"default-stack\">\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"default-stack\");\n // Chakra Stack defaults to column direction\n expect(stack).toHaveStyle({ display: \"flex\" });\n });\n\n it(\"handles horizontal layout with row direction\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"horizontal-stack\" direction=\"row\" gap=\"200\">\n <Button>Left</Button>\n <Button>Right</Button>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"horizontal-stack\");\n expect(stack).toHaveStyle({ flexDirection: \"row\" });\n expect(screen.getByRole(\"button\", { name: \"Left\" })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: \"Right\" })).toBeInTheDocument();\n });\n\n it(\"applies alignment props correctly\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"aligned-stack\"\n direction=\"row\"\n align=\"center\"\n justify=\"space-between\"\n >\n <Box>Left</Box>\n <Box>Right</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"aligned-stack\");\n expect(stack).toHaveStyle({\n alignItems: \"center\",\n justifyContent: \"space-between\",\n });\n });\n\n it(\"applies wrap prop for multi-row layouts\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"wrapped-stack\" direction=\"row\" wrap=\"wrap\">\n {Array.from({ length: 10 }, (_, i) => (\n <Box key={i}>Item {i + 1}</Box>\n ))}\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"wrapped-stack\");\n expect(stack).toHaveStyle({ flexWrap: \"wrap\" });\n });\n});\n```\n\n### Separator Tests\n\nTest rendering of separators between stack items\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Separators\", () => {\n it(\"renders separator elements between children\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"separated-stack\"\n separator={<Box data-testid=\"separator\">---</Box>}\n >\n <Box>Item 1</Box>\n <Box>Item 2</Box>\n <Box>Item 3</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const separators = screen.getAllByTestId(\"separator\");\n // Should have n-1 separators for n items\n expect(separators).toHaveLength(2);\n });\n\n it(\"renders custom separator components\", () => {\n render(\n <NimbusProvider>\n <Stack\n separator={<Box height=\"25\" bg=\"neutral.6\" data-testid=\"divider\" />}\n >\n <Box>Section 1</Box>\n <Box>Section 2</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const dividers = screen.getAllByTestId(\"divider\");\n expect(dividers).toHaveLength(1);\n });\n});\n```\n\n### Style Props Tests\n\nTest that Stack accepts and applies Chakra style props\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Style props\", () => {\n it(\"applies border radius and border props\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"bordered-stack\"\n borderRadius=\"200\"\n borderWidth=\"25\"\n borderColor=\"neutral.6\"\n >\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"bordered-stack\");\n // Border styles are applied (checking presence, not exact CSS vars)\n expect(stack).toBeInTheDocument();\n });\n\n it(\"applies width and height props\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"sized-stack\" width=\"6400\" height=\"3200\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"sized-stack\");\n expect(stack).toHaveStyle({\n width: \"var(--nimbus-sizes-6400)\",\n height: \"var(--nimbus-sizes-3200)\",\n });\n });\n\n it(\"applies overflow props\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"overflow-stack\" overflow=\"auto\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"overflow-stack\");\n expect(stack).toHaveStyle({ overflow: \"auto\" });\n });\n});\n```\n\n### Props Forwarding Tests\n\nVerify that Stack forwards standard HTML and ARIA attributes\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Props forwarding\", () => {\n it(\"forwards data attributes\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"data-stack\" data-custom=\"custom-value\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"data-stack\");\n expect(stack).toHaveAttribute(\"data-custom\", \"custom-value\");\n });\n\n it(\"forwards className prop\", () => {\n render(\n <NimbusProvider>\n <Stack data-testid=\"class-stack\" className=\"custom-class\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"class-stack\");\n expect(stack).toHaveClass(\"custom-class\");\n });\n\n it(\"forwards id prop\", () => {\n render(\n <NimbusProvider>\n <Stack id=\"unique-stack-id\">\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = document.getElementById(\"unique-stack-id\");\n expect(stack).toBeInTheDocument();\n });\n\n it(\"forwards ARIA attributes\", () => {\n render(\n <NimbusProvider>\n <Stack\n data-testid=\"aria-stack\"\n aria-label=\"Navigation stack\"\n role=\"navigation\"\n >\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n const stack = screen.getByTestId(\"aria-stack\");\n expect(stack).toHaveAttribute(\"aria-label\", \"Navigation stack\");\n expect(stack).toHaveAttribute(\"role\", \"navigation\");\n });\n});\n```\n\n### Ref Forwarding Tests\n\nVerify that Stack properly forwards refs to the underlying DOM element\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport { Stack, Box, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Stack - Ref forwarding\", () => {\n it(\"forwards ref to underlying div element\", () => {\n const ref = { current: null as HTMLDivElement | null };\n\n render(\n <NimbusProvider>\n <Stack ref={ref}>\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n expect(ref.current).toBeInstanceOf(HTMLDivElement);\n });\n\n it(\"allows access to DOM methods through ref\", () => {\n const ref = { current: null as HTMLDivElement | null };\n\n render(\n <NimbusProvider>\n <Stack ref={ref}>\n <Box>Content</Box>\n </Stack>\n </NimbusProvider>\n );\n\n expect(ref.current).not.toBeNull();\n expect(typeof ref.current?.getBoundingClientRect).toBe(\"function\");\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-stack--docs)\n- [Chakra UI Stack Documentation](https://www.chakra-ui.com/docs/components/stack)\n- [Box Component](/components/layout/box)\n- [Flex Component](/components/layout/flex)\n- [Spacer Component](/components/layout/spacer)\n- [Grid Component](/components/layout/grid)\n",
176
176
  "toc": [
177
177
  {
178
178
  "value": "Getting started",
@@ -258,7 +258,7 @@
258
258
  ]
259
259
  },
260
260
  "dev": {
261
- "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Toolbar, type ToolbarProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation creates a horizontal toolbar with default styling:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar>\n <Button variant=\"ghost\">File</Button>\n <Button variant=\"ghost\">Edit</Button>\n <Button variant=\"ghost\">View</Button>\n </Toolbar>\n)\n```\n\n## Usage examples\n\n### Size options\n\nThe `xs` and `md` size variants are available to match your interface density:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Size: xs</Text>\n <Toolbar size=\"xs\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Size: md</Text>\n <Toolbar size=\"md\">\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n### Visual variants\n\nChoose between `plain` and `outline` variants to match your design context:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Variant: plain</Text>\n <Toolbar variant=\"plain\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Variant: outline</Text>\n <Toolbar variant=\"outline\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n### Orientation\n\nThe `orientation` prop controls the layout direction and keyboard navigation:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\" alignItems=\"flex-start\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Horizontal (default)</Text>\n <Toolbar orientation=\"horizontal\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Vertical</Text>\n <Toolbar orientation=\"vertical\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n**Behavioral differences:**\n- `orientation=\"horizontal\"`: Arrow Left/Right keys navigate between controls\n- `orientation=\"vertical\"`: Arrow Up/Down keys navigate between controls\n\n### With Groups and Separators\n\nOrganize toolbar controls using `Group` and `Separator` components:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar>\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Open\">\n <Icons.FolderOpen />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Group>\n </Toolbar>\n)\n```\n\n### With ToggleButtonGroup\n\nIntegrate toggle button groups for stateful toolbar controls:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar variant=\"outline\">\n <ToggleButtonGroup.Root\n selectionMode=\"multiple\"\n defaultSelectedKeys={[\"bold\"]}\n >\n <IconToggleButton\n id=\"bold\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Bold\"\n >\n <Icons.FormatBold />\n </IconToggleButton>\n <IconToggleButton\n id=\"italic\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Italic\"\n >\n <Icons.FormatItalic />\n </IconToggleButton>\n <IconToggleButton\n id=\"underline\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Underline\"\n >\n <Icons.FormatUnderlined />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"single\"\n defaultSelectedKeys={[\"left\"]}\n >\n <IconToggleButton\n id=\"left\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Left\"\n >\n <Icons.FormatAlignLeft />\n </IconToggleButton>\n <IconToggleButton\n id=\"center\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Center\"\n >\n <Icons.FormatAlignCenter />\n </IconToggleButton>\n <IconToggleButton\n id=\"right\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Right\"\n >\n <Icons.FormatAlignRight />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n </Toolbar>\n)\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Toolbar component handles most accessibility requirements internally, including:\n- Setting the `role=\"toolbar\"` attribute\n- Managing keyboard navigation with arrow keys\n- Setting proper `aria-orientation` based on the `orientation` prop\n\nHowever, you should always provide an accessible label for the toolbar. This can be done by:\n\n- Using `aria-label` for a direct label:\n\n```tsx\n<Toolbar aria-label=\"Text formatting controls\">\n {/* toolbar controls */}\n</Toolbar>\n```\n\n- Using `aria-labelledby` to reference a visible label:\n\n```tsx\n<Text id=\"toolbar-label\">Text Formatting</Text>\n<Toolbar aria-labelledby=\"toolbar-label\">\n {/* toolbar controls */}\n</Toolbar>\n```\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 = \"example-toolbar\";\n\nexport const Example = () => (\n <Toolbar id={PERSISTENT_ID} aria-label=\"Main toolbar\">\n {/* toolbar controls */}\n </Toolbar>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab` / `Shift+Tab`: Move focus into and out of the toolbar\n- `Arrow Left` / `Arrow Right`: Navigate between controls in horizontal toolbars\n- `Arrow Up` / `Arrow Down`: Navigate between controls in vertical toolbars\n- `Home`: Focus first control in the toolbar\n- `End`: Focus last control in the toolbar\n\n## API reference\n\n<PropsTable id=\"Toolbar\" />\n\n## Common patterns\n\n### Text editor toolbar\n\nCreate a comprehensive text formatting toolbar with multiple control groups:\n\n```jsx live-dev\nconst App = () => {\n const [textStyle, setTextStyle] = useState(\"p\");\n\n const textStyles = [\n { id: \"h1\", label: \"Heading 1\" },\n { id: \"h2\", label: \"Heading 2\" },\n { id: \"p\", label: \"Paragraph\" },\n ];\n\n const selectedTextStyle = textStyles.find((v) => v.id === textStyle);\n\n return (\n <Toolbar size=\"md\" variant=\"outline\" aria-label=\"Text formatting\">\n <Group>\n <Menu.Root onAction={(v) => setTextStyle(String(v))}>\n <Menu.Trigger\n borderRadius=\"200\"\n border=\"1px solid\"\n borderColor=\"neutral.6\"\n width=\"3500\"\n aria-label=\"Text style\"\n >\n <Box display=\"flex\" alignItems=\"center\" gap=\"200\" px=\"200\">\n <Text>{selectedTextStyle?.label}</Text>\n <Icons.KeyboardArrowDown />\n </Box>\n </Menu.Trigger>\n <Menu.Content>\n {textStyles.map((style) => (\n <Menu.Item key={style.id} id={style.id}>\n {style.label}\n </Menu.Item>\n ))}\n </Menu.Content>\n </Menu.Root>\n </Group>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"multiple\"\n defaultSelectedKeys={[\"bold\"]}\n aria-label=\"Text formatting\"\n >\n <IconToggleButton id=\"bold\" size=\"xs\" variant=\"ghost\" aria-label=\"Bold\">\n <Icons.FormatBold />\n </IconToggleButton>\n <IconToggleButton id=\"italic\" size=\"xs\" variant=\"ghost\" aria-label=\"Italic\">\n <Icons.FormatItalic />\n </IconToggleButton>\n <IconToggleButton id=\"underline\" size=\"xs\" variant=\"ghost\" aria-label=\"Underline\">\n <Icons.FormatUnderlined />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"single\"\n defaultSelectedKeys={[\"left\"]}\n aria-label=\"Text alignment\"\n >\n <IconToggleButton id=\"left\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Left\">\n <Icons.FormatAlignLeft />\n </IconToggleButton>\n <IconToggleButton id=\"center\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Center\">\n <Icons.FormatAlignCenter />\n </IconToggleButton>\n <IconToggleButton id=\"right\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Right\">\n <Icons.FormatAlignRight />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n </Toolbar>\n );\n}\n```\n\n### Navigation toolbar\n\nBuild a vertical navigation toolbar for sidebar layouts:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar\n orientation=\"vertical\"\n variant=\"outline\"\n aria-label=\"Navigation\"\n >\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <Separator orientation=\"horizontal\" />\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Profile\">\n <Icons.Person />\n </IconButton>\n <Separator orientation=\"horizontal\" />\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Logout\">\n <Icons.Logout />\n </IconButton>\n </Toolbar>\n)\n```\n\n### Action toolbar\n\nCreate a simple action toolbar with primary controls:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\" width=\"full\">\n <Toolbar aria-label=\"Document actions\">\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New document\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Open document\">\n <Icons.FolderOpen />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save document\">\n <Icons.Save />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\" isDisabled>\n <Icons.Redo />\n </IconButton>\n </Group>\n </Toolbar>\n <Box\n p=\"600\"\n borderRadius=\"200\"\n borderWidth=\"1px\"\n borderColor=\"neutral.6\"\n minHeight=\"6000\"\n >\n <Text color=\"neutral.11\">Document content area</Text>\n </Box>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Toolbar within 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 component renders with expected elements\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Basic rendering\", () => {\n it(\"renders toolbar with role attribute\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Main toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n // Verify toolbar is rendered with correct role\n expect(\n screen.getByRole(\"toolbar\", { name: /main toolbar/i })\n ).toBeInTheDocument();\n });\n\n it(\"renders with horizontal orientation by default\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const toolbar = screen.getByRole(\"toolbar\");\n expect(toolbar).toHaveAttribute(\"aria-orientation\", \"horizontal\");\n });\n\n it(\"renders with vertical orientation when specified\", () => {\n render(\n <NimbusProvider>\n <Toolbar orientation=\"vertical\" aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const toolbar = screen.getByRole(\"toolbar\");\n expect(toolbar).toHaveAttribute(\"aria-orientation\", \"vertical\");\n });\n});\n```\n\n### Keyboard Navigation Tests\n\nTest keyboard navigation with the toolbar\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Keyboard navigation\", () => {\n it(\"navigates between controls with arrow keys in horizontal toolbar\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\" data-testid=\"btn-1\">\n File\n </Button>\n <Button size=\"xs\" data-testid=\"btn-2\">\n Edit\n </Button>\n <Button size=\"xs\" data-testid=\"btn-3\">\n View\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const btn1 = screen.getByTestId(\"btn-1\");\n const btn2 = screen.getByTestId(\"btn-2\");\n const btn3 = screen.getByTestId(\"btn-3\");\n\n // Focus first button\n await user.click(btn1);\n expect(btn1).toHaveFocus();\n\n // Navigate with Arrow Right\n await user.keyboard(\"{ArrowRight}\");\n expect(btn2).toHaveFocus();\n\n await user.keyboard(\"{ArrowRight}\");\n expect(btn3).toHaveFocus();\n\n // Navigate backwards with Arrow Left\n await user.keyboard(\"{ArrowLeft}\");\n expect(btn2).toHaveFocus();\n });\n\n it(\"navigates between controls with arrow keys in vertical toolbar\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Toolbar orientation=\"vertical\" aria-label=\"Test toolbar\">\n <Button size=\"xs\" data-testid=\"btn-1\">\n Home\n </Button>\n <Button size=\"xs\" data-testid=\"btn-2\">\n Search\n </Button>\n <Button size=\"xs\" data-testid=\"btn-3\">\n Settings\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const btn1 = screen.getByTestId(\"btn-1\");\n const btn2 = screen.getByTestId(\"btn-2\");\n const btn3 = screen.getByTestId(\"btn-3\");\n\n // Focus first button\n await user.click(btn1);\n expect(btn1).toHaveFocus();\n\n // Navigate with Arrow Down\n await user.keyboard(\"{ArrowDown}\");\n expect(btn2).toHaveFocus();\n\n await user.keyboard(\"{ArrowDown}\");\n expect(btn3).toHaveFocus();\n\n // Navigate backwards with Arrow Up\n await user.keyboard(\"{ArrowUp}\");\n expect(btn2).toHaveFocus();\n });\n});\n```\n\n### Testing Toolbar Children\n\nTest that toolbar properly contains and manages child elements\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Children\", () => {\n it(\"renders multiple child buttons\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n <Button size=\"xs\">Edit</Button>\n <Button size=\"xs\">View</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\", { name: /file/i })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: /edit/i })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: /view/i })).toBeInTheDocument();\n });\n\n it(\"maintains button functionality within toolbar\", async () => {\n const user = userEvent.setup();\n const handleClick = vi.fn();\n\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\" onClick={handleClick} data-testid=\"clickable-btn\">\n Click me\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const button = screen.getByTestId(\"clickable-btn\");\n await user.click(button);\n\n // Verify the click handler was called (may be called multiple times due to event bubbling)\n expect(handleClick).toHaveBeenCalled();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-toolbar--docs)\n- [React Aria Toolbar](https://react-spectrum.adobe.com/react-aria/Toolbar.html)\n- [ARIA Toolbar Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/)\n",
261
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Toolbar, type ToolbarProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation creates a horizontal toolbar with default styling:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar>\n <Button variant=\"ghost\">File</Button>\n <Button variant=\"ghost\">Edit</Button>\n <Button variant=\"ghost\">View</Button>\n </Toolbar>\n)\n```\n\n## Usage examples\n\n### Size options\n\nThe `xs` and `md` size variants are available to match your interface density:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Size: xs</Text>\n <Toolbar size=\"xs\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Size: md</Text>\n <Toolbar size=\"md\">\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"md\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n### Visual variants\n\nChoose between `plain` and `outline` variants to match your design context:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Variant: plain</Text>\n <Toolbar variant=\"plain\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Variant: outline</Text>\n <Toolbar variant=\"outline\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n### Orientation\n\nThe `orientation` prop controls the layout direction and keyboard navigation:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\" alignItems=\"flex-start\">\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Horizontal (default)</Text>\n <Toolbar orientation=\"horizontal\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n </Toolbar>\n </Box>\n <Box>\n <Text textStyle=\"sm\" mb=\"200\" color=\"neutral.11\">Vertical</Text>\n <Toolbar orientation=\"vertical\">\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n </Toolbar>\n </Box>\n </Stack>\n)\n```\n\n**Behavioral differences:**\n- `orientation=\"horizontal\"`: Arrow Left/Right keys navigate between controls\n- `orientation=\"vertical\"`: Arrow Up/Down keys navigate between controls\n\n### With Groups and Separators\n\nOrganize toolbar controls using `Group` and `Separator` components:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar>\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Open\">\n <Icons.FolderOpen />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save\">\n <Icons.Save />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Print\">\n <Icons.Print />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\">\n <Icons.Redo />\n </IconButton>\n </Group>\n </Toolbar>\n)\n```\n\n### With ToggleButtonGroup\n\nIntegrate toggle button groups for stateful toolbar controls:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar variant=\"outline\">\n <ToggleButtonGroup.Root\n selectionMode=\"multiple\"\n defaultSelectedKeys={[\"bold\"]}\n >\n <IconToggleButton\n id=\"bold\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Bold\"\n >\n <Icons.FormatBold />\n </IconToggleButton>\n <IconToggleButton\n id=\"italic\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Italic\"\n >\n <Icons.FormatItalic />\n </IconToggleButton>\n <IconToggleButton\n id=\"underline\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Underline\"\n >\n <Icons.FormatUnderlined />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"single\"\n defaultSelectedKeys={[\"left\"]}\n >\n <IconToggleButton\n id=\"left\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Left\"\n >\n <Icons.FormatAlignLeft />\n </IconToggleButton>\n <IconToggleButton\n id=\"center\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Center\"\n >\n <Icons.FormatAlignCenter />\n </IconToggleButton>\n <IconToggleButton\n id=\"right\"\n size=\"xs\"\n variant=\"ghost\"\n aria-label=\"Align Right\"\n >\n <Icons.FormatAlignRight />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n </Toolbar>\n)\n```\n\nThe example above uses `IconToggleButton` children, so each button keeps the\ntoolbar's normal spacing. When you use `ToggleButtonGroup.Button` children the\ngroup is **segmented** (buttons share borders), and the toolbar renders those\nsegments flush — no gap between them — while the group as a whole keeps the\ntoolbar spacing from its neighbours:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar variant=\"outline\">\n <ToggleButtonGroup.Root defaultSelectedKeys={[\"left\"]} aria-label=\"Alignment\">\n <ToggleButtonGroup.Button id=\"left\" aria-label=\"Align left\">\n <Icons.FormatAlignLeft />\n </ToggleButtonGroup.Button>\n <ToggleButtonGroup.Button id=\"center\" aria-label=\"Align center\">\n <Icons.FormatAlignCenter />\n </ToggleButtonGroup.Button>\n <ToggleButtonGroup.Button id=\"right\" aria-label=\"Align right\">\n <Icons.FormatAlignRight />\n </ToggleButtonGroup.Button>\n </ToggleButtonGroup.Root>\n <Separator orientation=\"vertical\" />\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"More\">\n <Icons.MoreHoriz />\n </IconButton>\n </Toolbar>\n)\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Toolbar component handles most accessibility requirements internally, including:\n- Setting the `role=\"toolbar\"` attribute\n- Managing keyboard navigation with arrow keys\n- Setting proper `aria-orientation` based on the `orientation` prop\n\nHowever, you should always provide an accessible label for the toolbar. This can be done by:\n\n- Using `aria-label` for a direct label:\n\n```tsx\n<Toolbar aria-label=\"Text formatting controls\">\n {/* toolbar controls */}\n</Toolbar>\n```\n\n- Using `aria-labelledby` to reference a visible label:\n\n```tsx\n<Text id=\"toolbar-label\">Text Formatting</Text>\n<Toolbar aria-labelledby=\"toolbar-label\">\n {/* toolbar controls */}\n</Toolbar>\n```\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 = \"example-toolbar\";\n\nexport const Example = () => (\n <Toolbar id={PERSISTENT_ID} aria-label=\"Main toolbar\">\n {/* toolbar controls */}\n </Toolbar>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n- `Tab` / `Shift+Tab`: Move focus into and out of the toolbar\n- `Arrow Left` / `Arrow Right`: Navigate between controls in horizontal toolbars\n- `Arrow Up` / `Arrow Down`: Navigate between controls in vertical toolbars\n- `Home`: Focus first control in the toolbar\n- `End`: Focus last control in the toolbar\n\n## API reference\n\n<PropsTable id=\"Toolbar\" />\n\n## Common patterns\n\n### Text editor toolbar\n\nCreate a comprehensive text formatting toolbar with multiple control groups:\n\n```jsx live-dev\nconst App = () => {\n const [textStyle, setTextStyle] = useState(\"p\");\n\n const textStyles = [\n { id: \"h1\", label: \"Heading 1\" },\n { id: \"h2\", label: \"Heading 2\" },\n { id: \"p\", label: \"Paragraph\" },\n ];\n\n const selectedTextStyle = textStyles.find((v) => v.id === textStyle);\n\n return (\n <Toolbar size=\"md\" variant=\"outline\" aria-label=\"Text formatting\">\n <Group>\n <Menu.Root onAction={(v) => setTextStyle(String(v))}>\n <Menu.Trigger\n borderRadius=\"200\"\n border=\"1px solid\"\n borderColor=\"neutral.6\"\n width=\"3500\"\n aria-label=\"Text style\"\n >\n <Box display=\"flex\" alignItems=\"center\" gap=\"200\" px=\"200\">\n <Text>{selectedTextStyle?.label}</Text>\n <Icons.KeyboardArrowDown />\n </Box>\n </Menu.Trigger>\n <Menu.Content>\n {textStyles.map((style) => (\n <Menu.Item key={style.id} id={style.id}>\n {style.label}\n </Menu.Item>\n ))}\n </Menu.Content>\n </Menu.Root>\n </Group>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"multiple\"\n defaultSelectedKeys={[\"bold\"]}\n aria-label=\"Text formatting\"\n >\n <IconToggleButton id=\"bold\" size=\"xs\" variant=\"ghost\" aria-label=\"Bold\">\n <Icons.FormatBold />\n </IconToggleButton>\n <IconToggleButton id=\"italic\" size=\"xs\" variant=\"ghost\" aria-label=\"Italic\">\n <Icons.FormatItalic />\n </IconToggleButton>\n <IconToggleButton id=\"underline\" size=\"xs\" variant=\"ghost\" aria-label=\"Underline\">\n <Icons.FormatUnderlined />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n <Separator orientation=\"vertical\" />\n <ToggleButtonGroup.Root\n selectionMode=\"single\"\n defaultSelectedKeys={[\"left\"]}\n aria-label=\"Text alignment\"\n >\n <IconToggleButton id=\"left\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Left\">\n <Icons.FormatAlignLeft />\n </IconToggleButton>\n <IconToggleButton id=\"center\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Center\">\n <Icons.FormatAlignCenter />\n </IconToggleButton>\n <IconToggleButton id=\"right\" size=\"xs\" variant=\"ghost\" aria-label=\"Align Right\">\n <Icons.FormatAlignRight />\n </IconToggleButton>\n </ToggleButtonGroup.Root>\n </Toolbar>\n );\n}\n```\n\n### Navigation toolbar\n\nBuild a vertical navigation toolbar for sidebar layouts:\n\n```jsx live-dev\nconst App = () => (\n <Toolbar\n orientation=\"vertical\"\n variant=\"outline\"\n aria-label=\"Navigation\"\n >\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Home\">\n <Icons.Home />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Search\">\n <Icons.Search />\n </IconButton>\n <Separator orientation=\"horizontal\" />\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Settings\">\n <Icons.Settings />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Profile\">\n <Icons.Person />\n </IconButton>\n <Separator orientation=\"horizontal\" />\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Logout\">\n <Icons.Logout />\n </IconButton>\n </Toolbar>\n)\n```\n\n### Action toolbar\n\nCreate a simple action toolbar with primary controls:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\" width=\"full\">\n <Toolbar aria-label=\"Document actions\">\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"New document\">\n <Icons.Add />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Open document\">\n <Icons.FolderOpen />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Save document\">\n <Icons.Save />\n </IconButton>\n </Group>\n <Separator orientation=\"vertical\" />\n <Group>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Undo\">\n <Icons.Undo />\n </IconButton>\n <IconButton size=\"xs\" variant=\"ghost\" aria-label=\"Redo\" isDisabled>\n <Icons.Redo />\n </IconButton>\n </Group>\n </Toolbar>\n <Box\n p=\"600\"\n borderRadius=\"200\"\n borderWidth=\"1px\"\n borderColor=\"neutral.6\"\n minHeight=\"6000\"\n >\n <Text color=\"neutral.11\">Document content area</Text>\n </Box>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Toolbar within 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 component renders with expected elements\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Basic rendering\", () => {\n it(\"renders toolbar with role attribute\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Main toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n // Verify toolbar is rendered with correct role\n expect(\n screen.getByRole(\"toolbar\", { name: /main toolbar/i })\n ).toBeInTheDocument();\n });\n\n it(\"renders with horizontal orientation by default\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const toolbar = screen.getByRole(\"toolbar\");\n expect(toolbar).toHaveAttribute(\"aria-orientation\", \"horizontal\");\n });\n\n it(\"renders with vertical orientation when specified\", () => {\n render(\n <NimbusProvider>\n <Toolbar orientation=\"vertical\" aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const toolbar = screen.getByRole(\"toolbar\");\n expect(toolbar).toHaveAttribute(\"aria-orientation\", \"vertical\");\n });\n});\n```\n\n### Keyboard Navigation Tests\n\nTest keyboard navigation with the toolbar\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Keyboard navigation\", () => {\n it(\"navigates between controls with arrow keys in horizontal toolbar\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\" data-testid=\"btn-1\">\n File\n </Button>\n <Button size=\"xs\" data-testid=\"btn-2\">\n Edit\n </Button>\n <Button size=\"xs\" data-testid=\"btn-3\">\n View\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const btn1 = screen.getByTestId(\"btn-1\");\n const btn2 = screen.getByTestId(\"btn-2\");\n const btn3 = screen.getByTestId(\"btn-3\");\n\n // Focus first button\n await user.click(btn1);\n expect(btn1).toHaveFocus();\n\n // Navigate with Arrow Right\n await user.keyboard(\"{ArrowRight}\");\n expect(btn2).toHaveFocus();\n\n await user.keyboard(\"{ArrowRight}\");\n expect(btn3).toHaveFocus();\n\n // Navigate backwards with Arrow Left\n await user.keyboard(\"{ArrowLeft}\");\n expect(btn2).toHaveFocus();\n });\n\n it(\"navigates between controls with arrow keys in vertical toolbar\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Toolbar orientation=\"vertical\" aria-label=\"Test toolbar\">\n <Button size=\"xs\" data-testid=\"btn-1\">\n Home\n </Button>\n <Button size=\"xs\" data-testid=\"btn-2\">\n Search\n </Button>\n <Button size=\"xs\" data-testid=\"btn-3\">\n Settings\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const btn1 = screen.getByTestId(\"btn-1\");\n const btn2 = screen.getByTestId(\"btn-2\");\n const btn3 = screen.getByTestId(\"btn-3\");\n\n // Focus first button\n await user.click(btn1);\n expect(btn1).toHaveFocus();\n\n // Navigate with Arrow Down\n await user.keyboard(\"{ArrowDown}\");\n expect(btn2).toHaveFocus();\n\n await user.keyboard(\"{ArrowDown}\");\n expect(btn3).toHaveFocus();\n\n // Navigate backwards with Arrow Up\n await user.keyboard(\"{ArrowUp}\");\n expect(btn2).toHaveFocus();\n });\n});\n```\n\n### Testing Toolbar Children\n\nTest that toolbar properly contains and manages child elements\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 { Toolbar, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Toolbar - Children\", () => {\n it(\"renders multiple child buttons\", () => {\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\">File</Button>\n <Button size=\"xs\">Edit</Button>\n <Button size=\"xs\">View</Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"button\", { name: /file/i })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: /edit/i })).toBeInTheDocument();\n expect(screen.getByRole(\"button\", { name: /view/i })).toBeInTheDocument();\n });\n\n it(\"maintains button functionality within toolbar\", async () => {\n const user = userEvent.setup();\n const handleClick = vi.fn();\n\n render(\n <NimbusProvider>\n <Toolbar aria-label=\"Test toolbar\">\n <Button size=\"xs\" onClick={handleClick} data-testid=\"clickable-btn\">\n Click me\n </Button>\n </Toolbar>\n </NimbusProvider>\n );\n\n const button = screen.getByTestId(\"clickable-btn\");\n await user.click(button);\n\n // Verify the click handler was called (may be called multiple times due to event bubbling)\n expect(handleClick).toHaveBeenCalled();\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-toolbar--docs)\n- [React Aria Toolbar](https://react-spectrum.adobe.com/react-aria/Toolbar.html)\n- [ARIA Toolbar Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/)\n",
262
262
  "toc": [
263
263
  {
264
264
  "value": "Getting started",
@@ -205,7 +205,7 @@
205
205
  ]
206
206
  },
207
207
  "a11y": {
208
- "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\nTree is rendered as an ARIA `treegrid` — React Aria's screen-reader-tested\npattern for interactive trees that support selection and per-row actions.\n\n```jsx live\nconst App = () => (\n <Tree.Root\n aria-label=\"Files\"\n selectionMode=\"multiple\"\n defaultExpandedKeys={[\"documents\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n <Tree.Item id=\"photos\" textValue=\"Photos\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Photos\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n);\n```\n\n### Accessibility standards\n\n- **Roles:** the tree exposes `role=\"treegrid\"`, each item is a `role=\"row\"`\n containing a `role=\"gridcell\"`. This pattern is announced reliably by screen\n readers for interactive, selectable trees.\n- **aria-level:** every row exposes `aria-level` reflecting its 1-based depth so\n assistive technology can communicate the hierarchy.\n- **aria-expanded:** rows with children expose `aria-expanded`, updated as the\n item expands and collapses.\n- **aria-selected:** when selection is enabled, selectable rows expose\n `aria-selected`.\n- **Accessible name:** always provide an accessible name on `Tree.Root` via\n `aria-label` or `aria-labelledby`.\n- **Item text:** provide a meaningful `textValue` per `Tree.Item`. It supplies\n the type-ahead target and accessible name when the label is not plain text.\n- **Keyboard navigation:** Up/Down move between visible rows, Right/Left\n expand/collapse (or move to a child/parent), Home/End jump to the first/last\n visible row, and typing performs type-ahead.\n- **Expand/collapse control:** the `Tree.Indicator` chevron is given a localized\n accessible name by React Aria and is hidden for leaf rows.\n- **Drag and drop:** when drag-and-drop is enabled, `Tree.ItemContent`\n automatically renders a `Button slot=\"drag\"` per row so reordering is operable\n by keyboard and screen reader (pick up with `Enter`, move with arrow keys,\n drop with `Enter`), not pointer-only. React Aria gives it a localized\n accessible name; consumers don't author it.\n- **Focus visible:** rows and the tree container provide a clear focus ring for\n keyboard users.\n- **Color contrast:** maintain sufficient contrast for row text and selection\n states against their background colors.\n",
208
+ "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\nTree is rendered as an ARIA `treegrid` — React Aria's screen-reader-tested\npattern for interactive trees that support selection and per-row actions.\n\n```jsx live\nconst App = () => (\n <Tree.Root\n aria-label=\"Files\"\n selectionMode=\"multiple\"\n defaultExpandedKeys={[\"documents\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n <Tree.Item id=\"photos\" textValue=\"Photos\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Photos\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n);\n```\n\n### Accessibility standards\n\n- **Roles:** the tree exposes `role=\"treegrid\"`, each item is a `role=\"row\"`\n containing a `role=\"gridcell\"`. This pattern is announced reliably by screen\n readers for interactive, selectable trees.\n- **aria-level:** every row exposes `aria-level` reflecting its 1-based depth so\n assistive technology can communicate the hierarchy.\n- **aria-expanded:** rows with children expose `aria-expanded`, updated as the\n item expands and collapses.\n- **aria-selected:** when selection is enabled, selectable rows expose\n `aria-selected`.\n- **Accessible name:** always provide an accessible name on `Tree.Root` via\n `aria-label` or `aria-labelledby`.\n- **Item text:** provide a meaningful `textValue` per `Tree.Item`. It supplies\n the type-ahead target and accessible name when the label is not plain text.\n- **Keyboard navigation:** Up/Down move between visible rows, Right expands a\n collapsed row (on an already-expanded row it moves into that row's own\n controls, not to its first child), Left collapses an expanded row or moves to\n its parent, Home/End jump to the first/last visible row, and typing performs\n type-ahead.\n- **Expand/collapse control:** the `Tree.Indicator` chevron is given a localized\n accessible name by React Aria and is hidden for leaf rows.\n- **Drag and drop:** when drag-and-drop is enabled, `Tree.ItemContent`\n automatically renders a `Button slot=\"drag\"` per row so reordering is operable\n by keyboard and screen reader (pick up with `Enter`, move with arrow keys,\n drop with `Enter`), not pointer-only. React Aria gives it a localized\n accessible name; consumers don't author it.\n- **Focus visible:** rows and the tree container provide a clear focus ring for\n keyboard users.\n- **Color contrast:** maintain sufficient contrast for row text and selection\n states against their background colors.\n",
209
209
  "toc": [
210
210
  {
211
211
  "value": "Accessibility",
@@ -231,7 +231,7 @@
231
231
  ]
232
232
  },
233
233
  "dev": {
234
- "mdx": "\n## Getting started\n\nThe `Tree` is a compound component for displaying hierarchical data such as file\ntrees and nested navigation structures. It wraps React Aria's `Tree` for\nkeyboard navigation, expand/collapse, selection, type-ahead and opt-in\ndrag-and-drop.\n\n### Import\n\nImport the Tree compound component and its TypeScript types:\n\n```tsx\nimport { Tree, type TreeRootProps } from \"@commercetools/nimbus\";\n```\n\n### Anatomy\n\n```tsx\n<Tree.Root>\n <Tree.Item>\n <Tree.ItemContent>\n <Tree.Indicator />\n {/* label */}\n </Tree.ItemContent>\n {/* nested <Tree.Item>s */}\n </Tree.Item>\n</Tree.Root>\n```\n\n- **`Tree.Root`** — the tree container. Owns size variant, selection mode,\n expanded keys and (optional) drag-and-drop. Requires an accessible name via\n `aria-label` or `aria-labelledby`.\n- **`Tree.Item`** — a single node. Contains a `Tree.ItemContent` and,\n optionally, nested `Tree.Item`s.\n- **`Tree.ItemContent`** — the node's content row. Renders the indicator, an\n optional selection checkbox (in multiple-selection mode) and the label, and\n applies level-based indentation.\n- **`Tree.Indicator`** — the expand/collapse chevron. Visible only for items\n with children.\n\n### Basic usage\n\nCompose nested `Tree.Item` elements directly. Use `defaultExpandedKeys` to\ncontrol which branches start open.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root\n aria-label=\"Files\"\n defaultExpandedKeys={[\"documents\", \"project\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"project\" textValue=\"Project\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Project\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Item>\n <Tree.Item id=\"photos\" textValue=\"Photos\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Photos\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n## Usage examples\n\n### Selection\n\nSet `selectionMode` to `\"single\"` or `\"multiple\"`. In multiple-selection mode,\n`Tree.ItemContent` renders a selection checkbox automatically.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root\n aria-label=\"Files\"\n selectionMode=\"multiple\"\n defaultExpandedKeys={[\"documents\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n### Sizes\n\n`Tree.Root` accepts a `size` prop (`\"sm\"` or `\"md\"`, default `\"md\"`) that scales\nrow height, font size, chevron size and indentation step.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root aria-label=\"Files\" size=\"sm\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n## Drag and drop\n\nDrag-and-drop is opt-in and driven entirely through Nimbus — no\n`react-aria-components` or `react-stately` imports required. The `useTree` hook\nowns the hierarchical state and wires drag-and-drop for you: reorder between\nsiblings, and re-parent by dropping an item _onto_ a group. Spread its result\nonto `Tree.Root`.\n\nYou don't author the drag handle. When the tree allows dragging,\n`Tree.ItemContent` renders a keyboard- and screen-reader-operable drag handle\nautomatically (`Enter` to grab, arrow keys to move, `Enter` to drop), with a\nconsistent icon and a localized accessible name. Drop indicators for valid\npositions are provided automatically. Your render function is identical to a\nnon-draggable tree — enabling drag-and-drop is the only change.\n\n```tsx\nimport { Tree, useTree } from \"@commercetools/nimbus\";\n\nfunction DraggableTree() {\n const tree = useTree({\n initialItems: [\n {\n id: \"documents\",\n title: \"Documents\",\n children: [{ id: \"report\", title: \"Weekly Report\" }],\n },\n { id: \"photos\", title: \"Photos\", children: [] },\n ],\n getKey: (item) => item.id,\n getChildren: (item) => item.children ?? [],\n dragAndDrop: true,\n });\n\n const renderItem = (node) => (\n <Tree.Item id={node.key} textValue={node.value.title}>\n <Tree.ItemContent>\n <Tree.Indicator />\n {node.value.title}\n </Tree.ItemContent>\n <Tree.SubTree items={node.children}>{renderItem}</Tree.SubTree>\n </Tree.Item>\n );\n\n return (\n <Tree.Root aria-label=\"Files\" {...tree}>\n {renderItem}\n </Tree.Root>\n );\n}\n```\n\nFor custom drag data or accepted external drag types, pass an options object\ninstead of `true`: `dragAndDrop={{ getItems, acceptedDragTypes }}`. For\nprogrammatic edits, the same `tree` object exposes `insert`, `remove`, `move`,\n`update`, and more.\n\n## Accessibility\n\nThe Tree component is built with React Aria and follows the WAI-ARIA `treegrid`\npattern. It provides:\n\n**Roles:**\n\n- The root exposes `role=\"treegrid\"`; each item is a `role=\"row\"` containing a\n `role=\"gridcell\"`.\n\n**Labeling:**\n\n- Each row exposes `aria-level` reflecting its depth, `aria-expanded` when it\n has children, and `aria-selected` when selection is enabled.\n- Always provide an accessible name on `Tree.Root` via `aria-label` or\n `aria-labelledby`.\n- Provide a meaningful `textValue` on each `Tree.Item` (used for type-ahead)\n when the label is not a plain string.\n\n**Keyboard navigation:**\n\n- `Up` / `Down` — move between visible rows\n- `Right` / `Left` — expand/collapse, or move to a child/parent\n- `Home` / `End` — move to the first/last visible row\n- _type characters_ — type-ahead to the next matching row\n\nWhen drag-and-drop is enabled, `Tree.ItemContent` automatically renders a drag\nhandle per item so the reorder is operable by keyboard and screen reader, not\njust pointer — you don't need to add one.\n\n## API reference\n\n<PropsTable id=\"Tree\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tree\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\n\nRender a tree with nested items and verify the treegrid structure\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tree, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tree - Basic rendering\", () => {\n it(\"renders a treegrid with rows\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"treegrid\", { name: \"Files\" })).toBeInTheDocument();\n expect(screen.getByRole(\"row\", { name: /Documents/ })).toBeInTheDocument();\n expect(\n screen.getByRole(\"row\", { name: /Weekly Report/ })\n ).toBeInTheDocument();\n });\n\n it(\"exposes aria-level reflecting depth\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"row\", { name: /Documents/ })).toHaveAttribute(\n \"aria-level\",\n \"1\"\n );\n expect(screen.getByRole(\"row\", { name: /Weekly Report/ })).toHaveAttribute(\n \"aria-level\",\n \"2\"\n );\n });\n});\n```\n\n### Selection\n\nSingle and multiple selection behavior\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tree, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tree - Selection\", () => {\n it(\"selects a row in single-selection mode\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" selectionMode=\"single\">\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n const row = screen.getByRole(\"row\", { name: /Documents/ });\n await user.click(row);\n expect(row).toHaveAttribute(\"aria-selected\", \"true\");\n });\n\n it(\"renders selection checkboxes in multiple-selection mode\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" selectionMode=\"multiple\">\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getAllByRole(\"checkbox\").length).toBeGreaterThan(0);\n });\n});\n```\n\n\n## Resources\n\n- [Tree Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tree--docs)\n- [React Aria Tree Documentation](https://react-spectrum.adobe.com/react-aria/Tree.html)\n- [WAI-ARIA treegrid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/)\n",
234
+ "mdx": "\n## Getting started\n\nThe `Tree` is a compound component for displaying hierarchical data such as file\ntrees and nested navigation structures. It wraps React Aria's `Tree` for\nkeyboard navigation, expand/collapse, selection, type-ahead and opt-in\ndrag-and-drop.\n\n### Import\n\nImport the Tree compound component and its TypeScript types:\n\n```tsx\nimport { Tree, type TreeRootProps } from \"@commercetools/nimbus\";\n```\n\n### Anatomy\n\n```tsx\n<Tree.Root>\n <Tree.Item>\n <Tree.ItemContent>\n <Tree.Indicator />\n {/* label */}\n </Tree.ItemContent>\n {/* nested <Tree.Item>s */}\n </Tree.Item>\n</Tree.Root>\n```\n\n- **`Tree.Root`** — the tree container. Owns size variant, selection mode,\n expanded keys and (optional) drag-and-drop. Requires an accessible name via\n `aria-label` or `aria-labelledby`.\n- **`Tree.Item`** — a single node. Contains a `Tree.ItemContent` and,\n optionally, nested `Tree.Item`s.\n- **`Tree.ItemContent`** — the node's content row. Renders the indicator, an\n optional selection checkbox (in multiple-selection mode) and the label, and\n applies level-based indentation.\n- **`Tree.Indicator`** — the expand/collapse chevron. Visible only for items\n with children.\n\n### Basic usage\n\nCompose nested `Tree.Item` elements directly. Use `defaultExpandedKeys` to\ncontrol which branches start open.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root\n aria-label=\"Files\"\n defaultExpandedKeys={[\"documents\", \"project\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"project\" textValue=\"Project\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Project\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Item>\n <Tree.Item id=\"photos\" textValue=\"Photos\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Photos\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n## Usage examples\n\n### Selection\n\nSet `selectionMode` to `\"single\"` or `\"multiple\"`. In multiple-selection mode,\n`Tree.ItemContent` renders a selection checkbox automatically.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root\n aria-label=\"Files\"\n selectionMode=\"multiple\"\n defaultExpandedKeys={[\"documents\"]}\n >\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n### Sizes\n\n`Tree.Root` accepts a `size` prop (`\"sm\"` or `\"md\"`, default `\"md\"`) that scales\nrow height, font size, chevron size and indentation step.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tree.Root aria-label=\"Files\" size=\"sm\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n );\n};\n```\n\n## Drag and drop\n\nDrag-and-drop is opt-in and driven entirely through Nimbus — no\n`react-aria-components` or `react-stately` imports required. The `useTree` hook\nowns the hierarchical state and wires drag-and-drop for you: reorder between\nsiblings, and re-parent by dropping an item _onto_ a group. Spread its result\nonto `Tree.Root`.\n\nYou don't author the drag handle. When the tree allows dragging,\n`Tree.ItemContent` renders a keyboard- and screen-reader-operable drag handle\nautomatically (`Enter` to grab, arrow keys to move, `Enter` to drop), with a\nconsistent icon and a localized accessible name. Drop indicators for valid\npositions are provided automatically. Your render function is identical to a\nnon-draggable tree — enabling drag-and-drop is the only change.\n\n```tsx\nimport { Tree, useTree } from \"@commercetools/nimbus\";\n\nfunction DraggableTree() {\n const tree = useTree({\n initialItems: [\n {\n id: \"documents\",\n title: \"Documents\",\n children: [{ id: \"report\", title: \"Weekly Report\" }],\n },\n { id: \"photos\", title: \"Photos\", children: [] },\n ],\n getKey: (item) => item.id,\n getChildren: (item) => item.children ?? [],\n dragAndDrop: true,\n });\n\n const renderItem = (node) => (\n <Tree.Item id={node.key} textValue={node.value.title}>\n <Tree.ItemContent>\n <Tree.Indicator />\n {node.value.title}\n </Tree.ItemContent>\n <Tree.SubTree items={node.children}>{renderItem}</Tree.SubTree>\n </Tree.Item>\n );\n\n return (\n <Tree.Root aria-label=\"Files\" {...tree}>\n {renderItem}\n </Tree.Root>\n );\n}\n```\n\nFor custom drag data, pass an options object instead of `true`:\n`dragAndDrop={{ getItems }}`. `acceptedDragTypes` narrows which types an\n*internal* drag will accept; dropping from an external source is not wired up in\nthis iteration, so external drops are always cancelled. For programmatic edits,\nthe same `tree` object exposes `insert`, `remove`, `move`, `update`, and more —\nnote that `move`, `moveBefore` and `moveAfter` all reject a destination inside\nthe moved node's own subtree, which would otherwise discard it and everything\nbeneath it.\n\n## Accessibility\n\nThe Tree component is built with React Aria and follows the WAI-ARIA `treegrid`\npattern. It provides:\n\n**Roles:**\n\n- The root exposes `role=\"treegrid\"`; each item is a `role=\"row\"` containing a\n `role=\"gridcell\"`.\n\n**Labeling:**\n\n- Each row exposes `aria-level` reflecting its depth, `aria-expanded` when it\n has children, and `aria-selected` when selection is enabled.\n- Always provide an accessible name on `Tree.Root` via `aria-label` or\n `aria-labelledby`.\n- Provide a meaningful `textValue` on each `Tree.Item` (used for type-ahead)\n when the label is not a plain string.\n\n**Keyboard navigation:**\n\n- `Up` / `Down` — move between visible rows\n- `Right` — expand a collapsed row; on an expanded row, move into that row's\n own controls\n- `Left` — collapse an expanded row, or move to its parent\n- `Home` / `End` — move to the first/last visible row\n- _type characters_ — type-ahead to the next matching row\n\nWhen drag-and-drop is enabled, `Tree.ItemContent` automatically renders a drag\nhandle per item so the reorder is operable by keyboard and screen reader, not\njust pointer — you don't need to add one.\n\n## API reference\n\n<PropsTable id=\"Tree\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tree\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\n\nRender a tree with nested items and verify the treegrid structure\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tree, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tree - Basic rendering\", () => {\n it(\"renders a treegrid with rows\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"treegrid\", { name: \"Files\" })).toBeInTheDocument();\n expect(screen.getByRole(\"row\", { name: /Documents/ })).toBeInTheDocument();\n expect(\n screen.getByRole(\"row\", { name: /Weekly Report/ })\n ).toBeInTheDocument();\n });\n\n it(\"exposes aria-level reflecting depth\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" defaultExpandedKeys={[\"documents\"]}>\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n <Tree.Item id=\"report\" textValue=\"Weekly Report\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Weekly Report\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"row\", { name: /Documents/ })).toHaveAttribute(\n \"aria-level\",\n \"1\"\n );\n expect(screen.getByRole(\"row\", { name: /Weekly Report/ })).toHaveAttribute(\n \"aria-level\",\n \"2\"\n );\n });\n});\n```\n\n### Selection\n\nSingle and multiple selection behavior\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tree, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tree - Selection\", () => {\n it(\"selects a row in single-selection mode\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" selectionMode=\"single\">\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n const row = screen.getByRole(\"row\", { name: /Documents/ });\n await user.click(row);\n expect(row).toHaveAttribute(\"aria-selected\", \"true\");\n });\n\n it(\"renders selection checkboxes in multiple-selection mode\", () => {\n render(\n <NimbusProvider>\n <Tree.Root aria-label=\"Files\" selectionMode=\"multiple\">\n <Tree.Item id=\"documents\" textValue=\"Documents\">\n <Tree.ItemContent>\n <Tree.Indicator />\n Documents\n </Tree.ItemContent>\n </Tree.Item>\n </Tree.Root>\n </NimbusProvider>\n );\n\n expect(screen.getAllByRole(\"checkbox\").length).toBeGreaterThan(0);\n });\n});\n```\n\n\n## Resources\n\n- [Tree Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tree--docs)\n- [React Aria Tree Documentation](https://react-spectrum.adobe.com/react-aria/Tree.html)\n- [WAI-ARIA treegrid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/)\n",
235
235
  "toc": [
236
236
  {
237
237
  "value": "Getting started",