@commercetools/nimbus-mcp 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/data/docs/route-manifest.json +1407 -794
  2. package/data/docs/routes/components-data-display-data-table.json +66 -34
  3. package/data/docs/routes/components-data-display-draggable-list.json +62 -7
  4. package/data/docs/routes/components-inputs-checkbox.json +2 -2
  5. package/data/docs/routes/components-layout-splitter.json +654 -0
  6. package/data/docs/routes/components-utilities-region.json +265 -0
  7. package/data/docs/routes/home-getting-started-bundler-plugins.json +248 -0
  8. package/data/docs/routes/hooks-usedraganddrop.json +310 -0
  9. package/data/docs/routes/patterns-actions.json +78 -0
  10. package/data/docs/routes/patterns-dialogs-confirmation-dialog.json +391 -0
  11. package/data/docs/routes/patterns-dialogs-form-dialog.json +358 -0
  12. package/data/docs/routes/patterns-dialogs.json +2 -2
  13. package/data/docs/routes/patterns-pages-public-page-layout.json +371 -0
  14. package/data/docs/routes/patterns-pages.json +78 -0
  15. package/data/docs/search-index.json +1 -1
  16. package/data/docs/types/AccordionContent.json +32 -32
  17. package/data/docs/types/AccordionHeader.json +102 -102
  18. package/data/docs/types/AccordionItem.json +28 -28
  19. package/data/docs/types/AccordionRoot.json +15 -15
  20. package/data/docs/types/AlertDescription.json +8 -8
  21. package/data/docs/types/AlertDismissButton.json +89 -89
  22. package/data/docs/types/AlertTitle.json +8 -8
  23. package/data/docs/types/Avatar.json +2 -2
  24. package/data/docs/types/Badge.json +2 -2
  25. package/data/docs/types/Body.json +6 -6
  26. package/data/docs/types/Box.json +6 -6
  27. package/data/docs/types/Button.json +97 -97
  28. package/data/docs/types/Calendar.json +111 -65
  29. package/data/docs/types/Caption.json +6 -6
  30. package/data/docs/types/CardRoot.json +2 -2
  31. package/data/docs/types/Cell.json +20 -20
  32. package/data/docs/types/Checkbox.json +99 -99
  33. package/data/docs/types/Code.json +10 -10
  34. package/data/docs/types/CollapsibleMotionContent.json +2 -2
  35. package/data/docs/types/CollapsibleMotionRoot.json +2 -2
  36. package/data/docs/types/CollapsibleMotionTrigger.json +4 -4
  37. package/data/docs/types/Column.json +8 -8
  38. package/data/docs/types/ColumnGroup.json +8 -8
  39. package/data/docs/types/ColumnHeader.json +18 -18
  40. package/data/docs/types/ComboBoxListBox.json +80 -80
  41. package/data/docs/types/ComboBoxOption.json +77 -77
  42. package/data/docs/types/ComboBoxPopover.json +77 -77
  43. package/data/docs/types/ComboBoxRoot.json +8 -8
  44. package/data/docs/types/ComboBoxSection.json +29 -29
  45. package/data/docs/types/ComboBoxTrigger.json +6 -6
  46. package/data/docs/types/ConfirmationDialog.json +224 -0
  47. package/data/docs/types/Content.json +2 -2
  48. package/data/docs/types/DataTable.json +17 -2
  49. package/data/docs/types/DataTableBody.json +24 -24
  50. package/data/docs/types/DataTableHeader.json +31 -31
  51. package/data/docs/types/DataTableRoot.json +17 -2
  52. package/data/docs/types/DataTableTable.json +35 -20
  53. package/data/docs/types/DateInput.json +84 -84
  54. package/data/docs/types/DatePicker.json +65 -65
  55. package/data/docs/types/DateRangePicker.json +99 -99
  56. package/data/docs/types/DateRangePickerField.json +99 -99
  57. package/data/docs/types/DefaultPageBackLink.json +16 -16
  58. package/data/docs/types/DefaultPageRoot.json +2 -2
  59. package/data/docs/types/DialogCloseTrigger.json +87 -87
  60. package/data/docs/types/DialogTrigger.json +2 -2
  61. package/data/docs/types/DragAndDropItemData.json +9 -0
  62. package/data/docs/types/DragAndDropProps.json +9 -0
  63. package/data/docs/types/DraggableListField.json +159 -70
  64. package/data/docs/types/DraggableListItem.json +63 -63
  65. package/data/docs/types/DraggableListRoot.json +159 -70
  66. package/data/docs/types/DrawerCloseTrigger.json +87 -87
  67. package/data/docs/types/DrawerTrigger.json +2 -2
  68. package/data/docs/types/FieldErrors.json +2 -2
  69. package/data/docs/types/Flex.json +22 -22
  70. package/data/docs/types/Footer.json +6 -6
  71. package/data/docs/types/FormDialog.json +198 -0
  72. package/data/docs/types/FormFieldRoot.json +2 -2
  73. package/data/docs/types/Grid.json +24 -24
  74. package/data/docs/types/Group.json +12 -12
  75. package/data/docs/types/Header.json +6 -6
  76. package/data/docs/types/Heading.json +8 -8
  77. package/data/docs/types/Icon.json +4 -4
  78. package/data/docs/types/IconButton.json +97 -97
  79. package/data/docs/types/IconToggleButton.json +84 -84
  80. package/data/docs/types/Image.json +30 -30
  81. package/data/docs/types/Indicator.json +6 -6
  82. package/data/docs/types/InlineSvg.json +2 -2
  83. package/data/docs/types/Item.json +6 -6
  84. package/data/docs/types/Kbd.json +8 -8
  85. package/data/docs/types/Link.json +31 -31
  86. package/data/docs/types/ListIndicator.json +6 -6
  87. package/data/docs/types/ListItem.json +6 -6
  88. package/data/docs/types/ListRoot.json +10 -10
  89. package/data/docs/types/LoadingSpinner.json +2 -2
  90. package/data/docs/types/MakeElementFocusable.json +19 -19
  91. package/data/docs/types/MenuItem.json +75 -75
  92. package/data/docs/types/MenuRoot.json +63 -63
  93. package/data/docs/types/MenuSection.json +35 -54
  94. package/data/docs/types/MenuSubmenuTrigger.json +5 -5
  95. package/data/docs/types/MenuTrigger.json +102 -102
  96. package/data/docs/types/MultilineTextInput.json +134 -134
  97. package/data/docs/types/MultilineTextInputField.json +131 -131
  98. package/data/docs/types/NumberInput.json +100 -100
  99. package/data/docs/types/NumberInputField.json +95 -95
  100. package/data/docs/types/PageContentColumn.json +6 -6
  101. package/data/docs/types/PageContentRoot.json +6 -6
  102. package/data/docs/types/PasswordInput.json +129 -129
  103. package/data/docs/types/PasswordInputField.json +129 -129
  104. package/data/docs/types/ProgressBar.json +14 -14
  105. package/data/docs/types/PublicPageLayout.json +99 -0
  106. package/data/docs/types/RadioInputOption.json +64 -64
  107. package/data/docs/types/RadioInputRoot.json +55 -55
  108. package/data/docs/types/RangeCalendar.json +90 -71
  109. package/data/docs/types/Region.json +114 -0
  110. package/data/docs/types/RegionProvider.json +25 -0
  111. package/data/docs/types/RegionTarget.json +112 -0
  112. package/data/docs/types/RichTextInput.json +2 -2
  113. package/data/docs/types/Root.json +10 -10
  114. package/data/docs/types/Row.json +6 -6
  115. package/data/docs/types/SPLITTER_SIZE_TOKENS.json +9 -0
  116. package/data/docs/types/ScrollArea.json +2 -2
  117. package/data/docs/types/SearchInput.json +136 -136
  118. package/data/docs/types/SearchInputField.json +131 -131
  119. package/data/docs/types/SelectOption.json +66 -66
  120. package/data/docs/types/SelectOptionGroup.json +22 -22
  121. package/data/docs/types/SelectOptions.json +74 -74
  122. package/data/docs/types/SelectRoot.json +102 -102
  123. package/data/docs/types/Separator.json +4 -4
  124. package/data/docs/types/SimpleGrid.json +28 -28
  125. package/data/docs/types/SplitButton.json +12 -12
  126. package/data/docs/types/Splitter.json +12 -0
  127. package/data/docs/types/SplitterAside.json +27 -0
  128. package/data/docs/types/SplitterHandle.json +27 -0
  129. package/data/docs/types/SplitterMain.json +27 -0
  130. package/data/docs/types/SplitterRoot.json +271 -0
  131. package/data/docs/types/SplitterSizeToken.json +9 -0
  132. package/data/docs/types/Stack.json +2 -2
  133. package/data/docs/types/StepsNextTrigger.json +2 -2
  134. package/data/docs/types/StepsPrevTrigger.json +2 -2
  135. package/data/docs/types/StepsRoot.json +2 -2
  136. package/data/docs/types/StepsTrigger.json +2 -2
  137. package/data/docs/types/Switch.json +38 -38
  138. package/data/docs/types/TabNavItem.json +18 -18
  139. package/data/docs/types/TabNavRoot.json +2 -2
  140. package/data/docs/types/TableBody.json +6 -6
  141. package/data/docs/types/TableCaption.json +6 -6
  142. package/data/docs/types/TableCell.json +20 -20
  143. package/data/docs/types/TableColumn.json +8 -8
  144. package/data/docs/types/TableColumnGroup.json +8 -8
  145. package/data/docs/types/TableColumnHeader.json +18 -18
  146. package/data/docs/types/TableFooter.json +6 -6
  147. package/data/docs/types/TableHeader.json +6 -6
  148. package/data/docs/types/TableRoot.json +32 -32
  149. package/data/docs/types/TableRow.json +6 -6
  150. package/data/docs/types/TableScrollArea.json +6 -6
  151. package/data/docs/types/TabsTab.json +2 -2
  152. package/data/docs/types/TagGroupRoot.json +27 -27
  153. package/data/docs/types/TagGroupTag.json +68 -68
  154. package/data/docs/types/TagGroupTagList.json +18 -18
  155. package/data/docs/types/Text.json +8 -8
  156. package/data/docs/types/TextInput.json +132 -132
  157. package/data/docs/types/TextInputField.json +129 -129
  158. package/data/docs/types/TimeInput.json +78 -78
  159. package/data/docs/types/ToggleButton.json +86 -86
  160. package/data/docs/types/ToggleButtonGroupButton.json +33 -33
  161. package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
  162. package/data/docs/types/Toolbar.json +12 -12
  163. package/data/docs/types/TooltipContent.json +31 -31
  164. package/data/docs/types/TooltipRoot.json +18 -18
  165. package/data/docs/types/Trigger.json +4 -4
  166. package/data/docs/types/UseDragAndDropOptions.json +9 -0
  167. package/data/docs/types/VisuallyHidden.json +7 -7
  168. package/data/docs/types/createArrayHandlers.json +12 -0
  169. package/data/docs/types/createItemsFromCsvDrop.json +833 -0
  170. package/data/docs/types/createItemsFromDirectoryDrop.json +833 -0
  171. package/data/docs/types/createItemsFromFileDrop.json +833 -0
  172. package/data/docs/types/createItemsFromImageDrop.json +833 -0
  173. package/data/docs/types/createItemsFromJsonDrop.json +833 -0
  174. package/data/docs/types/createItemsFromTextDrop.json +12 -0
  175. package/data/docs/types/createListDataHandlers.json +102 -0
  176. package/data/docs/types/manifest.json +29 -2
  177. package/data/docs/types/useDragAndDrop.json +174 -0
  178. package/data/docs/types/useRegion.json +1052 -0
  179. package/data/docs/types/useResponsiveSplitterSizes.json +143 -0
  180. package/package.json +6 -6
@@ -189,7 +189,7 @@
189
189
  ]
190
190
  },
