@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
@@ -4,17 +4,17 @@
4
4
 
5
5
  import { createContext } from '@radix-ui/react-context';
6
6
  import * as DialogPrimitive from '@radix-ui/react-dialog';
7
+ import { Primitive } from '@radix-ui/react-primitive';
8
+ import { Slot } from '@radix-ui/react-slot';
7
9
  import React, {
8
10
  type ComponentPropsWithRef,
9
11
  type ForwardRefExoticComponent,
10
12
  type FunctionComponent,
11
- type PropsWithChildren,
12
13
  forwardRef,
13
14
  } from 'react';
14
15
  import { useTranslation } from 'react-i18next';
15
16
 
16
- import { type DialogSize, osTranslations } from '@dxos/ui-theme';
17
- import { slottable } from '@dxos/ui-theme';
17
+ import { composableProps, type DialogSize, osTranslations, slottable } from '@dxos/ui-theme';
18
18
  import { type SlottableProps } from '@dxos/ui-types';
19
19
 
20
20
  import { useThemeContext } from '../../hooks';
@@ -138,18 +138,18 @@ DialogContent.displayName = DIALOG_CONTENT_NAME;
138
138
  // Header
139
139
  //
140
140
 
141
- type DialogHeaderProps = PropsWithChildren;
141
+ type DialogHeaderProps = SlottableProps;
142
142
 
143
- const DialogHeader: ForwardRefExoticComponent<DialogHeaderProps> = forwardRef<HTMLHeadingElement, DialogHeaderProps>(
144
- ({ children }, forwardedRef) => {
145
- const { tx } = useThemeContext();
146
- return (
147
- <Column.Row className={tx('dialog.header')} center ref={forwardedRef}>
148
- {children}
149
- </Column.Row>
150
- );
151
- },
152
- );
143
+ const DialogHeader = slottable<HTMLDivElement>(({ children, asChild, ...props }, forwardedRef) => {
144
+ const { className, ...rest } = composableProps(props);
145
+ const Comp = asChild ? Slot : Primitive.div;
146
+ const { tx } = useThemeContext();
147
+ return (
148
+ <Comp {...rest} className={tx('dialog.header', {}, className)} ref={forwardedRef}>
149
+ {children}
150
+ </Comp>
151
+ );
152
+ });
153
153
 
154
154
  //
155
155
  // CloseIconButton
@@ -181,11 +181,13 @@ const DialogCloseIconButton = forwardRef<HTMLButtonElement, DialogCloseIconButto
181
181
  type DialogBodyProps = SlottableProps;
182
182
 
183
183
  const DialogBody = slottable<HTMLDivElement>(({ children, asChild, ...props }, forwardedRef) => {
184
+ const { className, ...rest } = composableProps(props);
185
+ const Comp = asChild ? Slot : Primitive.div;
184
186
  const { tx } = useThemeContext();
185
187
  return (
186
- <Column.Content {...props} asChild={asChild} className={tx('dialog.body', {})} ref={forwardedRef}>
188
+ <Comp {...rest} className={tx('dialog.body', {}, className)} ref={forwardedRef}>
187
189
  {children}
188
- </Column.Content>
190
+ </Comp>
189
191
  );
190
192
  });
191
193
 
@@ -227,20 +229,18 @@ const DialogDescription = forwardRef<HTMLParagraphElement, DialogDescriptionProp
227
229
  // ActionBar
228
230
  //
229
231
 
230
- type DialogActionBarProps = ThemedClassName<PropsWithChildren>;
232
+ type DialogActionBarProps = SlottableProps;
231
233
 
232
- const DialogActionBar = forwardRef<HTMLDivElement, DialogActionBarProps>(
233
- ({ children, classNames, ...props }, forwardedRef) => {
234
- const { tx } = useThemeContext();
235
- return (
236
- <Column.Row center>
237
- <div {...props} className={tx('dialog.actionbar', {}, classNames)} ref={forwardedRef}>
238
- {children}
239
- </div>
240
- </Column.Row>
241
- );
242
- },
243
- );
234
+ const DialogActionBar = slottable<HTMLDivElement>(({ children, asChild, ...props }, forwardedRef) => {
235
+ const { className: classNames, ...rest } = composableProps(props);
236
+ const Comp = asChild ? Slot : Primitive.div;
237
+ const { tx } = useThemeContext();
238
+ return (
239
+ <Comp {...rest} className={tx('dialog.actionbar', {}, classNames)} ref={forwardedRef}>
240
+ {children}
241
+ </Comp>
242
+ );
243
+ });
244
244
 
