@dxos/react-ui 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (220) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/chunk-A5QCIG5R.mjs +24 -0
  4. package/dist/lib/browser/chunk-A5QCIG5R.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-KRSEIVRM.mjs → chunk-BDBC6H6V.mjs} +74 -2
  6. package/dist/lib/browser/{chunk-KRSEIVRM.mjs.map → chunk-BDBC6H6V.mjs.map} +4 -4
  7. package/dist/lib/browser/index.mjs +653 -422
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/testing/index.mjs +20 -13
  11. package/dist/lib/browser/testing/index.mjs.map +3 -3
  12. package/dist/lib/browser/translations.mjs +9 -0
  13. package/dist/lib/browser/translations.mjs.map +7 -0
  14. package/dist/lib/node-esm/{chunk-ENYC4TYH.mjs → chunk-3JSJK2ZY.mjs} +74 -2
  15. package/dist/lib/node-esm/{chunk-ENYC4TYH.mjs.map → chunk-3JSJK2ZY.mjs.map} +4 -4
  16. package/dist/lib/node-esm/chunk-XCFLA74M.mjs +26 -0
  17. package/dist/lib/node-esm/chunk-XCFLA74M.mjs.map +7 -0
  18. package/dist/lib/node-esm/index.mjs +653 -422
  19. package/dist/lib/node-esm/index.mjs.map +4 -4
  20. package/dist/lib/node-esm/meta.json +1 -1
  21. package/dist/lib/node-esm/testing/index.mjs +20 -13
  22. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  23. package/dist/lib/node-esm/translations.mjs +10 -0
  24. package/dist/lib/node-esm/translations.mjs.map +7 -0
  25. package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
  26. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  29. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/Button/Button.stories.d.ts +1 -1
  31. package/dist/types/src/components/Button/Button.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/Button/IconButton.d.ts +1 -0
  33. package/dist/types/src/components/Button/IconButton.d.ts.map +1 -1
  34. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/Button/Toggle.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Button/ToggleGroup.d.ts +2 -2
  37. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts +2 -2
  38. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Card/Card.d.ts +23 -49
  40. package/dist/types/src/components/Card/Card.d.ts.map +1 -1
  41. package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/Carousel/Carousel.d.ts +90 -0
  43. package/dist/types/src/components/Carousel/Carousel.d.ts.map +1 -0
  44. package/dist/types/src/components/Carousel/index.d.ts +2 -0
  45. package/dist/types/src/components/Carousel/index.d.ts.map +1 -0
  46. package/dist/types/src/components/Clipboard/ClipboardProvider.d.ts.map +1 -1
  47. package/dist/types/src/components/Clipboard/CopyButton.d.ts.map +1 -1
  48. package/dist/types/src/components/Clipboard/index.d.ts +1 -1
  49. package/dist/types/src/components/Clipboard/index.d.ts.map +1 -1
  50. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts.map +1 -1
  51. package/dist/types/src/components/Dialog/AlertDialog.d.ts +21 -23
  52. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -1
  53. package/dist/types/src/components/Dialog/AlertDialog.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/Dialog/Dialog.d.ts +22 -24
  55. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -1
  56. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts.map +1 -1
  58. package/dist/types/src/components/ErrorFallback/ErrorFallback.d.ts.map +1 -1
  59. package/dist/types/src/components/ErrorFallback/ErrorFallback.stories.d.ts.map +1 -1
  60. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts +14 -3
  61. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts.map +1 -1
  62. package/dist/types/src/components/ErrorFallback/ThrowError.d.ts.map +1 -1
  63. package/dist/types/src/components/Focus/Focus.d.ts +2 -10
  64. package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
  65. package/dist/types/src/components/Focus/Focus.stories.d.ts.map +1 -1
  66. package/dist/types/src/components/Icon/Icon.d.ts +1 -0
  67. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  68. package/dist/types/src/components/Icon/Icon.stories.d.ts +1 -1
  69. package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -1
  70. package/dist/types/src/components/Image/Image.d.ts +2 -1
  71. package/dist/types/src/components/Image/Image.d.ts.map +1 -1
  72. package/dist/types/src/components/Image/Image.stories.d.ts +3 -2
  73. package/dist/types/src/components/Image/Image.stories.d.ts.map +1 -1
  74. package/dist/types/src/components/Input/Input.d.ts +12 -15
  75. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  76. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  77. package/dist/types/src/components/Link/Link.stories.d.ts.map +1 -1
  78. package/dist/types/src/components/List/List.d.ts +2 -6
  79. package/dist/types/src/components/List/List.d.ts.map +1 -1
  80. package/dist/types/src/components/List/List.stories.d.ts +2 -6
  81. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  82. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +1 -1
  83. package/dist/types/src/components/List/Tree.d.ts +2 -2
  84. package/dist/types/src/components/List/Tree.d.ts.map +1 -1
  85. package/dist/types/src/components/List/Tree.stories.d.ts.map +1 -1
  86. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +1 -1
  87. package/dist/types/src/components/List/Treegrid.d.ts +1 -5
  88. package/dist/types/src/components/List/Treegrid.d.ts.map +1 -1
  89. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +1 -1
  90. package/dist/types/src/components/Main/Main.d.ts +7 -3
  91. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  92. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  93. package/dist/types/src/components/Main/useSwipeToDismiss.d.ts.map +1 -1
  94. package/dist/types/src/components/MediaPlayer/MediaPlayer.d.ts +30 -0
  95. package/dist/types/src/components/MediaPlayer/MediaPlayer.d.ts.map +1 -0
  96. package/dist/types/src/components/MediaPlayer/MediaPlayer.stories.d.ts +15 -0
  97. package/dist/types/src/components/MediaPlayer/MediaPlayer.stories.d.ts.map +1 -0
  98. package/dist/types/src/components/MediaPlayer/index.d.ts +2 -0
  99. package/dist/types/src/components/MediaPlayer/index.d.ts.map +1 -0
  100. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -1
  101. package/dist/types/src/components/Menu/ContextMenu.stories.d.ts.map +1 -1
  102. package/dist/types/src/components/Menu/DropdownMenu.d.ts +11 -3
  103. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -1
  104. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts.map +1 -1
  105. package/dist/types/src/components/Message/Message.d.ts +1 -1
  106. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  107. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  108. package/dist/types/src/components/Popover/Popover.d.ts +10 -2
  109. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  110. package/dist/types/src/components/Popover/Popover.stories.d.ts.map +1 -1
  111. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +2 -10
  112. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  113. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +2 -10
  114. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  115. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +11 -15
  116. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -1
  117. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -1
  118. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  119. package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
  120. package/dist/types/src/components/Skeleton/Skeleton.stories.d.ts.map +1 -1
  121. package/dist/types/src/components/Splitter/Splitter.d.ts +3 -11
  122. package/dist/types/src/components/Splitter/Splitter.d.ts.map +1 -1
  123. package/dist/types/src/components/Splitter/Splitter.stories.d.ts.map +1 -1
  124. package/dist/types/src/components/Status/Status.stories.d.ts.map +1 -1
  125. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  126. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  127. package/dist/types/src/components/ThemeProvider/ThemeProvider.stories.d.ts.map +1 -1
  128. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +54 -55
  129. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  130. package/dist/types/src/components/ThemeProvider/index.d.ts +1 -1
  131. package/dist/types/src/components/ThemeProvider/index.d.ts.map +1 -1
  132. package/dist/types/src/components/Toast/Toast.d.ts +4 -4
  133. package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
  134. package/dist/types/src/components/Toast/Toast.stories.d.ts.map +1 -1
  135. package/dist/types/src/components/Toolbar/Toolbar.d.ts +5 -13
  136. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  137. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  138. package/dist/types/src/components/Tooltip/Tooltip.d.ts +2 -2
  139. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  140. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  141. package/dist/types/src/components/index.d.ts +2 -0
  142. package/dist/types/src/components/index.d.ts.map +1 -1
  143. package/dist/types/src/exemplars/generics.stories.d.ts +1 -5
  144. package/dist/types/src/exemplars/generics.stories.d.ts.map +1 -1
  145. package/dist/types/src/exemplars/slot.stories.d.ts.map +1 -1
  146. package/dist/types/src/exemplars/tabster.stories.d.ts.map +1 -1
  147. package/dist/types/src/exemplars/virtualizer.stories.d.ts.map +1 -1
  148. package/dist/types/src/hooks/useDensityContext.d.ts.map +1 -1
  149. package/dist/types/src/hooks/useElevationContext.d.ts.map +1 -1
  150. package/dist/types/src/hooks/useIconHref.d.ts.map +1 -1
  151. package/dist/types/src/hooks/useSafeArea.d.ts.map +1 -1
  152. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts.map +1 -1
  153. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  154. package/dist/types/src/playground/Controls.stories.d.ts.map +1 -1
  155. package/dist/types/src/playground/Custom.stories.d.ts +1 -1
  156. package/dist/types/src/playground/Custom.stories.d.ts.map +1 -1
  157. package/dist/types/src/playground/Typography.stories.d.ts.map +1 -1
  158. package/dist/types/src/primitives/Column/Column.d.ts +11 -29
  159. package/dist/types/src/primitives/Column/Column.d.ts.map +1 -1
  160. package/dist/types/src/primitives/Column/Column.stories.d.ts +7 -7
  161. package/dist/types/src/primitives/Column/Column.stories.d.ts.map +1 -1
  162. package/dist/types/src/primitives/Container/Container.d.ts +1 -5
  163. package/dist/types/src/primitives/Container/Container.d.ts.map +1 -1
  164. package/dist/types/src/primitives/Container/Container.stories.d.ts.map +1 -1
  165. package/dist/types/src/primitives/Flex/Flex.d.ts +1 -5
  166. package/dist/types/src/primitives/Flex/Flex.d.ts.map +1 -1
  167. package/dist/types/src/primitives/Flex/Flex.stories.d.ts.map +1 -1
  168. package/dist/types/src/primitives/Grid/Grid.d.ts +1 -5
  169. package/dist/types/src/primitives/Grid/Grid.d.ts.map +1 -1
  170. package/dist/types/src/primitives/Grid/Grid.stories.d.ts.map +1 -1
  171. package/dist/types/src/primitives/Panel/Panel.d.ts +4 -20
  172. package/dist/types/src/primitives/Panel/Panel.d.ts.map +1 -1
  173. package/dist/types/src/primitives/Panel/Panel.stories.d.ts.map +1 -1
  174. package/dist/types/src/testing/Loading.d.ts.map +1 -1
  175. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -1
  176. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -1
  177. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  178. package/dist/types/src/translations.d.ts +8 -3
  179. package/dist/types/src/translations.d.ts.map +1 -1
  180. package/dist/types/src/util/usePx.d.ts.map +1 -1
  181. package/dist/types/tsconfig.tsbuildinfo +1 -1
  182. package/package.json +27 -25
  183. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  184. package/src/components/Button/IconButton.stories.tsx +1 -1
  185. package/src/components/Button/IconButton.tsx +3 -2
  186. package/src/components/Card/Card.stories.tsx +3 -3
  187. package/src/components/Card/Card.tsx +42 -20
  188. package/src/components/Carousel/Carousel.tsx +339 -0
  189. package/src/components/Carousel/index.ts +5 -0
  190. package/src/components/Clipboard/CopyButton.tsx +2 -2
  191. package/src/components/Dialog/Dialog.stories.tsx +2 -2
  192. package/src/components/Dialog/Dialog.tsx +29 -29
  193. package/src/components/ErrorFallback/ErrorStack.tsx +36 -2
  194. package/src/components/Icon/Icon.tsx +10 -3
  195. package/src/components/Image/Image.tsx +31 -8
  196. package/src/components/Input/Input.tsx +3 -3
  197. package/src/components/List/List.stories.tsx +1 -1
  198. package/src/components/List/List.tsx +1 -1
  199. package/src/components/List/ListDropIndicator.tsx +0 -1
  200. package/src/components/List/Tree.stories.tsx +1 -1
  201. package/src/components/MediaPlayer/MediaPlayer.stories.tsx +42 -0
  202. package/src/components/MediaPlayer/MediaPlayer.tsx +97 -0
  203. package/src/components/MediaPlayer/index.ts +5 -0
  204. package/src/components/Message/Message.stories.tsx +1 -1
  205. package/src/components/Message/Message.tsx +24 -7
  206. package/src/components/ScrollArea/ScrollArea.stories.tsx +1 -5
  207. package/src/components/ScrollContainer/ScrollContainer.tsx +1 -3
  208. package/src/components/ThemeProvider/index.ts +1 -1
  209. package/src/components/Toolbar/Toolbar.tsx +4 -2
  210. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  211. package/src/components/index.ts +2 -0
  212. package/src/exemplars/slot.stories.tsx +2 -4
  213. package/src/exemplars/virtualizer.stories.tsx +0 -1
  214. package/src/playground/Custom.stories.tsx +13 -6
  215. package/src/primitives/Column/AUDIT.md +105 -311
  216. package/src/primitives/Column/Column.stories.tsx +58 -59
  217. package/src/primitives/Column/Column.tsx +54 -58
  218. package/src/testing/Loading.tsx +24 -4
  219. package/src/testing/decorators/withLayout.tsx +7 -17
  220. package/src/translations.ts +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui",