191
191
  "dev": {
192
- "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport {\n DataTable,\n type DataTableProps,\n type DataTableColumnItem,\n type DataTableRowItem,\n type SortDescriptor,\n} from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nThe DataTable renders data in a tabular format with support for sorting,\nselection, resizing, and nested content. At minimum, you need to provide\n`columns` and `rows`:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },\n { id: '3', name: 'Carol Williams', email: 'carol@example.com', role: 'Editor' },\n ];\n\n return <DataTable columns={columns} rows={rows} />;\n}\n```\n\n## When to use DataTable\n\nUse the **DataTable** component when you need **interactive data management features** like sorting, filtering, selection, or pagination. It's built for complex, data-intensive interfaces.\n\n### Keep it simple\n\n**Don't use DataTable for static content.** If you only need to display read-only data without interactive features, use the simpler [Table component](/components/data-display/table) instead. It's lighter, faster, and easier to implement.\n\n**Use the Table component when you:**\n- Display static reference data (specifications, comparisons, pricing)\n- Have small datasets (between 10 - 20 rows)\n- Don't need sorting, filtering, or selection\n- Want a straightforward implementation without state management\n\n**Use DataTable when you need:**\n- Sorting columns in ascending or descending order\n- Row selection for bulk actions\n- Column management (show/hide, reorder, resize)\n- Search and filtering across data\n- Pagination for large datasets (> 50 rows)\n- Server-side data operations\n- Nested/hierarchical data with expansion\n- Dense data visualization with configurable row spacing\n\n**Example decision:**\n\n```tsx\n// ✗ Avoid - Overkill for static data\n<DataTable\n columns={threeStaticColumns}\n rows={fiveStaticRows}\n/>\n\n// ✓ Better - Use Table for static content\n<Table.Root>\n <Table.Header>{/* specification headers */}</Table.Header>\n <Table.Body>{/* specification rows */}</Table.Body>\n</Table.Root>\n```\n\n→ **[See Table component documentation](/components/data-display/table)** for simple table layouts.\n\n## Working with columns and rows\n\n### Column configuration\n\nEach column requires an `id`, `header`, and `accessor` function. Additional\noptions control sorting, resizing, and custom rendering:\n\n```tsx\nimport type { DataTableColumnItem } from \"@commercetools/nimbus\";\n\nconst columns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Product Name\",\n accessor: (row) => row.name,\n isSortable: true, // Enable sorting for this column\n isResizable: true, // Enable column resizing\n isRowHeader: true, // Mark as row header for accessibility\n width: 200, // Initial width in pixels\n minWidth: 100, // Minimum width when resizing\n maxWidth: 400, // Maximum width when resizing\n },\n {\n id: \"status\",\n header: \"Status\",\n accessor: (row) => row.status,\n // Custom rendering with the render prop\n render: ({ value }) => (\n <Badge colorPalette={value === \"Active\" ? \"positive\" : \"neutral\"}>\n {value}\n </Badge>\n ),\n },\n {\n id: \"actions\",\n header: \"Actions\",\n accessor: (row) => row.id,\n headerIcon: (\n <IconButton aria-label=\"Info\" size=\"2xs\" variant=\"ghost\">\n <Info />\n </IconButton>\n ),\n isSortable: false,\n },\n];\n```\n\n### Row data structure\n\nEach row must have a unique `id` property. Additional properties are accessed\nvia column `accessor` functions:\n\n```tsx\nimport type { DataTableRowItem } from \"@commercetools/nimbus\";\n\nconst rows: DataTableRowItem[] = [\n { id: \"1\", name: \"Product A\", status: \"Active\", price: 99.99 },\n {\n id: \"2\",\n name: \"Product B\",\n status: \"Draft\",\n price: 149.99,\n isDisabled: true,\n },\n { id: \"3\", name: \"Product C\", status: \"Active\", price: 79.99 },\n];\n```\n\n## Usage examples\n\n### Density options\n\nThe `density` prop controls row height and padding. Use `condensed` for\ndata-dense interfaces:\n\n```jsx live-dev\nconst App = () => {\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', role: 'Admin' },\n { id: '2', name: 'Bob', role: 'User' },\n { id: '3', name: 'Carol', role: 'Editor' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant={density === 'default' ? 'solid' : 'outline'}\n colorPalette=\"primary\"\n onPress={() => setDensity('default')}\n >\n Default\n </Button>\n <Button\n variant={density === 'condensed' ? 'solid' : 'outline'}\n colorPalette=\"primary\"\n onPress={() => setDensity('condensed')}\n >\n Condensed\n </Button>\n </Stack>\n <DataTable columns={columns} rows={rows} density={density} />\n </Stack>\n );\n}\n```\n\n### Sorting\n\nEnable sorting with `allowsSorting`. Individual columns can opt-out using\n`isSortable: false`:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isSortable: true },\n { id: 'age', header: 'Age', accessor: (row) => row.age, isSortable: true },\n { id: 'role', header: 'Role', accessor: (row) => row.role, isSortable: false },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', age: 30, role: 'Admin' },\n { id: '2', name: 'Bob', age: 25, role: 'User' },\n { id: '3', name: 'Carol', age: 28, role: 'Editor' },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Click column headers to sort. The \"Role\" column is not sortable.</Text>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n defaultSortDescriptor={{ column: 'name', direction: 'ascending' }}\n />\n </Stack>\n );\n}\n```\n\n### Controlled sorting\n\nFor external sort state management, use `sortDescriptor` and `onSortChange`:\n\n```jsx live-dev\nconst App = () => {\n const [sortDescriptor, setSortDescriptor] = useState({\n column: 'name',\n direction: 'ascending',\n });\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isSortable: true },\n { id: 'age', header: 'Age', accessor: (row) => row.age, isSortable: true },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', age: 30 },\n { id: '2', name: 'Bob', age: 25 },\n { id: '3', name: 'Carol', age: 28 },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>\n Sorting by: <Text as=\"strong\">{sortDescriptor.column}</Text> ({sortDescriptor.direction})\n </Text>\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant=\"outline\"\n size=\"md\"\n colorPalette=\"primary\"\n onPress={() => setSortDescriptor({ column: 'name', direction: 'ascending' })}\n >\n Sort by Name (A-Z)\n </Button>\n <Button\n variant=\"outline\"\n size=\"md\"\n colorPalette=\"primary\"\n onPress={() => setSortDescriptor({ column: 'age', direction: 'descending' })}\n >\n Sort by Age (High-Low)\n </Button>\n </Stack>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n sortDescriptor={sortDescriptor}\n onSortChange={setSortDescriptor}\n />\n </Stack>\n );\n}\n```\n\n### Row selection\n\nEnable selection with `selectionMode`. Supports `none`, `single`, or `multiple`:\n\n```jsx live-dev\nconst App = () => {\n const [selectedKeys, setSelectedKeys] = useState(new Set(['1']));\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com' },\n { id: '2', name: 'Bob', email: 'bob@example.com' },\n { id: '3', name: 'Carol', email: 'carol@example.com' },\n ];\n\n const selectedCount = selectedKeys === 'all' ? rows.length : selectedKeys.size;\n\n return (\n <Stack gap=\"400\">\n <Text>Selected: {selectedCount} row(s)</Text>\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant=\"outline\"\n colorPalette=\"primary\"\n size=\"md\"\n onPress={() => setSelectedKeys(new Set())}>\n Clear\n </Button>\n <Button size=\"md\" colorPalette=\"primary\" variant=\"outline\" onPress={() => setSelectedKeys('all')}>\n Select All\n </Button>\n </Stack>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n selectedKeys={selectedKeys}\n onSelectionChange={setSelectedKeys}\n />\n </Stack>\n );\n}\n```\n\n### Resizable columns\n\nEnable column resizing with `isResizable` on the table or individual columns:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isResizable: true, width: 150 },\n { id: 'description', header: 'Description', accessor: (row) => row.description, isResizable: true },\n { id: 'status', header: 'Status', accessor: (row) => row.status, isResizable: false },\n ];\n\n const rows = [\n { id: '1', name: 'Product A', description: 'A great product for everyday use', status: 'Active' },\n { id: '2', name: 'Product B', description: 'Premium quality item', status: 'Draft' },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Drag column borders to resize. The \"Status\" column is fixed width.</Text>\n <DataTable columns={columns} rows={rows} isResizable />\n </Stack>\n );\n}\n```\n\n### Sticky header with scrolling\n\nUse `maxHeight` to enable vertical scrolling with a sticky header:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = Array.from({ length: 20 }, (_, i) => ({\n id: String(i + 1),\n name: `User ${i + 1}`,\n role: i % 3 === 0 ? 'Admin' : i % 3 === 1 ? 'Editor' : 'Viewer',\n }));\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n maxHeight=\"300px\"\n />\n );\n}\n```\n\n### Text truncation\n\nEnable `isTruncated` to truncate long text with ellipsis:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'description', header: 'Description', accessor: (row) => row.description },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Product A',\n description: 'This is a very long description that should be truncated when displayed in the table cell to prevent layout issues.',\n },\n {\n id: '2',\n name: 'Product B',\n description: 'Another lengthy description that demonstrates how truncation works with ellipsis and maintains a clean table layout.',\n },\n ];\n\n return <DataTable columns={columns} rows={rows} isTruncated />;\n}\n```\n\n### Clickable rows\n\nUse `onRowClick` to handle row click events for navigation or detail views:\n\n```jsx live-dev\nconst App = () => {\n const [clickedRow, setClickedRow] = useState(null);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com' },\n { id: '2', name: 'Bob', email: 'bob@example.com' },\n { id: '3', name: 'Carol', email: 'carol@example.com' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>\n {clickedRow ? `Clicked: ${clickedRow.name}` : 'Click a row to see details'}\n </Text>\n <DataTable\n columns={columns}\n rows={rows}\n onRowClick={(row) => setClickedRow(row)}\n />\n </Stack>\n );\n}\n```\n\n### Search and filtering\n\nUse the `search` prop to filter rows across all visible columns:\n\n```jsx live-dev\nconst App = () => {\n const [search, setSearch] = useState('');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob Smith', role: 'Developer', department: 'Engineering' },\n { id: '3', name: 'Carol Williams', role: 'Designer', department: 'Design' },\n { id: '4', name: 'David Brown', role: 'Manager', department: 'Sales' },\n ];\n\n return (\n <Stack gap=\"400\">\n <TextInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search...\"\n width=\"300px\"\n aria-label=\"Search table\"\n />\n <DataTable columns={columns} rows={rows} search={search} />\n </Stack>\n );\n}\n```\n\n### Column visibility\n\nControl which columns are displayed using `visibleColumns`:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob', email: 'bob@example.com', role: 'User', department: 'Sales' },\n ];\n\n const toggleColumn = (colId) => {\n setVisibleColumns((prev) =>\n prev.includes(colId) ? prev.filter((id) => id !== colId) : [...prev, colId]\n );\n };\n\n return (\n <Stack gap=\"400\">\n <Stack direction=\"row\" gap=\"300\" wrap=\"wrap\">\n {columns.map((col) => (\n <Checkbox\n key={col.id}\n isSelected={visibleColumns.includes(col.id)}\n onChange={() => toggleColumn(col.id)}\n >\n {col.header}\n </Checkbox>\n ))}\n </Stack>\n <DataTable columns={columns} rows={rows} visibleColumns={visibleColumns} />\n </Stack>\n );\n}\n```\n\n### Nested rows\n\nUse `nestedKey` to enable expandable nested content:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Category', accessor: (row) => row.name },\n { id: 'count', header: 'Products', accessor: (row) => row.count },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Electronics',\n count: 3,\n children: [\n { id: '1-1', name: 'Laptops', count: 15 },\n { id: '1-2', name: 'Phones', count: 25 },\n { id: '1-3', name: 'Tablets', count: 10 },\n ],\n },\n {\n id: '2',\n name: 'Clothing',\n count: 2,\n children: [\n { id: '2-1', name: 'Shirts', count: 50 },\n { id: '2-2', name: 'Pants', count: 30 },\n ],\n },\n { id: '3', name: 'Books', count: 0 },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Click the expand button to view nested items.</Text>\n <DataTable columns={columns} rows={rows} nestedKey=\"children\" />\n </Stack>\n );\n}\n```\n\n### With footer\n\nAdd pagination or summary content using the `footer` prop:\n\n```jsx live-dev\nconst App = () => {\n const [page, setPage] = useState(1);\n const pageSize = 3;\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const allRows = Array.from({ length: 10 }, (_, i) => ({\n id: String(i + 1),\n name: `User ${i + 1}`,\n role: i % 2 === 0 ? 'Admin' : 'User',\n }));\n\n const totalPages = Math.ceil(allRows.length / pageSize);\n const rows = allRows.slice((page - 1) * pageSize, page * pageSize);\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n footer={\n <Stack direction=\"row\" justify=\"space-between\" align=\"center\" width=\"full\" px=\"400\" py=\"300\">\n <Text fontSize=\"sm\">\n Showing {(page - 1) * pageSize + 1}-{Math.min(page * pageSize, allRows.length)} of {allRows.length}\n </Text>\n <Stack direction=\"row\" gap=\"200\">\n <Button\n size=\"md\"\n colorPalette=\"primary\"\n variant=\"outline\"\n onPress={() => setPage((p) => Math.max(1, p - 1))}\n isDisabled={page === 1}\n >\n Previous\n </Button>\n <Button\n size=\"md\"\n variant=\"outline\"\n colorPalette=\"primary\"\n onPress={() => setPage((p) => Math.min(totalPages, p + 1))}\n isDisabled={page === totalPages}\n >\n Next\n </Button>\n </Stack>\n </Stack>\n }\n />\n );\n}\n```\n\n### Disabled rows\n\nUse `disabledKeys` to prevent interaction with specific rows:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'status', header: 'Status', accessor: (row) => row.status },\n ];\n\n const rows = [\n { id: '1', name: 'Active Item', status: 'Available' },\n { id: '2', name: 'Disabled Item', status: 'Unavailable' },\n { id: '3', name: 'Another Active', status: 'Available' },\n ];\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n disabledKeys={new Set(['2'])}\n />\n );\n}\n```\n\n### Row pinning\n\nPin important rows to the top of the table:\n\n```jsx live-dev\nconst App = () => {\n const [pinnedRows, setPinnedRows] = useState(new Set(['1']));\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'priority', header: 'Priority', accessor: (row) => row.priority },\n ];\n\n const rows = [\n { id: '1', name: 'High Priority Task', priority: 'High' },\n { id: '2', name: 'Normal Task', priority: 'Normal' },\n { id: '3', name: 'Low Priority Task', priority: 'Low' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>Row 1 is pinned to the top. Pinned rows stay at top when sorting.</Text>\n <DataTable\n columns={columns}\n rows={rows}\n pinnedRows={pinnedRows}\n onPinToggle={(rowId) => {\n setPinnedRows((prev) => {\n const next = new Set(prev);\n if (next.has(rowId)) {\n next.delete(rowId);\n } else {\n next.add(rowId);\n }\n return next;\n });\n }}\n allowsSorting\n />\n </Stack>\n );\n}\n```\n\n## Compound component API\n\nFor advanced customization, use the compound component pattern:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', role: 'Admin' },\n { id: '2', name: 'Bob', role: 'User' },\n ];\n\n return (\n <DataTable.Root columns={columns} rows={rows} allowsSorting>\n <DataTable.Manager />\n <DataTable.Table aria-label=\"Users table\">\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n <DataTable.Footer>\n <Stack direction=\"row\" justify=\"center\" py=\"300\">\n <Text fontSize=\"sm\" color=\"neutral.11\">Custom footer content</Text>\n </Stack>\n </DataTable.Footer>\n </DataTable.Root>\n );\n}\n```\n\n### Available sub-components\n\n| Component | Description |\n| ------------------- | ------------------------------------------------------------------------------------------------------- |\n| `DataTable.Root` | Provider component that manages state and context |\n| `DataTable.Table` | The main table element |\n| `DataTable.Header` | Table header with column headers |\n| `DataTable.Body` | Table body containing rows |\n| `DataTable.Footer` | Footer section for pagination or summaries |\n| `DataTable.Manager` | Settings panel for column visibility and layout - [see detailed docs below](#advanced-datatablemanager) |\n\n## Advanced: DataTable.Manager\n\nThe `DataTable.Manager` component provides a settings drawer for managing table\nconfiguration. It enables users to control column visibility, reorder columns,\nand adjust layout settings through an intuitive interface.\n\n### Manager overview\n\nDataTable.Manager must be used within a DataTable.Root to access table state and\ncolumns. It renders as a settings button that opens a drawer with multiple\nconfiguration tabs:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email', 'role']);\n const [isTruncated, setIsTruncated] = useState(false);\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },\n { id: '3', name: 'Carol Williams', email: 'carol@example.com', role: 'Editor' },\n ];\n\n const handleSettingsChange = (action) => {\n if (action === 'toggleTextVisibility') {\n setIsTruncated(!isTruncated);\n } else if (action === 'toggleRowDensity') {\n setDensity(density === 'default' ? 'condensed' : 'default');\n }\n };\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n isTruncated={isTruncated}\n density={density}\n onSettingsChange={handleSettingsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Users</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Column visibility\n\nThe Manager provides a dual-list interface for managing visible and hidden\ncolumns. Users can drag columns between lists, reorder visible columns, and\nsearch hidden columns:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User', department: 'Sales' },\n ];\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Team Members</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Layout settings\n\nThe Layout Settings tab allows users to control text truncation and row density.\nThese settings update the table's visual appearance in real-time:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'description']);\n const [isTruncated, setIsTruncated] = useState(false);\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'description', header: 'Description', accessor: (row) => row.description },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Product A',\n description: 'This is a very long description that will demonstrate text truncation when enabled'\n },\n {\n id: '2',\n name: 'Product B',\n description: 'Another lengthy description to show how the layout settings affect content display'\n },\n ];\n\n const handleSettingsChange = (action) => {\n if (action === 'toggleTextVisibility') {\n setIsTruncated(!isTruncated);\n } else if (action === 'toggleRowDensity') {\n setDensity(density === 'default' ? 'condensed' : 'default');\n }\n };\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n isTruncated={isTruncated}\n density={density}\n onSettingsChange={handleSettingsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Products</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Custom settings tab\n\nAdd a custom settings tab by providing the `customSettings` prop to\nDataTable.Root. This allows extending the Manager with application-specific\nconfiguration:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'status']);\n const [filterStatus, setFilterStatus] = useState('all');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'status', header: 'Status', accessor: (row) => row.status },\n ];\n\n const allRows = [\n { id: '1', name: 'Task A', status: 'Active' },\n { id: '2', name: 'Task B', status: 'Inactive' },\n { id: '3', name: 'Task C', status: 'Active' },\n ];\n\n const filteredRows = filterStatus === 'all'\n ? allRows\n : allRows.filter(row => row.status.toLowerCase() === filterStatus);\n\n const customSettingsPanel = (\n <Stack direction=\"column\" gap=\"400\">\n <Text fontWeight=\"600\">Filter Options</Text>\n <ToggleButtonGroup.Root\n selectedKeys={[filterStatus]}\n onSelectionChange={(keys) => setFilterStatus(Array.from(keys)[0])}\n >\n <ToggleButton id=\"all\">All</ToggleButton>\n <ToggleButton id=\"active\">Active</ToggleButton>\n <ToggleButton id=\"inactive\">Inactive</ToggleButton>\n </ToggleButtonGroup.Root>\n </Stack>\n );\n\n return (\n <DataTable.Root\n columns={columns}\n rows={filteredRows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n customSettings={{\n label: 'Filters',\n icon: <Icons.FilterList />,\n panel: customSettingsPanel,\n }}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Tasks</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Controlled column state with persistence\n\nManage column configuration externally for features like saving user\npreferences:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email', 'role']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n { id: 'location', header: 'Location', accessor: (row) => row.location },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Alice Johnson',\n email: 'alice@example.com',\n role: 'Admin',\n department: 'Engineering',\n location: 'New York'\n },\n ];\n\n const handleColumnsChange = (updatedColumns) => {\n const newVisibleColumns = updatedColumns.map(col => col.id);\n setVisibleColumns(newVisibleColumns);\n\n // Example: Save to localStorage\n localStorage.setItem('tableColumns', JSON.stringify(newVisibleColumns));\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={handleColumnsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Team Directory</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n <Text fontSize=\"sm\" color=\"gray.9\">\n Visible columns: {visibleColumns.join(', ')}\n </Text>\n </Stack>\n );\n}\n```\n\n### Manager: Context requirements\n\nDataTable.Manager **must** be used within a DataTable.Root component. It relies\non the DataTable context for:\n\n- Column configuration (`columns`, `visibleColumns`)\n- Layout settings (`isTruncated`, `density`)\n- Callbacks (`onColumnsChange`, `onSettingsChange`)\n- Custom settings configuration\n\nUsing Manager outside of DataTable.Root will throw an error.\n\n## Component requirements\n\n## Accessibility\n\nThe DataTable uses React Aria's Table components for full accessibility support:\n\n- **ARIA grid pattern**: The table implements the ARIA grid pattern with proper\n roles\n- **Keyboard navigation**: Full keyboard support with arrow keys, Tab, Enter,\n and Space\n- **Screen reader support**: Proper announcements for sorting, selection, and\n expansion\n\nAlways provide an accessible label for the table:\n\n```tsx\n<DataTable columns={columns} rows={rows} aria-label=\"Product inventory table\" />\n```\n\nOr with the compound component API:\n\n```tsx\n<DataTable.Root columns={columns} rows={rows}>\n <DataTable.Table aria-label=\"Product inventory table\">\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n</DataTable.Root>\n```\n\n#### Persistent ID\n\nIf your use case requires tracking and analytics for this component, it is good\npractice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"product-inventory-table\";\n\nexport const Example = () => (\n <DataTable\n id={PERSISTENT_ID}\n columns={columns}\n rows={rows}\n aria-label=\"Product inventory table\"\n />\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n\n- `Tab` / `Shift+Tab`: Move focus between interactive elements\n- `Arrow keys`: Navigate between cells and rows\n- `Enter` / `Space`: Activate sorting, selection, or expansion\n- `Escape`: Cancel current interaction\n\nThe Manager drawer also supports keyboard navigation:\n\n- `Tab` / `Shift+Tab`: Navigate between the settings button, tabs, and controls\n- `Enter` / `Space`: Open the Manager drawer, activate tabs, toggle buttons\n- `Arrow keys`: Navigate between tab panels, reorder items in drag-and-drop\n lists\n- `Escape`: Close the Manager drawer\n- Drag-and-drop keyboard operations: `Space` to pick up, arrow keys to move,\n `Space` to drop\n\n## API reference\n\n<PropsTable id=\"DataTable\" />\n\n### DataTableColumnItem\n\n```tsx\ntype DataTableColumnItem<T extends object = Record<string, unknown>> = {\n id: string; // Unique column identifier\n header: ReactNode; // Column header content\n accessor: (row: T) => ReactNode; // Function to extract cell value\n render?: (cell: {\n // Custom cell renderer\n value: unknown;\n row: T;\n column: DataTableColumnItem<T>;\n }) => ReactNode;\n isResizable?: boolean; // Enable column resizing\n width?: number | null; // Initial width in pixels\n defaultWidth?: number | null; // Default width\n minWidth?: number | null; // Minimum width when resizing\n maxWidth?: number | null; // Maximum width when resizing\n sticky?: boolean; // Stick column to edge\n isSortable?: boolean; // Enable sorting for column\n isRowHeader?: boolean; // Mark as row header (accessibility)\n headerIcon?: ReactNode; // Icon in column header\n};\n```\n\n### DataTableRowItem\n\n```tsx\ntype DataTableRowItem<T extends object = Record<string, unknown>> = T & {\n id: string; // Unique row identifier (required)\n isDisabled?: boolean; // Disable row interactions\n [key: string]: unknown;\n};\n```\n\n### SortDescriptor\n\n```tsx\ntype SortDescriptor = {\n column: string; // Column ID to sort by\n direction: \"ascending\" | \"descending\"; // Sort direction\n};\n```\n\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using DataTable\nin your application. As the component's internal functionality is already tested\nby Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic Rendering Tests\n\nVerify the DataTable renders with correct structure\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Basic rendering\", () => {\n it(\"renders table with grid role\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"grid\")).toBeInTheDocument();\n });\n\n it(\"renders column headers\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Email\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n });\n\n it(\"renders all data rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.getByText(\"Bob Smith\")).toBeInTheDocument();\n expect(screen.getByText(\"Carol Williams\")).toBeInTheDocument();\n });\n\n it(\"renders correct number of rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n // 1 header row + 3 data rows\n const allRows = screen.getAllByRole(\"row\");\n expect(allRows.length).toBe(4);\n });\n});\n```\n\n### Sorting Tests\n\nTest column sorting functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Sorting\", () => {\n it(\"renders sortable column headers when allowsSorting is true\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n const nameHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(nameHeader).toBeInTheDocument();\n });\n\n it(\"calls onSortChange when column header is clicked\", async () => {\n const user = userEvent.setup();\n const handleSortChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n onSortChange={handleSortChange}\n />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n expect(handleSortChange).toHaveBeenCalledWith({\n column: \"name\",\n direction: expect.any(String),\n });\n });\n\n it(\"displays sort indicator on sorted column\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n await waitFor(() => {\n const columnHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(columnHeader).toHaveAttribute(\"aria-sort\");\n });\n });\n});\n```\n\n### Selection Tests\n\nTest row selection functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Selection\", () => {\n it(\"renders checkboxes when selectionMode is multiple\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} selectionMode=\"multiple\" />\n </NimbusProvider>\n );\n\n const checkboxes = screen.getAllByRole(\"checkbox\");\n // 1 select-all checkbox + 3 row checkboxes\n expect(checkboxes.length).toBe(4);\n });\n\n it(\"allows selecting a row\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n onSelectionChange={handleSelectionChange}\n />\n </NimbusProvider>\n );\n\n const checkboxes = screen.getAllByRole(\"checkbox\");\n await user.click(checkboxes[1]); // First data row checkbox\n\n expect(handleSelectionChange).toHaveBeenCalled();\n });\n\n it(\"allows selecting all rows with header checkbox\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n onSelectionChange={handleSelectionChange}\n />\n </NimbusProvider>\n );\n\n const selectAllCheckbox = screen.getAllByRole(\"checkbox\")[0];\n await user.click(selectAllCheckbox);\n\n await waitFor(() => {\n expect(handleSelectionChange).toHaveBeenCalledWith(\"all\");\n });\n });\n\n it(\"supports single selection mode\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} selectionMode=\"single\" />\n </NimbusProvider>\n );\n\n const radios = screen.getAllByRole(\"checkbox\");\n expect(radios.length).toBe(3);\n });\n});\n```\n\n### Row Interaction Tests\n\nTest row click and interaction functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Row interactions\", () => {\n it(\"calls onRowClick when a row is clicked\", async () => {\n const user = userEvent.setup();\n const handleRowClick = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n search=\"\"\n onRowClick={handleRowClick}\n />\n </NimbusProvider>\n );\n\n const allRows = screen.getAllByRole(\"row\");\n const firstDataRow = allRows[1]; // Second data row (index 2 because of header)\n const clickableCell = within(firstDataRow).getAllByRole(\"rowheader\");\n await user.click(clickableCell[0]);\n\n await waitFor(() => {\n expect(handleRowClick).toHaveBeenCalled();\n });\n });\n\n it(\"applies disabled state to specified rows\", () => {\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n disabledKeys={new Set([\"2\"])}\n />\n </NimbusProvider>\n );\n\n const allRows = screen.getAllByRole(\"row\");\n const disabledRow = allRows[2]; // Second data row (index 2 because of header)\n const checkbox = within(disabledRow).getByRole(\"checkbox\");\n expect(checkbox).toBeDisabled();\n });\n});\n```\n\n### Search and Filtering Tests\n\nTest search functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Search and filtering\", () => {\n it(\"filters rows based on search term\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"Alice\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice\", { selector: \"mark\" })).toBeInTheDocument();\n expect(screen.getByText(\"Johnson\")).toBeInTheDocument();\n expect(screen.queryByText(\"Bob Smith\")).not.toBeInTheDocument();\n expect(screen.queryByText(\"Carol Williams\")).not.toBeInTheDocument();\n });\n\n it(\"shows all rows when search is empty\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.getByText(\"Bob Smith\")).toBeInTheDocument();\n expect(screen.getByText(\"Carol Williams\")).toBeInTheDocument();\n });\n\n it(\"filters across multiple columns\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"Admin\" />\n </NimbusProvider>\n );\n\n // Alice is the Admin\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.queryByText(\"Bob Smith\")).not.toBeInTheDocument();\n });\n});\n```\n\n### Column Visibility Tests\n\nTest column visibility control\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Column visibility\", () => {\n it(\"shows only specified visible columns\", () => {\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n visibleColumns={[\"name\", \"role\"]}\n />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n expect(screen.queryByText(\"Email\")).not.toBeInTheDocument();\n });\n\n it(\"shows all columns when visibleColumns is not specified\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Email\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n });\n});\n```\n\n### Density Tests\n\nTest density variants\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Density\", () => {\n it(\"renders with default density\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} density=\"default\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n\n it(\"renders with condensed density\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} density=\"condensed\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n});\n```\n\n### Nested Rows Tests\n\nTest expandable nested row functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Nested rows\", () => {\n const nestedRows: DataTableRowItem[] = [\n {\n id: \"1\",\n name: \"Parent Item\",\n email: \"parent@example.com\",\n role: \"Admin\",\n children: (\n <Box\n p=\"500\"\n bg=\"neutral.2\"\n borderRadius=\"md\"\n border=\"1px solid\"\n borderColor=\"neutral.6\"\n m=\"200 0\"\n >\n Child Item 1\n </Box>\n ),\n },\n {\n id: \"2\",\n name: \"No Children\",\n email: \"single@example.com\",\n role: \"User\",\n },\n ];\n\n it(\"renders expand button for rows with children\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n expect(expandButton).toBeInTheDocument();\n });\n\n it(\"expands nested content when expand button is clicked\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n // Initially nested content should not be visible\n expect(screen.queryByText(\"Child Item 1\")).not.toBeInTheDocument();\n\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n expect(expandButton).toBeInTheDocument();\n await user.click(expandButton);\n\n await waitFor(async () => {\n await expect(screen.getByText(\"Child Item 1\")).toBeInTheDocument();\n });\n });\n\n it(\"collapses nested content when collapse button is clicked\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n // Expand first\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n await user.click(expandButton);\n\n await waitFor(() => {\n expect(screen.getByText(\"Child Item 1\")).toBeInTheDocument();\n });\n\n // Collapse\n const collapseButton = screen.getByRole(\"button\", { name: /collapse/i });\n await user.click(collapseButton);\n\n await waitFor(() => {\n expect(screen.queryByText(\"Child Item 1\")).not.toBeInTheDocument();\n });\n });\n});\n```\n\n### Empty State Tests\n\nTest empty state rendering\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Empty state\", () => {\n it(\"renders custom empty state when no rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={[]} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"No Data\")).toBeInTheDocument();\n });\n\n it(\"renders empty state when search matches no rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"nonexistent\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"No Data\")).toBeInTheDocument();\n });\n});\n```\n\n### Accessibility Tests\n\nTest accessibility features\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Accessibility\", () => {\n it(\"has accessible grid role\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} aria-label=\"Users table\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n\n it(\"has proper row structure\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n const tableRows = screen.getAllByRole(\"row\");\n expect(tableRows.length).toBeGreaterThan(0);\n\n // Check that rows contain cells\n const firstDataRow = tableRows[1];\n const cells = within(firstDataRow).getAllByRole(\"gridcell\");\n expect(cells.length).toBeGreaterThan(0);\n });\n\n it(\"has sortable columns with aria-sort attribute\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n await waitFor(() => {\n const columnHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(columnHeader).toHaveAttribute(\"aria-sort\");\n });\n });\n});\n```\n\n### Manager: Basic Rendering Tests\n\nVerify the Manager button renders and opens the settings drawer\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Basic rendering\", () => {\n it(\"renders the settings button\", () => {\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /settings/i })\n ).toBeInTheDocument();\n });\n\n it(\"settings button is clickable\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n\n // Verify button can be clicked\n await user.click(settingsButton);\n\n // Button should still be in the document after click\n expect(settingsButton).toBeInTheDocument();\n });\n});\n```\n\n### Manager: Column Management Tests\n\nTest column visibility and reordering functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Column management\", () => {\n it(\"displays visible and hidden columns in separate lists\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n {\n id: \"role\",\n header: \"Role\",\n accessor: (row: Record<string, unknown>) => row.role as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\", role: \"Admin\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\", \"email\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n const visibleList = screen.getByTestId(\"visible-columns-list\");\n const hiddenList = screen.getByTestId(\"hidden-columns-list\");\n\n expect(within(visibleList).getByText(\"Name\")).toBeInTheDocument();\n expect(within(visibleList).getByText(\"Email\")).toBeInTheDocument();\n expect(within(hiddenList).getByText(\"Role\")).toBeInTheDocument();\n });\n });\n\n it(\"calls onColumnsChange when column visibility changes\", async () => {\n const user = userEvent.setup();\n const handleColumnsChange = vi.fn();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n onColumnsChange={handleColumnsChange}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n // Note: Full drag-and-drop testing would require more complex setup\n // This verifies the callback is available\n expect(handleColumnsChange).toBeDefined();\n });\n});\n```\n\n### Manager: Layout Settings Tests\n\nTest text visibility and row density controls\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Layout settings\", () => {\n it(\"displays layout settings tab\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n expect(screen.getByText(/layout settings/i)).toBeInTheDocument();\n });\n });\n\n it(\"calls onSettingsChange when layout settings change\", async () => {\n const user = userEvent.setup();\n const handleSettingsChange = vi.fn();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n onSettingsChange={handleSettingsChange}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n // Note: Actual toggle interaction testing would require finding specific buttons\n // This verifies the callback is wired up\n expect(handleSettingsChange).toBeDefined();\n });\n});\n```\n\n### Manager: Custom Settings Tests\n\nTest custom settings tab integration\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Custom settings\", () => {\n it(\"displays custom settings tab when provided\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n const customSettings = {\n label: \"Filters\",\n panel: <div>Custom Filter Panel</div>,\n };\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n customSettings={customSettings}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n expect(screen.getByText(\"Filters\")).toBeInTheDocument();\n });\n });\n\n it(\"renders custom settings panel content\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n const customSettings = {\n label: \"Advanced\",\n panel: <div data-testid=\"custom-panel\">Custom Panel Content</div>,\n };\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n customSettings={customSettings}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n const advancedTab = screen.getByText(\"Advanced\");\n expect(advancedTab).toBeInTheDocument();\n });\n\n // Click the custom tab\n const advancedTab = screen.getByText(\"Advanced\");\n await user.click(advancedTab);\n\n await waitFor(() => {\n expect(screen.getByTestId(\"custom-panel\")).toBeInTheDocument();\n expect(screen.getByText(\"Custom Panel Content\")).toBeInTheDocument();\n });\n });\n});\n```\n\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-datatable--docs)\n- [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table.html)\n- [ARIA Grid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/)\n\n**Related components**\n\n- **[Table Component](/components/data-display/table)** - Use the simpler Table component for static, read-only data without interactive features\n",
192
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport {\n DataTable,\n type DataTableProps,\n type DataTableColumnItem,\n type DataTableRowItem,\n type SortDescriptor,\n} from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\nThe DataTable renders data in a tabular format with support for sorting,\nselection, resizing, and nested content. At minimum, you need to provide\n`columns` and `rows`:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },\n { id: '3', name: 'Carol Williams', email: 'carol@example.com', role: 'Editor' },\n ];\n\n return <DataTable columns={columns} rows={rows} />;\n}\n```\n\n## When to use DataTable\n\nUse the **DataTable** component when you need **interactive data management features** like sorting, filtering, selection, or pagination. It's built for complex, data-intensive interfaces.\n\n### Keep it simple\n\n**Don't use DataTable for static content.** If you only need to display read-only data without interactive features, use the simpler [Table component](/components/data-display/table) instead. It's lighter, faster, and easier to implement.\n\n**Use the Table component when you:**\n- Display static reference data (specifications, comparisons, pricing)\n- Have small datasets (between 10 - 20 rows)\n- Don't need sorting, filtering, or selection\n- Want a straightforward implementation without state management\n\n**Use DataTable when you need:**\n- Sorting columns in ascending or descending order\n- Row selection for bulk actions\n- Column management (show/hide, reorder, resize)\n- Search and filtering across data\n- Pagination for large datasets (> 50 rows)\n- Server-side data operations\n- Nested/hierarchical data with expansion\n- Dense data visualization with configurable row spacing\n\n**Example decision:**\n\n```tsx\n// ✗ Avoid - Overkill for static data\n<DataTable\n columns={threeStaticColumns}\n rows={fiveStaticRows}\n/>\n\n// ✓ Better - Use Table for static content\n<Table.Root>\n <Table.Header>{/* specification headers */}</Table.Header>\n <Table.Body>{/* specification rows */}</Table.Body>\n</Table.Root>\n```\n\n→ **[See Table component documentation](/components/data-display/table)** for simple table layouts.\n\n## Working with columns and rows\n\n### Column configuration\n\nEach column requires an `id`, `header`, and `accessor` function. Additional\noptions control sorting, resizing, and custom rendering:\n\n```tsx\nimport type { DataTableColumnItem } from \"@commercetools/nimbus\";\n\nconst columns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Product Name\",\n accessor: (row) => row.name,\n isSortable: true, // Enable sorting for this column\n isResizable: true, // Enable column resizing\n isRowHeader: true, // Mark as row header for accessibility\n width: 200, // Initial width in pixels\n minWidth: 100, // Minimum width when resizing\n maxWidth: 400, // Maximum width when resizing\n },\n {\n id: \"status\",\n header: \"Status\",\n accessor: (row) => row.status,\n // Custom rendering with the render prop\n render: ({ value }) => (\n <Badge colorPalette={value === \"Active\" ? \"positive\" : \"neutral\"}>\n {value}\n </Badge>\n ),\n },\n {\n id: \"actions\",\n header: \"Actions\",\n accessor: (row) => row.id,\n headerIcon: (\n <IconButton aria-label=\"Info\" size=\"2xs\" variant=\"ghost\">\n <Info />\n </IconButton>\n ),\n isSortable: false,\n },\n];\n```\n\n### Row data structure\n\nEach row must have a unique `id` property. Additional properties are accessed\nvia column `accessor` functions:\n\n```tsx\nimport type { DataTableRowItem } from \"@commercetools/nimbus\";\n\nconst rows: DataTableRowItem[] = [\n { id: \"1\", name: \"Product A\", status: \"Active\", price: 99.99 },\n {\n id: \"2\",\n name: \"Product B\",\n status: \"Draft\",\n price: 149.99,\n isDisabled: true,\n },\n { id: \"3\", name: \"Product C\", status: \"Active\", price: 79.99 },\n];\n```\n\n## Usage examples\n\n### Density options\n\nThe `density` prop controls row height and padding. Use `condensed` for\ndata-dense interfaces:\n\n```jsx live-dev\nconst App = () => {\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', role: 'Admin' },\n { id: '2', name: 'Bob', role: 'User' },\n { id: '3', name: 'Carol', role: 'Editor' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant={density === 'default' ? 'solid' : 'outline'}\n colorPalette=\"primary\"\n onPress={() => setDensity('default')}\n >\n Default\n </Button>\n <Button\n variant={density === 'condensed' ? 'solid' : 'outline'}\n colorPalette=\"primary\"\n onPress={() => setDensity('condensed')}\n >\n Condensed\n </Button>\n </Stack>\n <DataTable columns={columns} rows={rows} density={density} />\n </Stack>\n );\n}\n```\n\n### Sorting\n\nEnable sorting with `allowsSorting`. Individual columns can opt-out using\n`isSortable: false`:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isSortable: true },\n { id: 'age', header: 'Age', accessor: (row) => row.age, isSortable: true },\n { id: 'role', header: 'Role', accessor: (row) => row.role, isSortable: false },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', age: 30, role: 'Admin' },\n { id: '2', name: 'Bob', age: 25, role: 'User' },\n { id: '3', name: 'Carol', age: 28, role: 'Editor' },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Click column headers to sort. The \"Role\" column is not sortable.</Text>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n defaultSortDescriptor={{ column: 'name', direction: 'ascending' }}\n />\n </Stack>\n );\n}\n```\n\n### Controlled sorting\n\nFor external sort state management, use `sortDescriptor` and `onSortChange`:\n\n```jsx live-dev\nconst App = () => {\n const [sortDescriptor, setSortDescriptor] = useState({\n column: 'name',\n direction: 'ascending',\n });\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isSortable: true },\n { id: 'age', header: 'Age', accessor: (row) => row.age, isSortable: true },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', age: 30 },\n { id: '2', name: 'Bob', age: 25 },\n { id: '3', name: 'Carol', age: 28 },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>\n Sorting by: <Text as=\"strong\">{sortDescriptor.column}</Text> ({sortDescriptor.direction})\n </Text>\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant=\"outline\"\n size=\"md\"\n colorPalette=\"primary\"\n onPress={() => setSortDescriptor({ column: 'name', direction: 'ascending' })}\n >\n Sort by Name (A-Z)\n </Button>\n <Button\n variant=\"outline\"\n size=\"md\"\n colorPalette=\"primary\"\n onPress={() => setSortDescriptor({ column: 'age', direction: 'descending' })}\n >\n Sort by Age (High-Low)\n </Button>\n </Stack>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n sortDescriptor={sortDescriptor}\n onSortChange={setSortDescriptor}\n />\n </Stack>\n );\n}\n```\n\n### Row selection\n\nEnable selection with `selectionMode`. Supports `none`, `single`, or `multiple`:\n\n```jsx live-dev\nconst App = () => {\n const [selectedKeys, setSelectedKeys] = useState(new Set(['1']));\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com' },\n { id: '2', name: 'Bob', email: 'bob@example.com' },\n { id: '3', name: 'Carol', email: 'carol@example.com' },\n ];\n\n const selectedCount = selectedKeys === 'all' ? rows.length : selectedKeys.size;\n\n return (\n <Stack gap=\"400\">\n <Text>Selected: {selectedCount} row(s)</Text>\n <Stack direction=\"row\" gap=\"300\">\n <Button\n variant=\"outline\"\n colorPalette=\"primary\"\n size=\"md\"\n onPress={() => setSelectedKeys(new Set())}>\n Clear\n </Button>\n <Button size=\"md\" colorPalette=\"primary\" variant=\"outline\" onPress={() => setSelectedKeys('all')}>\n Select All\n </Button>\n </Stack>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n selectedKeys={selectedKeys}\n onSelectionChange={setSelectedKeys}\n />\n </Stack>\n );\n}\n```\n\n### Resizable columns\n\nEnable column resizing with `isResizable` on the table or individual columns:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name, isResizable: true, width: 150 },\n { id: 'description', header: 'Description', accessor: (row) => row.description, isResizable: true },\n { id: 'status', header: 'Status', accessor: (row) => row.status, isResizable: false },\n ];\n\n const rows = [\n { id: '1', name: 'Product A', description: 'A great product for everyday use', status: 'Active' },\n { id: '2', name: 'Product B', description: 'Premium quality item', status: 'Draft' },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Drag column borders to resize. The \"Status\" column is fixed width.</Text>\n <DataTable columns={columns} rows={rows} isResizable />\n </Stack>\n );\n}\n```\n\n### Sticky header with scrolling\n\nUse `maxHeight` to enable vertical scrolling with a sticky header:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = Array.from({ length: 20 }, (_, i) => ({\n id: String(i + 1),\n name: `User ${i + 1}`,\n role: i % 3 === 0 ? 'Admin' : i % 3 === 1 ? 'Editor' : 'Viewer',\n }));\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n maxHeight=\"300px\"\n />\n );\n}\n```\n\n### Text truncation\n\nEnable `isTruncated` to truncate long text with ellipsis:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'description', header: 'Description', accessor: (row) => row.description },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Product A',\n description: 'This is a very long description that should be truncated when displayed in the table cell to prevent layout issues.',\n },\n {\n id: '2',\n name: 'Product B',\n description: 'Another lengthy description that demonstrates how truncation works with ellipsis and maintains a clean table layout.',\n },\n ];\n\n return <DataTable columns={columns} rows={rows} isTruncated />;\n}\n```\n\n### Clickable rows\n\nUse `onRowClick` to handle row click events for navigation or detail views:\n\n```jsx live-dev\nconst App = () => {\n const [clickedRow, setClickedRow] = useState(null);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com' },\n { id: '2', name: 'Bob', email: 'bob@example.com' },\n { id: '3', name: 'Carol', email: 'carol@example.com' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>\n {clickedRow ? `Clicked: ${clickedRow.name}` : 'Click a row to see details'}\n </Text>\n <DataTable\n columns={columns}\n rows={rows}\n onRowClick={(row) => setClickedRow(row)}\n />\n </Stack>\n );\n}\n```\n\n### Search and filtering\n\nUse the `search` prop to filter rows across all visible columns:\n\n```jsx live-dev\nconst App = () => {\n const [search, setSearch] = useState('');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob Smith', role: 'Developer', department: 'Engineering' },\n { id: '3', name: 'Carol Williams', role: 'Designer', department: 'Design' },\n { id: '4', name: 'David Brown', role: 'Manager', department: 'Sales' },\n ];\n\n return (\n <Stack gap=\"400\">\n <TextInput\n value={search}\n onChange={setSearch}\n placeholder=\"Search...\"\n width=\"300px\"\n aria-label=\"Search table\"\n />\n <DataTable columns={columns} rows={rows} search={search} />\n </Stack>\n );\n}\n```\n\n### Column visibility\n\nControl which columns are displayed using `visibleColumns`:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', email: 'alice@example.com', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob', email: 'bob@example.com', role: 'User', department: 'Sales' },\n ];\n\n const toggleColumn = (colId) => {\n setVisibleColumns((prev) =>\n prev.includes(colId) ? prev.filter((id) => id !== colId) : [...prev, colId]\n );\n };\n\n return (\n <Stack gap=\"400\">\n <Stack direction=\"row\" gap=\"300\" wrap=\"wrap\">\n {columns.map((col) => (\n <Checkbox\n key={col.id}\n isSelected={visibleColumns.includes(col.id)}\n onChange={() => toggleColumn(col.id)}\n >\n {col.header}\n </Checkbox>\n ))}\n </Stack>\n <DataTable columns={columns} rows={rows} visibleColumns={visibleColumns} />\n </Stack>\n );\n}\n```\n\n### Nested rows\n\nUse `nestedKey` to enable expandable nested content:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Category', accessor: (row) => row.name },\n { id: 'count', header: 'Products', accessor: (row) => row.count },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Electronics',\n count: 3,\n children: [\n { id: '1-1', name: 'Laptops', count: 15 },\n { id: '1-2', name: 'Phones', count: 25 },\n { id: '1-3', name: 'Tablets', count: 10 },\n ],\n },\n {\n id: '2',\n name: 'Clothing',\n count: 2,\n children: [\n { id: '2-1', name: 'Shirts', count: 50 },\n { id: '2-2', name: 'Pants', count: 30 },\n ],\n },\n { id: '3', name: 'Books', count: 0 },\n ];\n\n return (\n <Stack gap=\"300\">\n <Text>Click the expand button to view nested items.</Text>\n <DataTable columns={columns} rows={rows} nestedKey=\"children\" />\n </Stack>\n );\n}\n```\n\n### With footer\n\nAdd pagination or summary content using the `footer` prop:\n\n```jsx live-dev\nconst App = () => {\n const [page, setPage] = useState(1);\n const pageSize = 3;\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const allRows = Array.from({ length: 10 }, (_, i) => ({\n id: String(i + 1),\n name: `User ${i + 1}`,\n role: i % 2 === 0 ? 'Admin' : 'User',\n }));\n\n const totalPages = Math.ceil(allRows.length / pageSize);\n const rows = allRows.slice((page - 1) * pageSize, page * pageSize);\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n footer={\n <Stack direction=\"row\" justify=\"space-between\" align=\"center\" width=\"full\" px=\"400\" py=\"300\">\n <Text fontSize=\"sm\">\n Showing {(page - 1) * pageSize + 1}-{Math.min(page * pageSize, allRows.length)} of {allRows.length}\n </Text>\n <Stack direction=\"row\" gap=\"200\">\n <Button\n size=\"md\"\n colorPalette=\"primary\"\n variant=\"outline\"\n onPress={() => setPage((p) => Math.max(1, p - 1))}\n isDisabled={page === 1}\n >\n Previous\n </Button>\n <Button\n size=\"md\"\n variant=\"outline\"\n colorPalette=\"primary\"\n onPress={() => setPage((p) => Math.min(totalPages, p + 1))}\n isDisabled={page === totalPages}\n >\n Next\n </Button>\n </Stack>\n </Stack>\n }\n />\n );\n}\n```\n\n### Disabled rows\n\nUse `disabledKeys` to prevent interaction with specific rows:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'status', header: 'Status', accessor: (row) => row.status },\n ];\n\n const rows = [\n { id: '1', name: 'Active Item', status: 'Available' },\n { id: '2', name: 'Disabled Item', status: 'Unavailable' },\n { id: '3', name: 'Another Active', status: 'Available' },\n ];\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n disabledKeys={new Set(['2'])}\n />\n );\n}\n```\n\n### Row pinning\n\nPin important rows to the top of the table:\n\n```jsx live-dev\nconst App = () => {\n const [pinnedRows, setPinnedRows] = useState(new Set(['1']));\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'priority', header: 'Priority', accessor: (row) => row.priority },\n ];\n\n const rows = [\n { id: '1', name: 'High Priority Task', priority: 'High' },\n { id: '2', name: 'Normal Task', priority: 'Normal' },\n { id: '3', name: 'Low Priority Task', priority: 'Low' },\n ];\n\n return (\n <Stack gap=\"400\">\n <Text>Row 1 is pinned to the top. Pinned rows stay at top when sorting.</Text>\n <DataTable\n columns={columns}\n rows={rows}\n pinnedRows={pinnedRows}\n onPinToggle={(rowId) => {\n setPinnedRows((prev) => {\n const next = new Set(prev);\n if (next.has(rowId)) {\n next.delete(rowId);\n } else {\n next.add(rowId);\n }\n return next;\n });\n }}\n allowsSorting\n />\n </Stack>\n );\n}\n```\n\n## Drag and drop\n\nDataTable supports row reordering via drag and drop using the shared `useDragAndDrop` hook. Pass `dragAndDropHooks` to `DataTable` to enable it.\n\n### Reorderable rows\n\n```jsx live-dev\nimport { DataTable, useDragAndDrop, createArrayHandlers } from '@commercetools/nimbus';\n\nconst App = () => {\n const [rows, setRows] = useState([\n { id: '1', name: 'Alice', role: 'Admin' },\n { id: '2', name: 'Bob', role: 'User' },\n { id: '3', name: 'Carol', role: 'Manager' },\n ]);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const { dragAndDropHooks } = useDragAndDrop({\n ...createArrayHandlers(setRows, (row) => row.id),\n });\n\n return (\n <DataTable\n columns={columns}\n rows={rows}\n dragAndDropHooks={dragAndDropHooks}\n />\n );\n}\n```\n\nWhen `dragAndDropHooks` is provided:\n- A **drag handle column** with a grip icon is automatically added as the first column\n- Rows can be dragged using mouse or touch from anywhere on the row\n- **Keyboard accessible**: focus the drag handle, press Enter to start drag, use arrow keys to move, Enter to drop, Escape to cancel\n- A **drop indicator** shows where the row will be placed\n- **Double-click text selection** is preserved via programmatic word selection\n- Drag and drop works alongside **row selection** and **clickable rows**\n\n> **Note:** When combining drag and drop with **row pinning**, pinned rows can\n> be reordered among themselves and non-pinned rows among themselves, but items\n> cannot be dragged between pinned and non-pinned groups.\n\n### Namespace isolation\n\nUse `dragNamespace` to prevent drag between unrelated tables:\n\n```tsx\nconst { dragAndDropHooks } = useDragAndDrop({\n dragNamespace: 'my-table',\n ...createArrayHandlers(setRows, (row) => row.id),\n});\n```\n\nSee the [`useDragAndDrop` hook documentation](/hooks/use-drag-and-drop) for external drop support, outgoing formats, and helper functions.\n\n## Compound component API\n\nFor advanced customization, use the compound component pattern:\n\n```jsx live-dev\nconst App = () => {\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice', role: 'Admin' },\n { id: '2', name: 'Bob', role: 'User' },\n ];\n\n return (\n <DataTable.Root columns={columns} rows={rows} allowsSorting>\n <DataTable.Manager />\n <DataTable.Table aria-label=\"Users table\">\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n <DataTable.Footer>\n <Stack direction=\"row\" justify=\"center\" py=\"300\">\n <Text fontSize=\"sm\" color=\"neutral.11\">Custom footer content</Text>\n </Stack>\n </DataTable.Footer>\n </DataTable.Root>\n );\n}\n```\n\n### Available sub-components\n\n| Component | Description |\n| ------------------- | ------------------------------------------------------------------------------------------------------- |\n| `DataTable.Root` | Provider component that manages state and context |\n| `DataTable.Table` | The main table element |\n| `DataTable.Header` | Table header with column headers |\n| `DataTable.Body` | Table body containing rows |\n| `DataTable.Footer` | Footer section for pagination or summaries |\n| `DataTable.Manager` | Settings panel for column visibility and layout - [see detailed docs below](#advanced-datatablemanager) |\n\n## Advanced: DataTable.Manager\n\nThe `DataTable.Manager` component provides a settings drawer for managing table\nconfiguration. It enables users to control column visibility, reorder columns,\nand adjust layout settings through an intuitive interface.\n\n### Manager overview\n\nDataTable.Manager must be used within a DataTable.Root to access table state and\ncolumns. It renders as a settings button that opens a drawer with multiple\nconfiguration tabs:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email', 'role']);\n const [isTruncated, setIsTruncated] = useState(false);\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User' },\n { id: '3', name: 'Carol Williams', email: 'carol@example.com', role: 'Editor' },\n ];\n\n const handleSettingsChange = (action) => {\n if (action === 'toggleTextVisibility') {\n setIsTruncated(!isTruncated);\n } else if (action === 'toggleRowDensity') {\n setDensity(density === 'default' ? 'condensed' : 'default');\n }\n };\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n isTruncated={isTruncated}\n density={density}\n onSettingsChange={handleSettingsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Users</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Column visibility\n\nThe Manager provides a dual-list interface for managing visible and hidden\ncolumns. Users can drag columns between lists, reorder visible columns, and\nsearch hidden columns:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n ];\n\n const rows = [\n { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', department: 'Engineering' },\n { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'User', department: 'Sales' },\n ];\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Team Members</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Layout settings\n\nThe Layout Settings tab allows users to control text truncation and row density.\nThese settings update the table's visual appearance in real-time:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'description']);\n const [isTruncated, setIsTruncated] = useState(false);\n const [density, setDensity] = useState('default');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'description', header: 'Description', accessor: (row) => row.description },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Product A',\n description: 'This is a very long description that will demonstrate text truncation when enabled'\n },\n {\n id: '2',\n name: 'Product B',\n description: 'Another lengthy description to show how the layout settings affect content display'\n },\n ];\n\n const handleSettingsChange = (action) => {\n if (action === 'toggleTextVisibility') {\n setIsTruncated(!isTruncated);\n } else if (action === 'toggleRowDensity') {\n setDensity(density === 'default' ? 'condensed' : 'default');\n }\n };\n\n return (\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n isTruncated={isTruncated}\n density={density}\n onSettingsChange={handleSettingsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Products</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Custom settings tab\n\nAdd a custom settings tab by providing the `customSettings` prop to\nDataTable.Root. This allows extending the Manager with application-specific\nconfiguration:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'status']);\n const [filterStatus, setFilterStatus] = useState('all');\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'status', header: 'Status', accessor: (row) => row.status },\n ];\n\n const allRows = [\n { id: '1', name: 'Task A', status: 'Active' },\n { id: '2', name: 'Task B', status: 'Inactive' },\n { id: '3', name: 'Task C', status: 'Active' },\n ];\n\n const filteredRows = filterStatus === 'all'\n ? allRows\n : allRows.filter(row => row.status.toLowerCase() === filterStatus);\n\n const customSettingsPanel = (\n <Stack direction=\"column\" gap=\"400\">\n <Text fontWeight=\"600\">Filter Options</Text>\n <ToggleButtonGroup.Root\n selectedKeys={[filterStatus]}\n onSelectionChange={(keys) => setFilterStatus(Array.from(keys)[0])}\n >\n <ToggleButton id=\"all\">All</ToggleButton>\n <ToggleButton id=\"active\">Active</ToggleButton>\n <ToggleButton id=\"inactive\">Inactive</ToggleButton>\n </ToggleButtonGroup.Root>\n </Stack>\n );\n\n return (\n <DataTable.Root\n columns={columns}\n rows={filteredRows}\n visibleColumns={visibleColumns}\n onColumnsChange={(updatedColumns) => {\n setVisibleColumns(updatedColumns.map(col => col.id));\n }}\n customSettings={{\n label: 'Filters',\n icon: <Icons.FilterList />,\n panel: customSettingsPanel,\n }}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Tasks</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n );\n}\n```\n\n### Manager: Controlled column state with persistence\n\nManage column configuration externally for features like saving user\npreferences:\n\n```jsx live-dev\nconst App = () => {\n const [visibleColumns, setVisibleColumns] = useState(['name', 'email', 'role']);\n\n const columns = [\n { id: 'name', header: 'Name', accessor: (row) => row.name },\n { id: 'email', header: 'Email', accessor: (row) => row.email },\n { id: 'role', header: 'Role', accessor: (row) => row.role },\n { id: 'department', header: 'Department', accessor: (row) => row.department },\n { id: 'location', header: 'Location', accessor: (row) => row.location },\n ];\n\n const rows = [\n {\n id: '1',\n name: 'Alice Johnson',\n email: 'alice@example.com',\n role: 'Admin',\n department: 'Engineering',\n location: 'New York'\n },\n ];\n\n const handleColumnsChange = (updatedColumns) => {\n const newVisibleColumns = updatedColumns.map(col => col.id);\n setVisibleColumns(newVisibleColumns);\n\n // Example: Save to localStorage\n localStorage.setItem('tableColumns', JSON.stringify(newVisibleColumns));\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <DataTable.Root\n columns={columns}\n rows={rows}\n visibleColumns={visibleColumns}\n onColumnsChange={handleColumnsChange}\n >\n <Stack direction=\"row\" justifyContent=\"space-between\" mb=\"400\">\n <Text fontSize=\"lg\" fontWeight=\"600\">Team Directory</Text>\n <DataTable.Manager />\n </Stack>\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n <Text fontSize=\"sm\" color=\"gray.9\">\n Visible columns: {visibleColumns.join(', ')}\n </Text>\n </Stack>\n );\n}\n```\n\n### Manager: Context requirements\n\nDataTable.Manager **must** be used within a DataTable.Root component. It relies\non the DataTable context for:\n\n- Column configuration (`columns`, `visibleColumns`)\n- Layout settings (`isTruncated`, `density`)\n- Callbacks (`onColumnsChange`, `onSettingsChange`)\n- Custom settings configuration\n\nUsing Manager outside of DataTable.Root will throw an error.\n\n## Component requirements\n\n## Accessibility\n\nThe DataTable uses React Aria's Table components for full accessibility support:\n\n- **ARIA grid pattern**: The table implements the ARIA grid pattern with proper\n roles\n- **Keyboard navigation**: Full keyboard support with arrow keys, Tab, Enter,\n and Space\n- **Screen reader support**: Proper announcements for sorting, selection, and\n expansion\n\nAlways provide an accessible label for the table:\n\n```tsx\n<DataTable columns={columns} rows={rows} aria-label=\"Product inventory table\" />\n```\n\nOr with the compound component API:\n\n```tsx\n<DataTable.Root columns={columns} rows={rows}>\n <DataTable.Table aria-label=\"Product inventory table\">\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n</DataTable.Root>\n```\n\n#### Persistent ID\n\nIf your use case requires tracking and analytics for this component, it is good\npractice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"product-inventory-table\";\n\nexport const Example = () => (\n <DataTable\n id={PERSISTENT_ID}\n columns={columns}\n rows={rows}\n aria-label=\"Product inventory table\"\n />\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n\n- `Tab` / `Shift+Tab`: Move focus between interactive elements\n- `Arrow keys`: Navigate between cells and rows\n- `Enter` / `Space`: Activate sorting, selection, or expansion\n- `Escape`: Cancel current interaction\n\nThe Manager drawer also supports keyboard navigation:\n\n- `Tab` / `Shift+Tab`: Navigate between the settings button, tabs, and controls\n- `Enter` / `Space`: Open the Manager drawer, activate tabs, toggle buttons\n- `Arrow keys`: Navigate between tab panels, reorder items in drag-and-drop\n lists\n- `Escape`: Close the Manager drawer\n- Drag-and-drop keyboard operations: `Enter` / `Space` to pick up, arrow keys to move,\n `Enter` / `Space` to drop\n\n## API reference\n\n<PropsTable id=\"DataTable\" />\n\n### DataTableColumnItem\n\n```tsx\ntype DataTableColumnItem<T extends object = Record<string, unknown>> = {\n id: string; // Unique column identifier\n header: ReactNode; // Column header content\n accessor: (row: T) => ReactNode; // Function to extract cell value\n render?: (cell: {\n // Custom cell renderer\n value: unknown;\n row: T;\n column: DataTableColumnItem<T>;\n }) => ReactNode;\n isResizable?: boolean; // Enable column resizing\n width?: number | null; // Initial width in pixels\n defaultWidth?: number | null; // Default width\n minWidth?: number | null; // Minimum width when resizing\n maxWidth?: number | null; // Maximum width when resizing\n sticky?: boolean; // Stick column to edge\n isSortable?: boolean; // Enable sorting for column\n isRowHeader?: boolean; // Mark as row header (accessibility)\n headerIcon?: ReactNode; // Icon in column header\n};\n```\n\n### DataTableRowItem\n\n```tsx\ntype DataTableRowItem<T extends object = Record<string, unknown>> = T & {\n id: string; // Unique row identifier (required)\n isDisabled?: boolean; // Disable row interactions\n [key: string]: unknown;\n};\n```\n\n### SortDescriptor\n\n```tsx\ntype SortDescriptor = {\n column: string; // Column ID to sort by\n direction: \"ascending\" | \"descending\"; // Sort direction\n};\n```\n\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using DataTable\nin your application. As the component's internal functionality is already tested\nby Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic Rendering Tests\n\nVerify the DataTable renders with correct structure\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Basic rendering\", () => {\n it(\"renders table with grid role\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"grid\")).toBeInTheDocument();\n });\n\n it(\"renders column headers\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Email\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n });\n\n it(\"renders all data rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.getByText(\"Bob Smith\")).toBeInTheDocument();\n expect(screen.getByText(\"Carol Williams\")).toBeInTheDocument();\n });\n\n it(\"renders correct number of rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n // 1 header row + 3 data rows\n const allRows = screen.getAllByRole(\"row\");\n expect(allRows.length).toBe(4);\n });\n});\n```\n\n### Sorting Tests\n\nTest column sorting functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Sorting\", () => {\n it(\"renders sortable column headers when allowsSorting is true\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n const nameHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(nameHeader).toBeInTheDocument();\n });\n\n it(\"calls onSortChange when column header is clicked\", async () => {\n const user = userEvent.setup();\n const handleSortChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n allowsSorting\n onSortChange={handleSortChange}\n />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n expect(handleSortChange).toHaveBeenCalledWith({\n column: \"name\",\n direction: expect.any(String),\n });\n });\n\n it(\"displays sort indicator on sorted column\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n await waitFor(() => {\n const columnHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(columnHeader).toHaveAttribute(\"aria-sort\");\n });\n });\n});\n```\n\n### Selection Tests\n\nTest row selection functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Selection\", () => {\n it(\"renders checkboxes when selectionMode is multiple\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} selectionMode=\"multiple\" />\n </NimbusProvider>\n );\n\n const checkboxes = screen.getAllByRole(\"checkbox\");\n // 1 select-all checkbox + 3 row checkboxes\n expect(checkboxes.length).toBe(4);\n });\n\n it(\"allows selecting a row\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n onSelectionChange={handleSelectionChange}\n />\n </NimbusProvider>\n );\n\n const checkboxes = screen.getAllByRole(\"checkbox\");\n await user.click(checkboxes[1]); // First data row checkbox\n\n expect(handleSelectionChange).toHaveBeenCalled();\n });\n\n it(\"allows selecting all rows with header checkbox\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n onSelectionChange={handleSelectionChange}\n />\n </NimbusProvider>\n );\n\n const selectAllCheckbox = screen.getAllByRole(\"checkbox\")[0];\n await user.click(selectAllCheckbox);\n\n await waitFor(() => {\n expect(handleSelectionChange).toHaveBeenCalledWith(\"all\");\n });\n });\n\n it(\"supports single selection mode\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} selectionMode=\"single\" />\n </NimbusProvider>\n );\n\n const radios = screen.getAllByRole(\"checkbox\");\n expect(radios.length).toBe(3);\n });\n});\n```\n\n### Row Interaction Tests\n\nTest row click and interaction functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Row interactions\", () => {\n it(\"calls onRowClick when a row is clicked\", async () => {\n const user = userEvent.setup();\n const handleRowClick = vi.fn();\n\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n search=\"\"\n onRowClick={handleRowClick}\n />\n </NimbusProvider>\n );\n\n const allRows = screen.getAllByRole(\"row\");\n const firstDataRow = allRows[1]; // Second data row (index 2 because of header)\n const clickableCell = within(firstDataRow).getAllByRole(\"rowheader\");\n await user.click(clickableCell[0]);\n\n await waitFor(() => {\n expect(handleRowClick).toHaveBeenCalled();\n });\n });\n\n it(\"applies disabled state to specified rows\", () => {\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n selectionMode=\"multiple\"\n disabledKeys={new Set([\"2\"])}\n />\n </NimbusProvider>\n );\n\n const allRows = screen.getAllByRole(\"row\");\n const disabledRow = allRows[2]; // Second data row (index 2 because of header)\n const checkbox = within(disabledRow).getByRole(\"checkbox\");\n expect(checkbox).toBeDisabled();\n });\n});\n```\n\n### Search and Filtering Tests\n\nTest search functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Search and filtering\", () => {\n it(\"filters rows based on search term\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"Alice\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice\", { selector: \"mark\" })).toBeInTheDocument();\n expect(screen.getByText(\"Johnson\")).toBeInTheDocument();\n expect(screen.queryByText(\"Bob Smith\")).not.toBeInTheDocument();\n expect(screen.queryByText(\"Carol Williams\")).not.toBeInTheDocument();\n });\n\n it(\"shows all rows when search is empty\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.getByText(\"Bob Smith\")).toBeInTheDocument();\n expect(screen.getByText(\"Carol Williams\")).toBeInTheDocument();\n });\n\n it(\"filters across multiple columns\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"Admin\" />\n </NimbusProvider>\n );\n\n // Alice is the Admin\n expect(screen.getByText(\"Alice Johnson\")).toBeInTheDocument();\n expect(screen.queryByText(\"Bob Smith\")).not.toBeInTheDocument();\n });\n});\n```\n\n### Column Visibility Tests\n\nTest column visibility control\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Column visibility\", () => {\n it(\"shows only specified visible columns\", () => {\n render(\n <NimbusProvider>\n <DataTable\n columns={columns}\n rows={rows}\n visibleColumns={[\"name\", \"role\"]}\n />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n expect(screen.queryByText(\"Email\")).not.toBeInTheDocument();\n });\n\n it(\"shows all columns when visibleColumns is not specified\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"Name\")).toBeInTheDocument();\n expect(screen.getByText(\"Email\")).toBeInTheDocument();\n expect(screen.getByText(\"Role\")).toBeInTheDocument();\n });\n});\n```\n\n### Density Tests\n\nTest density variants\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Density\", () => {\n it(\"renders with default density\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} density=\"default\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n\n it(\"renders with condensed density\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} density=\"condensed\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n});\n```\n\n### Nested Rows Tests\n\nTest expandable nested row functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Nested rows\", () => {\n const nestedRows: DataTableRowItem[] = [\n {\n id: \"1\",\n name: \"Parent Item\",\n email: \"parent@example.com\",\n role: \"Admin\",\n children: (\n <Box\n p=\"500\"\n bg=\"neutral.2\"\n borderRadius=\"md\"\n border=\"1px solid\"\n borderColor=\"neutral.6\"\n m=\"200 0\"\n >\n Child Item 1\n </Box>\n ),\n },\n {\n id: \"2\",\n name: \"No Children\",\n email: \"single@example.com\",\n role: \"User\",\n },\n ];\n\n it(\"renders expand button for rows with children\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n expect(expandButton).toBeInTheDocument();\n });\n\n it(\"expands nested content when expand button is clicked\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n // Initially nested content should not be visible\n expect(screen.queryByText(\"Child Item 1\")).not.toBeInTheDocument();\n\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n expect(expandButton).toBeInTheDocument();\n await user.click(expandButton);\n\n await waitFor(async () => {\n await expect(screen.getByText(\"Child Item 1\")).toBeInTheDocument();\n });\n });\n\n it(\"collapses nested content when collapse button is clicked\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={nestedRows} nestedKey=\"children\" />\n </NimbusProvider>\n );\n\n // Expand first\n const expandButton = screen.getByRole(\"button\", { name: /expand/i });\n await user.click(expandButton);\n\n await waitFor(() => {\n expect(screen.getByText(\"Child Item 1\")).toBeInTheDocument();\n });\n\n // Collapse\n const collapseButton = screen.getByRole(\"button\", { name: /collapse/i });\n await user.click(collapseButton);\n\n await waitFor(() => {\n expect(screen.queryByText(\"Child Item 1\")).not.toBeInTheDocument();\n });\n });\n});\n```\n\n### Empty State Tests\n\nTest empty state rendering\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Empty state\", () => {\n it(\"renders custom empty state when no rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={[]} />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"No Data\")).toBeInTheDocument();\n });\n\n it(\"renders empty state when search matches no rows\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} search=\"nonexistent\" />\n </NimbusProvider>\n );\n\n expect(screen.getByText(\"No Data\")).toBeInTheDocument();\n });\n});\n```\n\n### Accessibility Tests\n\nTest accessibility features\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable - Accessibility\", () => {\n it(\"has accessible grid role\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} aria-label=\"Users table\" />\n </NimbusProvider>\n );\n\n const table = screen.getByRole(\"grid\");\n expect(table).toBeInTheDocument();\n });\n\n it(\"has proper row structure\", () => {\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} />\n </NimbusProvider>\n );\n\n const tableRows = screen.getAllByRole(\"row\");\n expect(tableRows.length).toBeGreaterThan(0);\n\n // Check that rows contain cells\n const firstDataRow = tableRows[1];\n const cells = within(firstDataRow).getAllByRole(\"gridcell\");\n expect(cells.length).toBeGreaterThan(0);\n });\n\n it(\"has sortable columns with aria-sort attribute\", async () => {\n const user = userEvent.setup();\n\n render(\n <NimbusProvider>\n <DataTable columns={columns} rows={rows} allowsSorting />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"Name\"));\n\n await waitFor(() => {\n const columnHeader = screen\n .getByText(\"Name\")\n .closest('[role=\"columnheader\"]');\n expect(columnHeader).toHaveAttribute(\"aria-sort\");\n });\n });\n});\n```\n\n### Manager: Basic Rendering Tests\n\nVerify the Manager button renders and opens the settings drawer\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Basic rendering\", () => {\n it(\"renders the settings button\", () => {\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /settings/i })\n ).toBeInTheDocument();\n });\n\n it(\"settings button is clickable\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n\n // Verify button can be clicked\n await user.click(settingsButton);\n\n // Button should still be in the document after click\n expect(settingsButton).toBeInTheDocument();\n });\n});\n```\n\n### Manager: Column Management Tests\n\nTest column visibility and reordering functionality\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Column management\", () => {\n it(\"displays visible and hidden columns in separate lists\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n {\n id: \"role\",\n header: \"Role\",\n accessor: (row: Record<string, unknown>) => row.role as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\", role: \"Admin\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\", \"email\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n const visibleList = screen.getByTestId(\"visible-columns-list\");\n const hiddenList = screen.getByTestId(\"hidden-columns-list\");\n\n expect(within(visibleList).getByText(\"Name\")).toBeInTheDocument();\n expect(within(visibleList).getByText(\"Email\")).toBeInTheDocument();\n expect(within(hiddenList).getByText(\"Role\")).toBeInTheDocument();\n });\n });\n\n it(\"calls onColumnsChange when column visibility changes\", async () => {\n const user = userEvent.setup();\n const handleColumnsChange = vi.fn();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n onColumnsChange={handleColumnsChange}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n // Note: Full drag-and-drop testing would require more complex setup\n // This verifies the callback is available\n expect(handleColumnsChange).toBeDefined();\n });\n});\n```\n\n### Manager: Layout Settings Tests\n\nTest text visibility and row density controls\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Layout settings\", () => {\n it(\"displays layout settings tab\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n expect(screen.getByText(/layout settings/i)).toBeInTheDocument();\n });\n });\n\n it(\"calls onSettingsChange when layout settings change\", async () => {\n const user = userEvent.setup();\n const handleSettingsChange = vi.fn();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n onSettingsChange={handleSettingsChange}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n // Note: Actual toggle interaction testing would require finding specific buttons\n // This verifies the callback is wired up\n expect(handleSettingsChange).toBeDefined();\n });\n});\n```\n\n### Manager: Custom Settings Tests\n\nTest custom settings tab integration\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen, waitFor, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { DataTable, NimbusProvider, Box } from \"@commercetools/nimbus\";\nimport type { DataTableColumnItem, DataTableRowItem } from \"./data-table.types\";\n\ndescribe(\"DataTable.Manager - Custom settings\", () => {\n it(\"displays custom settings tab when provided\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n const customSettings = {\n label: \"Filters\",\n panel: <div>Custom Filter Panel</div>,\n };\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n customSettings={customSettings}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n expect(screen.getByText(\"Filters\")).toBeInTheDocument();\n });\n });\n\n it(\"renders custom settings panel content\", async () => {\n const user = userEvent.setup();\n const managerColumns: DataTableColumnItem[] = [\n {\n id: \"name\",\n header: \"Name\",\n accessor: (row: Record<string, unknown>) => row.name as React.ReactNode,\n },\n {\n id: \"email\",\n header: \"Email\",\n accessor: (row: Record<string, unknown>) =>\n row.email as React.ReactNode,\n },\n ];\n const managerRows: DataTableRowItem[] = [\n { id: \"1\", name: \"Alice\", email: \"alice@example.com\" },\n ];\n\n const customSettings = {\n label: \"Advanced\",\n panel: <div data-testid=\"custom-panel\">Custom Panel Content</div>,\n };\n\n render(\n <NimbusProvider>\n <DataTable.Root\n columns={managerColumns}\n rows={managerRows}\n visibleColumns={[\"name\"]}\n customSettings={customSettings}\n >\n <DataTable.Manager />\n <DataTable.Table>\n <DataTable.Header />\n <DataTable.Body />\n </DataTable.Table>\n </DataTable.Root>\n </NimbusProvider>\n );\n\n const settingsButton = screen.getByRole(\"button\", { name: /settings/i });\n await user.click(settingsButton);\n\n await waitFor(() => {\n const advancedTab = screen.getByText(\"Advanced\");\n expect(advancedTab).toBeInTheDocument();\n });\n\n // Click the custom tab\n const advancedTab = screen.getByText(\"Advanced\");\n await user.click(advancedTab);\n\n await waitFor(() => {\n expect(screen.getByTestId(\"custom-panel\")).toBeInTheDocument();\n expect(screen.getByText(\"Custom Panel Content\")).toBeInTheDocument();\n });\n });\n});\n```\n\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-datatable--docs)\n- [React Aria Table](https://react-spectrum.adobe.com/react-aria/Table.html)\n- [ARIA Grid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/)\n\n**Related components**\n\n- **[Table Component](/components/data-display/table)** - Use the simpler Table component for static, read-only data without interactive features\n",
193
193
  "toc": [
194
194
  {
195
195
  "value": "Getting started",
@@ -440,13 +440,45 @@
440
440
  ],
441
441
  "parent": "root"
442
442
  },