245
245
  //
246
246
  // Close
@@ -9,11 +9,45 @@ import { mx } from '@dxos/ui-theme';
9
9
 
10
10
  type LocalFrame = { href: string; fileName: string };
11
11
 
12
+ export type ParsedStackFrame = ReturnType<typeof ErrorStackParser.parse>[number];
13
+
14
+ export type ErrorStackProps = {
15
+ /** When set, these frames are shown instead of parsing `error`. */
16
+ frames?: ParsedStackFrame[];
17
+ /** Used when `frames` is omitted. */
18
+ error?: Error;
19
+ };
20
+
21
+ /**
22
+ * Parses `captureOwnerStack()` output (React dev) into frames for {@link ErrorStack}.
23
+ * Prefixes a synthetic Error line when needed so `error-stack-parser` can read V8-style stacks.
24
+ */
25
+ export const parseCaptureOwnerStack = (stack: string | null): ParsedStackFrame[] | null => {
26
+ if (stack == null || stack.length === 0) {
27
+ return null;
28
+ }
29
+ const err = new Error();
30
+ err.stack = stack;
31
+ try {
32
+ return ErrorStackParser.parse(err);
33
+ } catch {
34
+ err.stack = `Error\n${stack}`;
35
+ try {
36
+ return ErrorStackParser.parse(err);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+ };
42
+
12
43
  /**
13
44
  * Renders a parsed error stack trace with tree connector symbols and clickable vscode:// links for local frames.
14
45
  */
15
- export const ErrorStack = ({ error }: { error: Error }) => {
16
- const frames = ErrorStackParser.parse(error);
46
+ export const ErrorStack = ({ error, frames: framesProp }: ErrorStackProps) => {
47
+ const frames = framesProp ?? (error ? ErrorStackParser.parse(error) : []);
48
+ if (frames.length === 0) {
49
+ return null;
50
+ }
17
51
 
18
52
  return (
19
53
  <div className='font-mono text-sm'>
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { type Primitive } from '@radix-ui/react-primitive';
6
- import React, { type ComponentPropsWithRef, forwardRef, memo } from 'react';
6
+ import React, { type ComponentPropsWithRef, forwardRef, memo, useMemo } from 'react';
7
7
 
8
8
  import { type Size } from '@dxos/ui-types';
9
9
 
@@ -13,18 +13,25 @@ import { type ThemedClassName } from '../../util';
13
13
  export type IconProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.svg>> & {
14
14
  icon: string;
15
15
  size?: Size;
16
+ synchronized?: boolean;
16
17
  };
17
18
 
18
19
  /**
19
20
  * The Icon's size can be set directly or inherited from the `--dx-icon-size` CSS variable.
20
21
  */
21
22
  export const Icon = memo(
22
- forwardRef<SVGSVGElement, IconProps>(({ icon, classNames, size, ...props }, forwardedRef) => {
23
+ forwardRef<SVGSVGElement, IconProps>(({ classNames, icon, size, synchronized, style, ...props }, forwardedRef) => {
23
24
  const { tx } = useThemeContext();
25
+ const spinDelay = useMemo(() => (synchronized ? `${-(Date.now() % 1_000)}ms` : undefined), [synchronized]);
24
26
  const href = useIconHref(icon);
25
27
 
26
28
  return (
27
- <svg {...props} className={tx('icon.root', { size }, classNames)} ref={forwardedRef}>
29
+ <svg
30
+ {...props}
31
+ style={{ ...style, animationDelay: spinDelay }}
32
+ className={tx('icon.root', { size }, classNames)}
33
+ ref={forwardedRef}
34
+ >
28
35
  <use href={href} />
29
36
  </svg>
30
37
  );
@@ -13,15 +13,16 @@ export type ImageProps = ThemedClassName<
13
13
  {
14
14
  src: string;
15
15
  alt?: string;
16
+ fit?: 'contain' | 'cover';
16
17
  crossOrigin?: 'anonymous' | 'use-credentials' | '';
17
18
  } & ColorOptions
18
19
  >;
19
20
 
20
- // TODO(burdon): Option for neutral background color.
21
21
  export const Image = ({
22
22
  classNames,
23
23
  src,
24
24
  alt = '',
25
+ fit = 'contain',
25
26
  crossOrigin = 'anonymous',
26
27
  sampleSize = 64,
27
28
  contrast = 0.9,
@@ -68,7 +69,16 @@ export const Image = ({
68
69
 
69
70
  return (
70
71
  <div
71
- className={mx(`relative flex w-full justify-center overflow-hidden transition-all duration-700`, classNames)}
72
+ // `isolate` (`isolation: isolate`) creates a new stacking context so
73
+ // the inner <img>'s `z-10` stays scoped to this wrapper. Without it
74
+ // the z-10 leaks into the parent's stacking context and elevates the
75
+ // image above any pseudo-element rings (e.g. Focus.Item's
76
+ // `dx-ring-pseudo` `::after`) painted on ancestors — most visibly,
77
+ // the focus ring on a Card containing a Card.Poster.
78
+ className={mx(
79
+ `relative flex w-full justify-center overflow-hidden transition-all duration-700 isolate`,
80
+ classNames,
81
+ )}
72
82
  style={{
73
83
  backgroundColor: dominantColor,
74
84
  }}
@@ -94,7 +104,10 @@ export const Image = ({
94
104
  crossOrigin={crossOriginState}
95
105
  onError={handleImageError}
96
106
  onLoad={handleImageLoad}
97
- className='z-10 object-contain transition-opacity duration-500'
107
+ className={mx(
108
+ 'z-10 transition-opacity duration-500',
109
+ fit === 'cover' ? 'w-full h-full object-cover' : 'object-contain',
110
+ )}
98
111
  style={{
99
112
  opacity: imageLoaded ? 1 : 0,
100
113
  }}
@@ -162,7 +175,9 @@ const extractDominantColor = (
162
175
  const alpha = pixels[i + 3];
163
176
 
164
177
  // Skip transparent pixels.
165
- if (alpha === 0) continue;
178
+ if (alpha === 0) {
179
+ continue;
180
+ }
166
181
 
167
182
  // Calculate saturation to weight vibrant colors more.
168
183
  const max = Math.max(red, green, blue);
@@ -202,21 +217,29 @@ const isTransparent = (pixels: Uint8ClampedArray, sampleSize: number, threshold:
202
217
  for (let x = 0; x < sampleSize; x++) {
203
218
  // Top edge.
204
219
  const topIndex = x * 4;
205
- if (pixels[topIndex + 3] === 0) edgeTransparentPixels++;
220
+ if (pixels[topIndex + 3] === 0) {
221
+ edgeTransparentPixels++;
222
+ }
206
223
 
207
224
  // Bottom edge.
208
225
  const bottomIndex = ((sampleSize - 1) * sampleSize + x) * 4;
209
- if (pixels[bottomIndex + 3] === 0) edgeTransparentPixels++;
226
+ if (pixels[bottomIndex + 3] === 0) {
227
+ edgeTransparentPixels++;
228
+ }
210
229
  }
211
230
 
212
231
  for (let y = 1; y < sampleSize - 1; y++) {
213
232
  // Left edge.
214
233
  const leftIndex = y * sampleSize * 4;
215
- if (pixels[leftIndex + 3] === 0) edgeTransparentPixels++;
234
+ if (pixels[leftIndex + 3] === 0) {
235
+ edgeTransparentPixels++;
236
+ }
216
237
 
217
238
  // Right edge.
218
239
  const rightIndex = (y * sampleSize + sampleSize - 1) * 4;
219
- if (pixels[rightIndex + 3] === 0) edgeTransparentPixels++;
240
+ if (pixels[rightIndex + 3] === 0) {
241
+ edgeTransparentPixels++;
242
+ }
220
243
  }
221
244
 
222
245
  return edgeTransparentPixels / edgePixels > threshold;
@@ -130,13 +130,13 @@ type TextInputProps = InputSharedProps & ThemedClassName<TextInputPrimitiveProps
130
130
 
131
131
  const TextInput = forwardRef<HTMLInputElement, InputScopedProps<TextInputProps>>(
132
132
  (
133
- { __inputScope, classNames, density: propsDensity, elevation: propsElevation, variant, noAutoFill, ...props },
133
+ { __inputScope, classNames, density: densityProp, elevation: elevationProp, variant, noAutoFill, ...props },
134
134
  forwardedRef,
135
135
  ) => {
136
136
  const { hasIosKeyboard } = useThemeContext();
137
137
  const { tx } = useThemeContext();
138
- const density = useDensityContext(propsDensity);
139
- const elevation = useElevationContext(propsElevation);
138
+ const density = useDensityContext(densityProp);
139
+ const elevation = useElevationContext(elevationProp);
140
140
  const { validationValence } = useInputContext(INPUT_NAME, __inputScope);
141
141
 
142
142
  return (
@@ -176,7 +176,7 @@ export const Collapsible: Story = {
176
176
  <List {...args}>
177
177
  {items.map(({ id, text, body }, index) => (
178
178
  <ListItem.Root key={id} id={id} collapsible={index !== 2} defaultOpen={index % 2 === 0}>
179
- <div role='none' className='grow flex'>
179
+ <div className='grow flex'>
180
180
  {index !== 2 ? <ListItem.OpenTrigger /> : <ListItem.MockOpenTrigger />}
181
181
  <ListItem.Heading classNames='grow pt-2'>{text}</ListItem.Heading>
182
182
  <ListItem.Endcap>
@@ -75,7 +75,7 @@ const MockListItemOpenTrigger = ({
75
75
  }: ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'children'>>) => {
76
76
  const density = useDensityContext();
77
77
  const { tx } = useThemeContext();
78
- return <div role='none' {...props} className={tx('list.item.openTrigger', { density }, classNames)} />;
78
+ return <div {...props} className={tx('list.item.openTrigger', { density }, classNames)} />;
79
79
  };
80
80
 
81
81
  type ListItemHeadingProps = ThemedClassName<ListPrimitiveItemHeadingProps>;
@@ -52,7 +52,6 @@ export const ListDropIndicator = ({
52
52
 
53
53
  return (
54
54
  <div
55
- role='none'
56
55
  style={
57
56
  {
58
57
  '--line-thickness': `${strokeSize}px`,
@@ -32,7 +32,7 @@ const StorybookTreeItem = ({ data, prefix }: StorybookTreeItemProps) => {
32
32
 
33
33
  return (
34
34
  <TreeItem.Root key={id} id={id} collapsible={!valueIsScalar} defaultOpen>
35
- <div role='none' className='grow flex'>
35
+ <div className='grow flex'>
36
36
  {valueIsScalar ? <TreeItem.MockOpenTrigger /> : <TreeItem.OpenTrigger />}
37
37
  <TreeItem.Heading classNames='grow pt-1'>{valueIsScalar ? String(value) : key}</TreeItem.Heading>
38
38
  </div>
@@ -0,0 +1,42 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+
7
+ import { withTheme } from '@dxos/react-ui/testing';
8
+
9
+ import { MediaPlayer } from './MediaPlayer';
10
+
11
+ const meta = {
12
+ title: 'ui/react-ui/MediaPlayer',
13
+ component: MediaPlayer,
14
+ decorators: [withTheme()],
15
+ parameters: { layout: 'centered' },
16
+ } satisfies Meta<typeof MediaPlayer>;
17
+
18
+ export default meta;
19
+
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ export const Video: Story = {
23
+ args: {
24
+ src: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
25
+ classNames: 'max-w-[640px]',
26
+ },
27
+ };
28
+
29
+ export const Audio: Story = {
30
+ args: {
31
+ src: 'https://commondatastorage.googleapis.com/codeskulptor-demos/DDR_assets/Kangaroo_MusiQue_-_The_Neverwritten_Role_Playing_Game.mp3',
32
+ classNames: 'min-w-[480px]',
33
+ },
34
+ };
35
+
36
+ export const ExplicitKind: Story = {
37
+ args: {
38
+ src: 'https://commondatastorage.googleapis.com/codeskulptor-demos/DDR_assets/Kangaroo_MusiQue_-_The_Neverwritten_Role_Playing_Game.mp3',
39
+ kind: 'audio',
40
+ classNames: 'min-w-[480px]',
41
+ },
42
+ };
@@ -0,0 +1,97 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { mx } from '@dxos/ui-theme';
8
+
9
+ import { type ThemedClassName } from '../../util';
10
+
11
+ export type MediaKind = 'video' | 'audio';
12
+
13
+ export type MediaPlayerProps = ThemedClassName<{
14
+ src: string;
15
+ /** Override auto-detection. When omitted, `detectMediaKind(src)` is used and falls back to 'video'. */
16
+ kind?: MediaKind;
17
+ controls?: boolean;
18
+ autoPlay?: boolean;
19
+ loop?: boolean;
20
+ muted?: boolean;
21
+ /** Accessible label for the `<video>` / `<audio>` element. */
22
+ alt?: string;
23
+ /** Defaults to 'anonymous' for cross-origin sources (e.g. signed S3 URLs). */
24
+ crossOrigin?: 'anonymous' | 'use-credentials' | '';
25
+ }>;
26
+
27
+ const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogv', '.mov', '.m4v'];
28
+ const AUDIO_EXTENSIONS = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'];
29
+
30
+ /**
31
+ * Best-effort detection of `video` vs `audio` from a media URL.
32
+ * Inspects the pathname's extension (ignoring query/hash). Returns `undefined`
33
+ * when the URL doesn't look like a recognised media file — callers should
34
+ * default to 'video' or render a fallback (e.g. iframe / img).
35
+ */
36
+ export const detectMediaKind = (src: string): MediaKind | undefined => {
37
+ // Strip query and hash, then take the last path segment's extension.
38
+ const pathname = src.split(/[?#]/, 1)[0]!;
39
+ const lower = pathname.toLowerCase();
40
+ if (VIDEO_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
41
+ return 'video';
42
+ }
43
+ if (AUDIO_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
44
+ return 'audio';
45
+ }
46
+ return undefined;
47
+ };
48
+
49
+ /**
50
+ * Renders a generated or remotely-hosted media artefact as a native
51
+ * `<video>` or `<audio>` element. `kind` defaults to the auto-detected
52
+ * kind from the URL extension (and falls back to 'video' for ambiguous URLs).
53
+ */
54
+ export const MediaPlayer = ({
55
+ classNames,
56
+ src,
57
+ kind,
58
+ controls = true,
59
+ autoPlay = false,
60
+ loop = false,
61
+ muted = false,
62
+ alt,
63
+ crossOrigin = 'anonymous',
64
+ }: MediaPlayerProps) => {
65
+ const resolved = kind ?? detectMediaKind(src) ?? 'video';
66
+ if (resolved === 'audio') {
67
+ return (
68
+ // TODO(burdon): Move to ui-theme.
69
+ <audio
70
+ className={mx('w-full', classNames)}
71
+ src={src}
72
+ controls={controls}
73
+ autoPlay={autoPlay}
74
+ loop={loop}
75
+ muted={muted}
76
+ crossOrigin={crossOrigin}
77
+ aria-label={alt}
78
+ />
79
+ );
80
+ }
81
+
82
+ return (
83
+ // TODO(burdon): Move to ui-theme.
84
+ <div className={mx('dx-container flex items-center justify-center', classNames)}>
85
+ <video
86
+ className='aspect-video max-w-full max-h-full'
87
+ src={src}
88
+ controls={controls}
89
+ autoPlay={autoPlay}
90
+ loop={loop}
91
+ muted={muted}
92
+ crossOrigin={crossOrigin}
93
+ aria-label={alt}
94
+ />
95
+ </div>
96
+ );
97
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './MediaPlayer';
@@ -22,7 +22,7 @@ type DefaultStoryProps = {
22
22
  const DefaultStory = ({ valence, title, body }: DefaultStoryProps) => (
23
23
  <div className='w-[30rem]'>
24
24
  <Message.Root valence={valence}>
25
- {title && <Message.Title>{title}</Message.Title>}
25
+ {title && <Message.Title onClose={() => console.log('close')}>{title}</Message.Title>}
26
26
  {body && <Message.Content>{body}</Message.Content>}
27
27
  </Message.Root>
28
28
  </div>
@@ -6,12 +6,16 @@ import { createContext } from '@radix-ui/react-context';
6
6
  import { Primitive } from '@radix-ui/react-primitive';
7
7
  import { Slot } from '@radix-ui/react-slot';
8
8
  import React, { type ComponentPropsWithRef, forwardRef } from 'react';
9
+ import { useTranslation } from 'react-i18next';
9
10
 
10
11
  import { useId } from '@dxos/react-hooks';
11
12
  import { type Elevation, type MessageValence } from '@dxos/ui-types';
12
13
 
14
+ import { translationKey } from '#translations';
15
+
13
16
  import { useElevationContext, useThemeContext } from '../../hooks';
14
17
  import { type ThemedClassName } from '../../util';
18
+ import { IconButton } from '../Button';
15
19
  import { Icon } from '../Icon';
16
20
 
17
21
  const messageIcons: Record<MessageValence, string> = {
@@ -84,23 +88,36 @@ MessageRoot.displayName = MESSAGE_NAME;
84
88
  //
85
89
 
86
90
  type MessageTitleProps = Omit<ThemedClassName<ComponentPropsWithRef<typeof Primitive.h2>>, 'id'> & {
87
- asChild?: boolean;
88
91
  icon?: string;
92
+ onClose?: () => void;
89
93
  };
90
94
 
91
95
  const MESSAGE_TITLE_NAME = 'MessageTitle';
92
96
 
93
97
  const MessageTitle = forwardRef<HTMLHeadingElement, MessageTitleProps>(
94
- ({ asChild, classNames, children, icon: iconProp, ...props }, forwardedRef) => {
98
+ ({ classNames, children, icon: iconProp, onClose }, forwardedRef) => {
99
+ const { t } = useTranslation(translationKey);
95
100
  const { tx } = useThemeContext();
96
101
  const { titleId, valence } = useMessageContext(MESSAGE_TITLE_NAME);
97
- const Comp = asChild ? Slot : Primitive.h2;
98
102
  const icon = iconProp ?? messageIcons[valence];
99
103
  return (
100
- <Comp {...props} className={tx('message.header', {}, classNames)} id={titleId} ref={forwardedRef}>
101
- {!icon && valence === 'neutral' ? <div /> : <Icon icon={icon} classNames={tx('message.icon', { valence })} />}
102
- <span className={tx('message.title', {}, classNames)}>{children}</span>
103
- </Comp>
104
+ <div className={tx('message.header', {}, classNames)} id={titleId} ref={forwardedRef}>
105
+ {icon && (
106
+ <div className={tx('message.icon', { valence })}>
107
+ <Icon icon={icon} />
108
+ </div>
109
+ )}
110
+ <h2 className={tx('message.title', {}, classNames)}>{children}</h2>
111
+ {onClose && (
112
+ <IconButton
113
+ variant='ghost'
114
+ icon='ph--x--regular'
115
+ iconOnly
116
+ label={t('toolbar-close.label')}
117
+ onClick={onClose}
118
+ />
119
+ )}
120
+ </div>
104
121
  );
105
122
  },
106
123
  );
@@ -48,11 +48,7 @@ const Row = ({ items = 50 }: { items?: number }) => (
48
48
  );
49
49
 
50
50
  const Container = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => {
51
- return (
52
- <div role='none' className={mx('border border-separator rounded-md overflow-hidden', classNames)}>
53
- {children}
54
- </div>
55
- );
51
+ return <div className={mx('border border-separator rounded-md overflow-hidden', classNames)}>{children}</div>;
56
52
  };
57
53
 
58
54
  export const Vertical = {
@@ -276,13 +276,12 @@ const Fade = () => {
276
276
 
277
277
  return (
278
278
  <div
279
- role='none'
280
279
  data-visible={overflow}
281
280
  className={mx(
282
281
  // NOTE: Gradients may not be visible with dark reader extensions.
283
282
  'z-10 absolute top-0 inset-x-0 h-24 w-full',
284
283
  'opacity-0 duration-200 transition-opacity data-[visible="true"]:opacity-100',
285
- 'bg-gradient-to-b from-(--surface-bg) to-transparent pointer-events-none',
284
+ 'bg-gradient-to-b from-(--color-base-surface) to-transparent pointer-events-none',
286
285
  )}
287
286
  />
288
287
  );
@@ -303,7 +302,6 @@ const ScrollDownButton = ({ classNames }: ScrollDownButtonProps) => {
303
302
 
304
303
  return (
305
304
  <div
306
- role='none'
307
305
  className={mx(
308
306
  'absolute bottom-2 right-4 opacity-100 transition-opacity duration-300',
309
307
  pinned && 'opacity-0',
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export { type Label, isLabel, toLocalizedString } from '@dxos/ui-types';
5
+ export { type Label, isLabel, toLocalizedString } from '@dxos/ui-types/translations';
6
6
 
7
7
  export * from './ThemeProvider';
8
8
  export { useTranslation } from './TranslationsProvider';
@@ -12,8 +12,9 @@ import { useTranslation } from 'react-i18next';
12
12
  import { composable, composableProps, slottable, type ToolbarStyleProps } from '@dxos/ui-theme';
13
13
  import { type SlottableProps } from '@dxos/ui-types';
14
14
 
15
+ import { translationKey } from '#translations';
16
+
15
17
  import { useThemeContext } from '../../hooks';
16
- import { translationKey } from '../../translations';
17
18
  import {
18
19
  Button,
19
20
  ButtonGroup,
@@ -160,7 +161,7 @@ const ToolbarToggleGroupItem = forwardRef<HTMLButtonElement, ToolbarToggleGroupI
160
161
  type ToolbarToggleGroupIconItemProps = Omit<ToggleGroupItemPrimitiveProps, 'className'> & IconButtonProps;
161
162
 
162
163
  const ToolbarToggleGroupIconItem = forwardRef<HTMLButtonElement, ToolbarToggleGroupIconItemProps>(
163
- ({ variant, density, elevation, classNames, icon, label, iconOnly, ...props }, forwardedRef) => {
164
+ ({ variant, density, elevation, classNames, icon, label, iconOnly, iconClassNames, ...props }, forwardedRef) => {
164
165
  return (
165
166
  <ToolbarPrimitive.ToolbarToggleItem {...props} asChild>
166
167
  <IconButton
@@ -172,6 +173,7 @@ const ToolbarToggleGroupIconItem = forwardRef<HTMLButtonElement, ToolbarToggleGr
172
173
  icon,
173
174
  label,
174
175
  iconOnly,
176
+ iconClassNames,
175
177
  }}
176
178
  ref={forwardedRef}
177
179
  />
@@ -19,7 +19,7 @@ type DefaultStoryProps = {
19
19
  const DefaultStory = ({ tooltips, defaultOpen }: DefaultStoryProps) => {
20
20
  return (
21
21
  <Tooltip.Provider defaultOpen={defaultOpen}>
22
- <div role='none' className='w-32'>
22
+ <div className='w-32'>
23
23
  {tooltips.map(({ label, content }, i) => (
24
24
  <Tooltip.Trigger asChild key={i} content={content} side='right'>
25
25
  <Button classNames='block w-full'>{label}</Button>
@@ -10,6 +10,7 @@ export * from './Avatars';
10
10
  export * from './Breadcrumb';
11
11
  export * from './Button';
12
12
  export * from './Card';
13
+ export * from './Carousel';
13
14
  export * from './Clipboard';
14
15
  export * from './Dialog';
15
16
  export * from './ErrorFallback';
@@ -20,6 +21,7 @@ export * from './Input';
20
21
  export * from './Link';
21
22
  export * from './List';
22
23
  export * from './Main';
24
+ export * from './MediaPlayer';
23
25
  export * from './Menu';
24
26
  export * from './Message';
25
27
  export * from './Popover';