3
- "version": "0.8.4-main.74a063c4e0",
3
+ "version": "0.8.4-main.765dc60934",
4
4
  "description": "Low-level React components for DXOS, applying a theme to a core group of primitives",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,10 +8,13 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": false,
14
14
  "type": "module",
15
+ "imports": {
16
+ "#translations": "./src/translations.ts"
17
+ },
15
18
  "exports": {
16
19
  ".": {
17
20
  "source": "./src/index.ts",
@@ -24,16 +27,15 @@
24
27
  "browser": "./dist/lib/browser/testing/index.mjs",
25
28
  "node": "./dist/lib/node-esm/testing/index.mjs",
26
29
  "types": "./dist/types/src/testing/index.d.ts"
30
+ },
31
+ "./translations": {
32
+ "source": "./src/translations.ts",
33
+ "types": "./dist/types/src/translations.d.ts",
34
+ "browser": "./dist/lib/browser/translations.mjs",
35
+ "node": "./dist/lib/node-esm/translations.mjs"
27
36
  }
28
37
  },
29
38
  "types": "dist/types/src/index.d.ts",
30
- "typesVersions": {
31
- "*": {
32
- "testing": [
33
- "./dist/types/src/testing/index.d.ts"
34
- ]
35
- }
36
- },
37
39
  "files": [
38
40
  "dist",
39
41
  "src"
@@ -81,17 +83,17 @@
81
83
  "react-error-boundary": "^4.0.13",
82
84
  "react-i18next": "^11.18.6",
83
85
  "react-remove-scroll": "^2.6.0",
84
- "@dxos/async": "0.8.4-main.74a063c4e0",
85
- "@dxos/debug": "0.8.4-main.74a063c4e0",
86
- "@dxos/invariant": "0.8.4-main.74a063c4e0",
87
- "@dxos/lit-ui": "0.8.4-main.74a063c4e0",
88
- "@dxos/log": "0.8.4-main.74a063c4e0",
89
- "@dxos/react-hooks": "0.8.4-main.74a063c4e0",
90
- "@dxos/react-error-boundary": "0.8.4-main.74a063c4e0",
91
- "@dxos/react-input": "0.8.4-main.74a063c4e0",
92
- "@dxos/react-list": "0.8.4-main.74a063c4e0",
93
- "@dxos/ui-types": "0.8.4-main.74a063c4e0",
94
- "@dxos/util": "0.8.4-main.74a063c4e0"
86
+ "@dxos/lit-ui": "0.8.4-main.765dc60934",
87
+ "@dxos/async": "0.8.4-main.765dc60934",
88
+ "@dxos/invariant": "0.8.4-main.765dc60934",
89
+ "@dxos/log": "0.8.4-main.765dc60934",
90
+ "@dxos/react-error-boundary": "0.8.4-main.765dc60934",
91
+ "@dxos/react-hooks": "0.8.4-main.765dc60934",
92
+ "@dxos/react-list": "0.8.4-main.765dc60934",
93
+ "@dxos/debug": "0.8.4-main.765dc60934",
94
+ "@dxos/ui-types": "0.8.4-main.765dc60934",
95
+ "@dxos/react-input": "0.8.4-main.765dc60934",
96
+ "@dxos/util": "0.8.4-main.765dc60934"
95
97
  },