443
+ {
444
+ "value": "Drag and drop",
445
+ "href": "#drag-and-drop",
446
+ "depth": 2,
447
+ "numbering": [
448
+ 1,
449
+ 5
450
+ ],
451
+ "parent": "root"
452
+ },
453
+ {
454
+ "value": "Reorderable rows",
455
+ "href": "#reorderable-rows",
456
+ "depth": 3,
457
+ "numbering": [
458
+ 1,
459
+ 5,
460
+ 1
461
+ ],
462
+ "parent": "root"
463
+ },
464
+ {
465
+ "value": "Namespace isolation",
466
+ "href": "#namespace-isolation",
467
+ "depth": 3,
468
+ "numbering": [
469
+ 1,
470
+ 5,
471
+ 2
472
+ ],
473
+ "parent": "root"
474
+ },
443
475
  {
444
476
  "value": "Compound component API",
445
477
  "href": "#compound-component-api",
446
478
  "depth": 2,
447
479
  "numbering": [
448
480
  1,
449
- 5
481
+ 6
450
482
  ],
451
483
  "parent": "root"
452
484
  },
@@ -456,7 +488,7 @@
456
488
  "depth": 3,
457
489
  "numbering": [
458
490
  1,
459
- 5,
491
+ 6,
460
492
  1
461
493
  ],
462
494
  "parent": "root"
@@ -467,7 +499,7 @@
467
499
  "depth": 2,
468
500
  "numbering": [
469
501
  1,
470
- 6
502
+ 7
471
503
  ],
472
504
  "parent": "root"
473
505
  },
@@ -477,7 +509,7 @@
477
509
  "depth": 3,
478
510
  "numbering": [
479
511
  1,
480
- 6,
512
+ 7,
481
513
  1
482
514
  ],
483
515
  "parent": "root"
@@ -488,7 +520,7 @@
488
520
  "depth": 3,
489
521
  "numbering": [
490
522
  1,
491
- 6,
523
+ 7,
492
524
  2
493
525
  ],
494
526
  "parent": "root"
@@ -499,7 +531,7 @@
499
531
  "depth": 3,
500
532
  "numbering": [
501
533
  1,
502
- 6,
534
+ 7,
503
535
  3
504
536
  ],
505
537
  "parent": "root"
@@ -510,7 +542,7 @@
510
542
  "depth": 3,
511
543
  "numbering": [
512
544
  1,
513
- 6,
545
+ 7,
514
546
  4
515
547
  ],
516
548
  "parent": "root"
@@ -521,7 +553,7 @@
521
553
  "depth": 3,
522
554
  "numbering": [
523
555
  1,
524
- 6,
556
+ 7,
525
557
  5
526
558
  ],