96
98
  "devDependencies": {
97
99
  "@dnd-kit/core": "^6.0.5",
@@ -104,15 +106,15 @@
104
106
  "react": "~19.2.3",
105
107
  "react-dom": "~19.2.3",
106
108
  "tabster": "^8.5.5",
107
- "vite": "^7.1.11",
108
- "@dxos/random": "0.8.4-main.74a063c4e0",
109
- "@dxos/ui-theme": "0.8.4-main.74a063c4e0",
110
- "@dxos/util": "0.8.4-main.74a063c4e0"
109
+ "vite": "^8.0.13",
110
+ "@dxos/util": "0.8.4-main.765dc60934",
111
+ "@dxos/random": "0.8.4-main.765dc60934",
112
+ "@dxos/ui-theme": "0.8.4-main.765dc60934"
111
113
  },
112
114
  "peerDependencies": {
113
115
  "react": "~19.2.3",
114
116
  "react-dom": "~19.2.3",
115
- "@dxos/ui-theme": "0.8.4-main.74a063c4e0"
117
+ "@dxos/ui-theme": "0.8.4-main.765dc60934"
116
118
  },
117
119
  "publishConfig": {
118
120
  "access": "public"
@@ -15,7 +15,7 @@ const DefaultStory = (props: BreadcrumbRootProps) => {
15
15
  <Breadcrumb.List>
16
16
  <Breadcrumb.ListItem>
17
17
  <Breadcrumb.Link asChild>
18
- <Button variant='ghost' classNames='px-0 text-base-surface-text font-normal'>
18
+ <Button variant='ghost' classNames='px-0 text-base-foreground font-normal'>
19
19
  Grocery
20
20
  </Button>
21
21
  </Breadcrumb.Link>
@@ -13,7 +13,7 @@ import { IconButton, type IconButtonProps } from './IconButton';
13
13
  const DefaultStory = (props: IconButtonProps) => {
14
14
  return (
15
15
  <Tooltip.Provider>
16
- <div role='none' className='flex gap-4'>
16
+ <div className='flex gap-4'>
17
17
  <IconButton {...props} />
18
18
  <IconButton iconOnly {...props} />
19
19
  <Button>{props.label}</Button>
@@ -16,6 +16,7 @@ type IconButtonProps = Omit<ButtonProps, 'children'> &
16
16
  noTooltip?: boolean;
17
17
  caretDown?: boolean;
18
18
  iconOnly?: boolean;
19
+ square?: boolean; // TODO(burdon): Handle more uniformly.
19
20
  iconEnd?: boolean;
20
21
  iconClassNames?: ThemedClassName<any>['classNames'];
21
22
  tooltipSide?: TooltipSide;
@@ -45,12 +46,12 @@ const IconOnlyButton = forwardRef<HTMLButtonElement, IconButtonProps>(
45
46
 
46
47
  const LabelledIconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
47
48
  (
48
- { size, icon, iconOnly, iconEnd, iconClassNames, label, caretDown, noTooltip: _, classNames, ...props },
49
+ { size, icon, iconOnly, square, iconEnd, iconClassNames, label, caretDown, noTooltip: _, classNames, ...props },
49
50
  forwardedRef,
50
51
  ) => {
51
52
  const { tx } = useThemeContext();
52
53
  return (
53
- <Button {...props} classNames={tx('iconButton.root', { iconOnly }, classNames)} ref={forwardedRef}>
54
+ <Button {...props} classNames={tx('iconButton.root', { iconOnly, square }, classNames)} ref={forwardedRef}>
54
55
  {icon && !iconEnd && <Icon icon={icon} size={size} classNames={iconClassNames} />}
55
56
  <span className={iconOnly ? 'sr-only' : undefined}>{label}</span>
56
57
  {icon && iconEnd && <Icon icon={icon} size={size} classNames={iconClassNames} />}
@@ -129,18 +129,18 @@ export const Description: Story = {
129
129
  export const Mock = () => (
130
130
  <div className='grid grid-cols-[2rem_1fr_2rem] w-full dx-card-min-width dx-card-max-width border border-separator rounded-xs'>
131
131
  <div className='grid grid-cols-subgrid col-span-full'>
132
- <div role='none' className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
132
+ <div className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
133
133
  <Icon icon='ph--dots-six-vertical--regular' />
134
134
  </div>
135
135
  <div className='p-1 whitespace-normal break-words text-description items-center'>
136
136
  This line is very very long and it should wrap.
137
137
  </div>
138
- <div role='none' className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
138
+ <div className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
139
139
  <Icon icon='ph--x--regular' />
140
140
  </div>
141
141
  </div>
142
142
  <div className='grid grid-cols-subgrid col-span-3'>
143
- <div role='none' className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
143
+ <div className='grid h-[var(--dx-rail-item)] w-[var(--dx-rail-item)] place-items-center'>
144
144
  <Icon icon='ph--dots-six-vertical--regular' />
145
145
  </div>
146
146
  <div className='p-1 text-description items-center col-span-2'>
@@ -52,7 +52,7 @@ const CardContext = createContext<CardContextValue>({});
52
52
 
53
53
  const CARD_ROOT_NAME = 'Card.Root';
54
54
 
55
- type CardRootOwnProps = {
55
+ type CardRootProps = {
56
56
  id?: string;
57
57
  border?: boolean;
58
58
  fullWidth?: boolean;
@@ -64,24 +64,33 @@ type CardRootOwnProps = {
64
64
  'data-testid'?: string;
65
65
  };
66
66
 
67
- type CardRootProps = CardRootOwnProps;
68
-
69
- const CardRoot = slottable<HTMLDivElement, CardRootOwnProps>(
70
- ({ children, id, asChild, role, border = true, fullWidth, density, ...props }, forwardedRef) => {
67
+ /**
68
+ * `Card.Root` does not support `asChild`. The Column grid is the root element
69
+ * (one `<div>` carrying both the `dx-card` and `dx-column-root` classes
70
+ * instead of the previous outer-card + inner-column pair), so caller-provided
71
+ * HTML attributes — `onClick`, `tabIndex`, `style`, `data-*`, `grid-template-rows`
72
+ * overrides via `classNames` — land directly on the grid container.
73
+ * Slot-parents (`Focus.Item asChild`, `Mosaic.Tile asChild`, etc.) continue to
74
+ * work because `composable()` preserves the COMPOSABLE marker that slottable parents
75
+ * check before warning, and Radix `Slot` merges the parent's props onto the inner
76
+ * `<div>` exactly the way `slottable`'s `Slot`/`Primitive.div` branch did.
77
+ */
78
+ const CardRoot = composable<HTMLDivElement, CardRootProps>(
79
+ ({ children, id, role, border = true, fullWidth, density, ...props }, forwardedRef) => {
71
80
  const { className, ...rest } = composableProps(props);
72
- const Comp = asChild ? Slot : Primitive.div;
73
81
  const { tx } = useThemeContext();
74
82
 
75
83
  return (
76
- <Comp
77
- {...rest}
78
- {...(id && { 'data-object-id': id })}
84
+ <Column.Root
85
+ asChild
86
+ gutter={density === 'coarse' ? 'lg' : 'md'}
87
+ classNames={tx('card.root', { border, fullWidth }, className)}
79
88
  role={role ?? 'group'}
80
- className={tx('card.root', { border, fullWidth }, className)}
81
- ref={forwardedRef}
82
89
  >
83
- <Column.Root gutter={density === 'coarse' ? 'lg' : 'md'}>{children}</Column.Root>
84
- </Comp>
90
+ <div {...rest} {...(id && { 'data-object-id': id })} ref={forwardedRef}>
91
+ {children}
92
+ </div>
93
+ </Column.Root>
85
94
  );
86
95
  },
87
96
  );
@@ -194,7 +203,7 @@ const CardIconBlock = forwardRef<HTMLDivElement, ThemedClassName<PropsWithChildr
194
203
  const { tx } = useThemeContext();
195
204
 
196
205
  return (
197
- <div {...props} role='none' className={tx('card.icon-block', { padding }, classNames)} ref={forwardedRef}>
206
+ <div {...props} className={tx('card.icon-block', { padding }, classNames)} ref={forwardedRef}>
198
207
  {children}
199
208
  </div>
200
209
  );
@@ -251,15 +260,17 @@ const CARD_ROW_NAME = 'Card.Row';
251
260
 
252
261
  type CardRowProps = { icon?: string; fullWidth?: boolean };
253
262
 
254
- const CardRow = composable<HTMLDivElement, CardRowProps>(({ children, icon, ...props }, forwardedRef) => {
263
+ // TODO(burdon): fullWidth should mean no columns.
264
+ const CardRow = slottable<HTMLDivElement, CardRowProps>(({ children, asChild, icon, ...props }, forwardedRef) => {
255
265
  const { className, ...rest } = composableProps(props);
266
+ const Comp = asChild ? Slot : Primitive.div;
256
267
  const { tx } = useThemeContext();
257
268
 
258
269
  return (
259
- <Column.Row {...rest} className={tx('card.row', {}, className)} ref={forwardedRef}>
270
+ <Comp {...rest} className={tx('card.row', {}, className)} ref={forwardedRef}>
260
271
  {(icon && <CardIcon classNames='text-subdued' icon={icon} size={4} />) || <div />}
261
272
  {children}
262
- </Column.Row>
273
+ </Comp>
263
274
  );
264
275
  });
265
276
 
@@ -362,7 +373,6 @@ const CardHtml = ({ html, variant = 'default', ...props }: CardHtmlProps & Theme
362
373
  return (
363
374
  <div
364
375
  {...props}
365
- role='none'
366
376
  className={tx('card.text', { variant })}
367
377
  // eslint-disable-next-line react/no-danger
368
378
  dangerouslySetInnerHTML={{ __html: sanitized }}
@@ -382,6 +392,13 @@ type CardPosterProps = ThemedClassName<
382
392
  {
383
393
  alt: string;
384
394
  aspect?: 'video' | 'auto';
395
+ /**
396
+ * How the image fills the poster box. `'contain'` (default) preserves
397
+ * aspect ratio and may letterbox; `'cover'` fills the box edge-to-edge,
398
+ * cropping as needed. Forwarded to the underlying `Image`'s
399
+ * `object-fit`.
400
+ */
401
+ fit?: 'contain' | 'cover';
385
402
  } & Partial<{ image: string; icon: string }>
386
403
  >;
387
404
 
@@ -391,8 +408,13 @@ const CardPoster = (props: CardPosterProps) => {
391
408
 
392
409
  if (props.image) {
393
410
  return (
394
- <div role='none' className='col-span-full mb-1'>
395
- <Image classNames={[tx('card.poster', {}), aspect, props.classNames]} src={props.image} alt={props.alt} />
411
+ <div className='col-span-full'>
412
+ <Image
413
+ classNames={[tx('card.poster', {}), aspect, props.classNames]}
414
+ src={props.image}
415
+ alt={props.alt}
416
+ fit={props.fit}
417
+ />
396
418
  </div>
397
419
  );
398
420
  }
@@ -0,0 +1,339 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
+ import React, {
7
+ createContext,
8
+ type KeyboardEvent,
9
+ type PropsWithChildren,
10
+ type ReactNode,
11
+ useCallback,
12
+ useContext,
13
+ useEffect,
14
+ useMemo,
15
+ useState,
16
+ } from 'react';
17
+
18
+ import { mx } from '@dxos/ui-theme';
19
+
20
+ import { translationKey } from '../../translations';
21
+ import { type ThemedClassName } from '../../util';
22
+ import { IconButton } from '../Button';
23
+ import { useTranslation } from '../ThemeProvider';
24
+
25
+ // TODO(burdon): Move per-element class strings to `@dxos/ui-theme` (theme tokens)
26
+ // so callers can re-theme via the same mechanism the rest of `react-ui` uses.
27
+
28
+ //
29
+ // Context
30
+ //
31
+
32
+ type CarouselContextValue = {
33
+ index: number;
34
+ count: number;
35
+ setIndex: (index: number) => void;
36
+ next: () => void;
37
+ prev: () => void;
38
+ };
39
+
40
+ const CarouselContext = createContext<CarouselContextValue | null>(null);
41
+
42
+ /** Returns the current carousel state. Must be used within {@link Carousel.Root}. */
43
+ export const useCarousel = (): CarouselContextValue => {
44
+ const context = useContext(CarouselContext);
45
+ if (!context) {
46
+ throw new Error('useCarousel must be used within Carousel.Root');
47
+ }
48
+ return context;
49
+ };
50
+
51
+ //
52
+ // Root
53
+ //
54
+
55
+ export type CarouselRootProps = ThemedClassName<
56
+ PropsWithChildren<{
57
+ /** Total number of slides; drives auto-advance and indicator counts. */
58
+ count: number;
59
+ /** Whether to auto-advance slides on mount. Defaults to `false`. */
60
+ autorun?: boolean;
61
+ /** Auto-advance interval in milliseconds. Set 0 to disable. */
62
+ intervalMs?: number;
63
+ defaultIndex?: number;
64
+ }>
65
+ >;
66
+
67
+ const CarouselRoot = ({
68
+ classNames,
69
+ children,
70
+ count,
71
+ autorun = false,
72
+ intervalMs = 5_000,
73
+ defaultIndex = 0,
74
+ }: CarouselRootProps) => {
75
+ const [index, setIndexState] = useState(defaultIndex);
76
+ const [autoAdvance, setAutoAdvance] = useState(autorun);
77
+
78
+ // Reset to first slide if the slide count shrinks below the current index.
79
+ useEffect(() => {
80
+ if (index >= count) {
81
+ setIndexState(0);
82
+ }
83
+ }, [count, index]);
84
+
85
+ // Auto-advance — stops permanently once the user interacts with any control.
86
+ useEffect(() => {
87
+ if (!autoAdvance || count <= 1 || intervalMs <= 0) {
88
+ return;
89
+ }
90
+ const handle = setInterval(() => setIndexState((i) => (i + 1) % count), intervalMs);
91
+ return () => clearInterval(handle);
92
+ }, [autoAdvance, count, intervalMs]);
93
+
94
+ const setIndex = useCallback((next: number) => {
95
+ setAutoAdvance(false);
96
+ setIndexState(next);
97
+ }, []);
98
+ const next = useCallback(() => {
99
+ setAutoAdvance(false);
100
+ setIndexState((i) => (i + 1) % count);
101
+ }, [count]);
102
+ const prev = useCallback(() => {
103
+ setAutoAdvance(false);
104
+ setIndexState((i) => (i - 1 + count) % count);
105
+ }, [count]);
106
+
107
+ const value = useMemo(() => ({ index, count, setIndex, next, prev }), [index, count, setIndex, next, prev]);
108
+
109
+ if (count === 0) {
110
+ return null;
111
+ }
112
+
113
+ return (
114
+ <CarouselContext.Provider value={value}>
115
+ {/*
116
+ * Rows are `[1fr, auto]`: row 1 (Previous|Viewport|Next) stretches when the parent
117
+ * gives the carousel a definite height, and row 2 (Indicators / Caption) sticks to
118
+ * its content height. With no parent height constraint, the `1fr` row simply tracks
119
+ * row-1 content — preserving the existing aspect-video behaviour for unbounded use.
120
+ */}
121
+ {/* TODO(burdon): Move to ui-theme. */}
122
+ <div
123
+ className={mx(
124
+ 'w-full grid grid-cols-[min-content_1fr_min-content] grid-rows-[minmax(0,1fr)_auto] gap-4 items-center',
125
+ classNames,
126
+ )}
127
+ >
128
+ {children}
129
+ </div>
130
+ </CarouselContext.Provider>
131
+ );
132
+ };
133
+
134
+ CarouselRoot.displayName = 'Carousel.Root';
135
+
136
+ //
137
+ // Viewport
138
+ //
139
+
140
+ export type CarouselViewportProps = ThemedClassName<PropsWithChildren<{}>>;
141
+
142
+ const CarouselViewport = ({ children, classNames }: CarouselViewportProps) => {
143
+ const { t } = useTranslation(translationKey);
144
+ const { count, next, prev } = useCarousel();
145
+ const handleKeyDown = useCallback(
146
+ (event: KeyboardEvent<HTMLDivElement>) => {
147
+ if (count <= 1) {
148
+ return;
149
+ }
150
+ if (event.key === 'ArrowLeft') {
151
+ event.preventDefault();
152
+ prev();
153
+ } else if (event.key === 'ArrowRight') {
154
+ event.preventDefault();
155
+ next();
156
+ }
157
+ },
158
+ [count, next, prev],
159
+ );
160
+
161
+ return (
162
+ <div
163
+ // TODO(burdon): Move to ui-theme.
164
+ className={mx(
165
+ 'relative w-full aspect-video overflow-hidden',
166
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
167
+ classNames,
168
+ )}
169
+ tabIndex={0}
170
+ role='region'
171
+ aria-roledescription='carousel'
172
+ aria-label={t('carousel-viewport.label')}
173
+ onKeyDown={handleKeyDown}
174
+ >
175
+ {children}
176
+ </div>
177
+ );
178
+ };
179
+
180
+ CarouselViewport.displayName = 'Carousel.Viewport';
181
+
182
+ //
183
+ // Slide
184
+ //
185
+
186
+ export type CarouselSlideProps = ThemedClassName<
187
+ PropsWithChildren<{
188
+ index: number;
189
+ }>
190
+ >;
191
+
192
+ const CarouselSlide = ({ children, index, classNames }: CarouselSlideProps) => {
193
+ const { index: active } = useCarousel();
194
+ if (active !== index) {
195
+ return null;
196
+ }
197
+
198
+ // TODO(burdon): Move to ui-theme.
199
+ return <div className={mx('absolute inset-0 w-full h-full bg-baseSurface', classNames)}>{children}</div>;
200
+ };
201
+
202
+ CarouselSlide.displayName = 'Carousel.Slide';
203
+
204
+ //
205
+ // Previous / Next
206
+ //
207
+
208
+ export type CarouselButtonProps = ThemedClassName<{}>;
209
+
210
+ const CarouselPrevious = ({ classNames }: CarouselButtonProps) => {
211
+ const { t } = useTranslation(translationKey);
212
+ const { count, prev } = useCarousel();
213
+ if (count <= 1) {
214
+ return <div />;
215
+ }
216
+
217
+ return (
218
+ <IconButton
219
+ classNames={classNames}
220
+ square
221
+ variant='ghost'
222
+ icon='ph--caret-left--regular'
223
+ iconOnly
224
+ label={t('carousel-prev.label')}
225
+ onClick={prev}
226
+ />
227
+ );
228
+ };
229
+
230
+ CarouselPrevious.displayName = 'Carousel.Previous';
231
+
232
+ const CarouselNext = ({ classNames }: CarouselButtonProps) => {
233
+ const { t } = useTranslation(translationKey);
234
+ const { count, next } = useCarousel();
235
+ if (count <= 1) {
236
+ return <div />;
237
+ }
238
+
239
+ return (
240
+ <IconButton
241
+ classNames={classNames}
242
+ square
243
+ variant='ghost'
244
+ icon='ph--caret-right--regular'
245
+ iconOnly
246
+ label={t('carousel-next.label')}
247
+ onClick={next}
248
+ />
249
+ );
250
+ };
251
+
252
+ CarouselNext.displayName = 'Carousel.Next';
253
+
254
+ //
255
+ // Indicators
256
+ //
257
+
258
+ export type CarouselIndicatorsProps = ThemedClassName<{}>;
259
+
260
+ /** Tab-strip of slide indicators. Sits in the centre column so it matches the viewport's width. */
261
+ const CarouselIndicators = ({ classNames }: CarouselIndicatorsProps) => {
262
+ const { t } = useTranslation(translationKey);
263
+ const { count, index, setIndex } = useCarousel();
264
+ const arrowNavigationAttrs = useArrowNavigationGroup({ axis: 'horizontal', memorizeCurrent: true });
265
+ if (count <= 1) {
266
+ return null;
267
+ }
268
+
269
+ return (
270
+ // TODO(burdon): Move to ui-theme.
271
+ <div className='col-start-2'>
272
+ <div
273
+ {...arrowNavigationAttrs}
274
+ className={mx('flex items-center justify-center', classNames)}
275
+ role='tablist'
276
+ aria-label={t('carousel-indicators.label')}
277
+ >
278
+ {Array.from({ length: count }).map((_, i) => (
279
+ <IconButton
280
+ key={i}
281
+ role='tab'
282
+ aria-selected={i === index}
283
+ // TODO(burdon): Move to ui-theme.
284
+ classNames={i === index ? 'text-primary-500' : 'text-description'}
285
+ icon={i === index ? 'ph--circle--fill' : 'ph--circle--regular'}
286
+ iconOnly
287
+ label={t('carousel-go-to.label', { index: i + 1 })}
288
+ onClick={() => setIndex(i)}
289
+ onFocus={() => setIndex(i)}
290
+ size={3}
291
+ variant='ghost'
292
+ />
293
+ ))}
294
+ </div>
295
+ </div>
296
+ );
297
+ };
298
+
299
+ CarouselIndicators.displayName = 'Carousel.Indicators';
300
+
301
+ //
302
+ // Caption
303
+ //
304
+
305
+ export type CarouselCaptionProps = ThemedClassName<{
306
+ /** Render prop receiving the active slide index. */
307
+ children: (index: number) => ReactNode;
308
+ }>;
309
+
310
+ /** Caption sized to the viewport's column. */
311
+ const CarouselCaption = ({ children, classNames }: CarouselCaptionProps) => {
312
+ const { index } = useCarousel();
313
+ const content = children(index);
314
+ if (content == null || content === false || content === '') {
315
+ return null;
316
+ }
317
+ return (
318
+ // TODO(burdon): Move to ui-theme.
319
+ <div className='col-start-2'>
320
+ <p className={mx('text-center text-description', classNames)}>{content}</p>
321
+ </div>
322
+ );
323
+ };
324
+
325
+ CarouselCaption.displayName = 'Carousel.Caption';
326
+
327
+ //
328
+ // Carousel
329
+ //
330
+
331
+ export const Carousel = {
332
+ Root: CarouselRoot,
333
+ Viewport: CarouselViewport,
334
+ Slide: CarouselSlide,
335
+ Previous: CarouselPrevious,
336
+ Next: CarouselNext,
337
+ Indicators: CarouselIndicators,
338
+ Caption: CarouselCaption,
339
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './Carousel';
@@ -30,11 +30,11 @@ export const CopyButton = ({ classNames, value, size = 5, ...props }: CopyButton
30
30
  onClick={() => setTextValue(value)}
31
31
  data-testid='copy-invitation'
32
32
  >
33
- <div role='none' className={mx('flex gap-1 items-center', isCopied && inactiveLabelStyles)}>
33
+ <div className={mx('flex gap-1 items-center', isCopied && inactiveLabelStyles)}>
34
34
  <span className='px-1'>{t('copy.label')}</span>
35
35
  <Icon icon='ph--copy--regular' size={size} />
36
36
  </div>
37
- <div role='none' className={mx('flex gap-1 items-center', !isCopied && inactiveLabelStyles)}>
37
+ <div className={mx('flex gap-1 items-center', !isCopied && inactiveLabelStyles)}>
38
38
  <span className='px-1'>{t('copy-success.label')}</span>
39
39
  <Icon icon='ph--check--regular' size={size} />
40
40
  </div>
@@ -24,7 +24,7 @@ type DefaultStoryProps = Pick<DialogContentProps, 'size'> &
24
24
 
25
25
  /**
26
26
  * Standard Dialog with non-scrolling content in Dialog.Body.
27
- * Dialog.Body delegates to Column.Content, which applies gutter padding via `px-[var(--gutter)]`.
27
+ * Dialog.Body propagates the Column grid via subgrid. Children auto-center via --dx-col.
28
28
  */
29
29
  const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: DefaultStoryProps) => {
30
30
  return (
@@ -62,7 +62,7 @@ const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blo
62
62
 
63
63
  /**
64
64
  * Dialog with a ScrollArea child inside Dialog.Body.
65
- * The ScrollArea breaks out of Body's gutter padding via `--gutter-offset`
65
+ * The ScrollArea breaks out of Body's gutter padding via `--gutter`
66
66
  * and applies its own asymmetric padding (accounting for scrollbar width).
67
67
  */
68
68
  const ScrollingStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: DefaultStoryProps) => {