527
559
  "parent": "root"
@@ -532,7 +564,7 @@
532
564
  "depth": 3,
533
565
  "numbering": [
534
566
  1,
535
- 6,
567
+ 7,
536
568
  6
537
569
  ],
538
570
  "parent": "root"
@@ -543,7 +575,7 @@
543
575
  "depth": 2,
544
576
  "numbering": [
545
577
  1,
546
- 7
578
+ 8
547
579
  ],
548
580
  "parent": "root"
549
581
  },
@@ -553,7 +585,7 @@
553
585
  "depth": 2,
554
586
  "numbering": [
555
587
  1,
556
- 8
588
+ 9
557
589
  ],
558
590
  "parent": "root"
559
591
  },
@@ -563,7 +595,7 @@
563
595
  "depth": 4,
564
596
  "numbering": [
565
597
  1,
566
- 8,
598
+ 9,
567
599
  1,
568
600
  1
569
601
  ],
@@ -575,7 +607,7 @@
575
607
  "depth": 4,
576
608
  "numbering": [
577
609
  1,
578
- 8,
610
+ 9,
579
611
  1,
580
612
  2
581
613
  ],
@@ -587,7 +619,7 @@
587
619
  "depth": 2,
588
620
  "numbering": [
589
621
  1,
590
- 9
622
+ 10
591
623
  ],
592
624
  "parent": "root"
593
625
  },
@@ -597,7 +629,7 @@
597
629
  "depth": 3,
598
630
  "numbering": [
599
631
  1,
600
- 9,
632
+ 10,
601
633
  1
602
634
  ],
603
635
  "parent": "root"
@@ -608,7 +640,7 @@
608
640
  "depth": 3,
609
641
  "numbering": [
610
642
  1,
611
- 9,
643
+ 10,
612
644
  2
613
645
  ],
614
646
  "parent": "root"
@@ -619,7 +651,7 @@
619
651
  "depth": 3,
620
652
  "numbering": [
621
653
  1,
622
- 9,
654
+ 10,
623
655
  3
624
656
  ],
625
657
  "parent": "root"
@@ -630,7 +662,7 @@
630
662
  "depth": 2,
631
663
  "numbering": [
632
664
  1,
633
- 10
665
+ 11
634
666
  ],
635
667
  "parent": "root"
636
668
  },
@@ -640,7 +672,7 @@
640
672
  "depth": 3,
641
673
  "numbering": [
642
674
  1,
643
- 10,
675
+ 11,
644
676
  1
645
677
  ],
646
678
  "parent": "root"
@@ -651,7 +683,7 @@
651
683
  "depth": 3,
652
684
  "numbering": [
653
685
  1,
654
- 10,
686
+ 11,
655
687
  2
656
688
  ],
657
689
  "parent": "root"
@@ -662,7 +694,7 @@
662
694
  "depth": 3,
663
695
  "numbering": [
664
696
  1,
665
- 10,
697
+ 11,
666
698
  3
667
699
  ],
668
700
  "parent": "root"
@@ -673,7 +705,7 @@
673
705
  "depth": 3,
674
706
  "numbering": [
675
707
  1,
676
- 10,
708
+ 11,
677
709
  4
678
710
  ],
679
711
  "parent": "root"
@@ -684,7 +716,7 @@
684
716
  "depth": 3,
685
717
  "numbering": [
686
718
  1,
687
- 10,
719
+ 11,
688
720
  5
689
721
  ],
690
722
  "parent": "root"
@@ -695,7 +727,7 @@
695
727
  "depth": 3,
696
728
  "numbering": [
697
729
  1,
698
- 10,
730
+ 11,
699
731
  6
700
732
  ],
701
733
  "parent": "root"
@@ -706,7 +738,7 @@
706
738
  "depth": 3,
707
739
  "numbering": [
708
740
  1,
709
- 10,
741
+ 11,
710
742
  7
711
743
  ],
712
744
  "parent": "root"
@@ -717,7 +749,7 @@
717
749
  "depth": 3,
718
750
  "numbering": [
719
751
  1,
720
- 10,
752
+ 11,
721
753
  8
722
754
  ],
723
755
  "parent": "root"
@@ -728,7 +760,7 @@
728
760
  "depth": 3,
729
761
  "numbering": [
730
762
  1,
731
- 10,
763
+ 11,
732
764
  9
733
765
  ],
734
766
  "parent": "root"
@@ -739,7 +771,7 @@
739
771
  "depth": 3,
740
772
  "numbering": [
741
773
  1,
742
- 10,
774
+ 11,
743
775
  10
744
776
  ],
745
777
  "parent": "root"
@@ -750,7 +782,7 @@
750
782
  "depth": 3,
751
783
  "numbering": [
752
784
  1,
753
- 10,
785
+ 11,
754
786
  11
755
787
  ],
756
788
  "parent": "root"
@@ -761,7 +793,7 @@
761
793
  "depth": 3,
762
794
  "numbering": [
763
795
  1,
764
- 10,
796
+ 11,
765
797
  12
766
798
  ],
767
799
  "parent": "root"
@@ -772,7 +804,7 @@
772
804
  "depth": 3,
773
805
  "numbering": [
774
806
  1,
775
- 10,
807
+ 11,
776
808
  13
777
809
  ],
778
810
  "parent": "root"
@@ -783,7 +815,7 @@
783
815
  "depth": 3,
784
816
  "numbering": [
785
817
  1,
786
- 10,
818
+ 11,
787
819
  14
788
820
  ],
789
821
  "parent": "root"
@@ -794,7 +826,7 @@
794
826
  "depth": 2,
795
827
  "numbering": [
796
828
  1,
797
- 11
829
+ 12
798
830
  ],
799
831
  "parent": "root"
800
832
  }