@dxos/react-ui 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a

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 (239) hide show
  1. package/dist/lib/browser/{chunk-HFJYTGHU.mjs → chunk-CMHHVHX3.mjs} +4 -1
  2. package/dist/lib/browser/chunk-CMHHVHX3.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-F3PLXTDA.mjs → chunk-GTHV4WYK.mjs} +123 -232
  4. package/dist/lib/browser/chunk-GTHV4WYK.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1069 -976
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +5 -5
  9. package/dist/lib/browser/testing/index.mjs.map +2 -2
  10. package/dist/lib/browser/translations.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-LLVUYLAM.mjs → chunk-3VSKWNCQ.mjs} +123 -232
  12. package/dist/lib/node-esm/chunk-3VSKWNCQ.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-OJYUPUCY.mjs → chunk-P4KFQGJI.mjs} +4 -1
  14. package/dist/lib/node-esm/chunk-P4KFQGJI.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1069 -976
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +5 -5
  19. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  20. package/dist/lib/node-esm/translations.mjs +1 -1
  21. package/dist/types/src/components/Avatars/Avatar.d.ts +1 -1
  22. package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
  23. package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts +3 -3
  24. package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  25. package/dist/types/src/components/Button/Button.d.ts +2 -2
  26. package/dist/types/src/components/Button/Button.d.ts.map +1 -1
  27. package/dist/types/src/components/Button/IconButton.stories.d.ts +3 -0
  28. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Button/MicButton.d.ts +24 -0
  30. package/dist/types/src/components/Button/MicButton.d.ts.map +1 -0
  31. package/dist/types/src/components/Button/MicButton.stories.d.ts +57 -0
  32. package/dist/types/src/components/Button/MicButton.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Button/SystemIconButton.d.ts +15 -2
  34. package/dist/types/src/components/Button/SystemIconButton.d.ts.map +1 -1
  35. package/dist/types/src/components/Button/SystemIconButton.stories.d.ts +51 -0
  36. package/dist/types/src/components/Button/SystemIconButton.stories.d.ts.map +1 -0
  37. package/dist/types/src/components/Button/ToggleGroup.d.ts +2 -2
  38. package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -1
  39. package/dist/types/src/components/Button/index.d.ts +1 -0
  40. package/dist/types/src/components/Button/index.d.ts.map +1 -1
  41. package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -1
  42. package/dist/types/src/components/Calendar/Calendar.theme.d.ts.map +1 -1
  43. package/dist/types/src/components/Card/Card.d.ts +8 -8
  44. package/dist/types/src/components/Card/Card.d.ts.map +1 -1
  45. package/dist/types/src/components/Card/Card.stories.d.ts +2 -2
  46. package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/Carousel/Carousel.d.ts +29 -6
  48. package/dist/types/src/components/Carousel/Carousel.d.ts.map +1 -1
  49. package/dist/types/src/components/Carousel/Carousel.stories.d.ts +48 -2
  50. package/dist/types/src/components/Carousel/Carousel.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/Column/Column.d.ts +2 -2
  52. package/dist/types/src/components/Column/Column.d.ts.map +1 -1
  53. package/dist/types/src/components/Dialog/AlertDialog.d.ts +2 -2
  54. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -1
  55. package/dist/types/src/components/Dialog/Dialog.d.ts +1 -1
  56. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -1
  57. package/dist/types/src/components/Dialog/Dialog.stories.d.ts +2 -2
  58. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/Icon/Icon.stories.d.ts +5 -0
  60. package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -1
  61. package/dist/types/src/components/Input/Input.d.ts +4 -1
  62. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  63. package/dist/types/src/components/Input/Input.stories.d.ts +3 -3
  64. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/Input/Input.theme.d.ts +10 -10
  66. package/dist/types/src/components/Input/Input.theme.d.ts.map +1 -1
  67. package/dist/types/src/components/Input/SegmentedInput.d.ts +8 -8
  68. package/dist/types/src/components/Input/SegmentedInput.d.ts.map +1 -1
  69. package/dist/types/src/components/Input/index.d.ts +0 -1
  70. package/dist/types/src/components/Input/index.d.ts.map +1 -1
  71. package/dist/types/src/components/Main/Main.d.ts +3 -3
  72. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  73. package/dist/types/src/components/Menu/ContextMenu.d.ts +1 -1
  74. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -1
  75. package/dist/types/src/components/Menu/DropdownMenu.d.ts +2 -2
  76. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -1
  77. package/dist/types/src/components/Message/Message.d.ts +1 -1
  78. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  79. package/dist/types/src/components/Message/Message.stories.d.ts +2 -2
  80. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  81. package/dist/types/src/components/Panel/Panel.d.ts +1 -1
  82. package/dist/types/src/components/Panel/Panel.d.ts.map +1 -1
  83. package/dist/types/src/components/Panel/Panel.theme.d.ts.map +1 -1
  84. package/dist/types/src/components/Popover/Popover.d.ts +2 -2
  85. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  86. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +2 -0
  87. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  88. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +1 -0
  89. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  90. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +1 -1
  91. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -1
  92. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +2 -2
  93. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -1
  94. package/dist/types/src/components/Select/Select.d.ts +1 -1
  95. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  96. package/dist/types/src/components/Select/Select.stories.d.ts +2 -2
  97. package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
  98. package/dist/types/src/components/Separator/Separator.d.ts.map +1 -1
  99. package/dist/types/src/components/Splitter/Splitter.d.ts +30 -16
  100. package/dist/types/src/components/Splitter/Splitter.d.ts.map +1 -1
  101. package/dist/types/src/components/Splitter/Splitter.stories.d.ts +6 -1
  102. package/dist/types/src/components/Splitter/Splitter.stories.d.ts.map +1 -1
  103. package/dist/types/src/components/Splitter/Splitter.theme.d.ts +3 -1
  104. package/dist/types/src/components/Splitter/Splitter.theme.d.ts.map +1 -1
  105. package/dist/types/src/components/Tag/Tag.d.ts +1 -1
  106. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  107. package/dist/types/src/components/Tag/Tag.theme.d.ts +1 -1
  108. package/dist/types/src/components/Tag/Tag.theme.d.ts.map +1 -1
  109. package/dist/types/src/components/Toast/Toast.d.ts +1 -1
  110. package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
  111. package/dist/types/src/components/Toast/Toast.theme.d.ts.map +1 -1
  112. package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -1
  113. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  114. package/dist/types/src/components/Tooltip/Tooltip.d.ts +3 -3
  115. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  116. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +2 -2
  117. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  118. package/dist/types/src/components/index.d.ts +0 -1
  119. package/dist/types/src/components/index.d.ts.map +1 -1
  120. package/dist/types/src/index.d.ts +1 -1
  121. package/dist/types/src/index.d.ts.map +1 -1
  122. package/dist/types/src/primitives/ThemeProvider/TranslationsProvider.d.ts +1 -8
  123. package/dist/types/src/primitives/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  124. package/dist/types/src/theme/defaultTheme.d.ts.map +1 -1
  125. package/dist/types/src/theme/index.d.ts +0 -2
  126. package/dist/types/src/theme/index.d.ts.map +1 -1
  127. package/dist/types/src/translations.d.ts +3 -0
  128. package/dist/types/src/translations.d.ts.map +1 -1
  129. package/dist/types/src/util/slots.d.ts +1 -1
  130. package/dist/types/src/util/slots.d.ts.map +1 -1
  131. package/dist/types/tsconfig.tsbuildinfo +1 -1
  132. package/package.json +22 -21
  133. package/src/components/Avatars/Avatar.stories.tsx +2 -2
  134. package/src/components/Avatars/Avatar.theme.ts +1 -1
  135. package/src/components/Avatars/Avatar.tsx +3 -3
  136. package/src/components/Breadcrumb/Breadcrumb.tsx +6 -6
  137. package/src/components/Button/Button.tsx +2 -2
  138. package/src/components/Button/MicButton.stories.tsx +60 -0
  139. package/src/components/Button/MicButton.tsx +117 -0
  140. package/src/components/Button/SystemIconButton.stories.tsx +91 -0
  141. package/src/components/Button/SystemIconButton.tsx +113 -2
  142. package/src/components/Button/ToggleGroup.tsx +2 -2
  143. package/src/components/Button/index.ts +1 -0
  144. package/src/components/Calendar/Calendar.theme.ts +2 -0
  145. package/src/components/Calendar/Calendar.tsx +3 -3
  146. package/src/components/Card/Card.stories.tsx +2 -2
  147. package/src/components/Card/Card.tsx +11 -11
  148. package/src/components/Carousel/Carousel.stories.tsx +42 -18
  149. package/src/components/Carousel/Carousel.tsx +169 -33
  150. package/src/components/Column/Column.tsx +4 -7
  151. package/src/components/Dialog/AlertDialog.stories.tsx +2 -2
  152. package/src/components/Dialog/AlertDialog.tsx +13 -13
  153. package/src/components/Dialog/Dialog.stories.tsx +3 -4
  154. package/src/components/Dialog/Dialog.theme.ts +1 -1
  155. package/src/components/Dialog/Dialog.tsx +12 -12
  156. package/src/components/Icon/Icon.stories.tsx +23 -0
  157. package/src/components/Icon/Icon.theme.ts +1 -1
  158. package/src/components/Input/Input.stories.tsx +13 -5
  159. package/src/components/Input/Input.theme.ts +17 -39
  160. package/src/components/Input/Input.tsx +30 -13
  161. package/src/components/Input/SegmentedInput.tsx +50 -41
  162. package/src/components/Input/index.ts +0 -1
  163. package/src/components/Main/Main.tsx +3 -3
  164. package/src/components/MediaPlayer/MediaPlayer.tsx +4 -4
  165. package/src/components/Menu/ContextMenu.tsx +8 -8
  166. package/src/components/Menu/DropdownMenu.tsx +12 -13
  167. package/src/components/Message/Message.stories.tsx +2 -2
  168. package/src/components/Message/Message.theme.ts +1 -1
  169. package/src/components/Message/Message.tsx +2 -2
  170. package/src/components/Panel/Panel.theme.ts +2 -1
  171. package/src/components/Panel/Panel.tsx +1 -1
  172. package/src/components/Popover/Popover.tsx +9 -9
  173. package/src/components/ScrollArea/ScrollArea.tsx +7 -4
  174. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +2 -2
  175. package/src/components/ScrollContainer/ScrollContainer.tsx +2 -2
  176. package/src/components/Select/Select.stories.tsx +2 -2
  177. package/src/components/Select/Select.theme.ts +1 -1
  178. package/src/components/Select/Select.tsx +14 -14
  179. package/src/components/Separator/Separator.tsx +1 -0
  180. package/src/components/Splitter/Splitter.stories.tsx +84 -20
  181. package/src/components/Splitter/Splitter.theme.ts +20 -3
  182. package/src/components/Splitter/Splitter.tsx +320 -47
  183. package/src/components/Tag/Tag.stories.tsx +1 -1
  184. package/src/components/Tag/Tag.theme.ts +1 -1
  185. package/src/components/Tag/Tag.tsx +3 -5
  186. package/src/components/Toast/Toast.theme.ts +3 -2
  187. package/src/components/Toast/Toast.tsx +5 -5
  188. package/src/components/Toolbar/Toolbar.tsx +10 -10
  189. package/src/components/Tooltip/Tooltip.stories.tsx +2 -2
  190. package/src/components/Tooltip/Tooltip.tsx +3 -3
  191. package/src/components/index.ts +0 -1
  192. package/src/exemplars/tabster.stories.tsx +1 -1
  193. package/src/index.ts +1 -1
  194. package/src/primitives/ThemeProvider/TranslationsProvider.tsx +10 -30
  195. package/src/testing/decorators/withLayout.tsx +4 -4
  196. package/src/theme/defaultTheme.ts +0 -4
  197. package/src/theme/index.ts +0 -2
  198. package/src/translations.ts +8 -0
  199. package/src/util/slots.ts +5 -5
  200. package/dist/lib/browser/chunk-F3PLXTDA.mjs.map +0 -7
  201. package/dist/lib/browser/chunk-HFJYTGHU.mjs.map +0 -7
  202. package/dist/lib/node-esm/chunk-LLVUYLAM.mjs.map +0 -7
  203. package/dist/lib/node-esm/chunk-OJYUPUCY.mjs.map +0 -7
  204. package/dist/types/src/components/Input/constants.d.ts +0 -5
  205. package/dist/types/src/components/Input/constants.d.ts.map +0 -1
  206. package/dist/types/src/components/List/List.d.ts +0 -29
  207. package/dist/types/src/components/List/List.d.ts.map +0 -1
  208. package/dist/types/src/components/List/List.stories.d.ts +0 -16
  209. package/dist/types/src/components/List/List.stories.d.ts.map +0 -1
  210. package/dist/types/src/components/List/List.theme.d.ts +0 -7
  211. package/dist/types/src/components/List/List.theme.d.ts.map +0 -1
  212. package/dist/types/src/components/List/ListDropIndicator.d.ts +0 -13
  213. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +0 -1
  214. package/dist/types/src/components/List/Tree.d.ts +0 -23
  215. package/dist/types/src/components/List/Tree.d.ts.map +0 -1
  216. package/dist/types/src/components/List/Tree.stories.d.ts +0 -15
  217. package/dist/types/src/components/List/Tree.stories.d.ts.map +0 -1
  218. package/dist/types/src/components/List/TreeDropIndicator.d.ts +0 -8
  219. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +0 -1
  220. package/dist/types/src/components/List/Treegrid.d.ts +0 -33
  221. package/dist/types/src/components/List/Treegrid.d.ts.map +0 -1
  222. package/dist/types/src/components/List/Treegrid.stories.d.ts +0 -12
  223. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +0 -1
  224. package/dist/types/src/components/List/Treegrid.theme.d.ts +0 -7
  225. package/dist/types/src/components/List/Treegrid.theme.d.ts.map +0 -1
  226. package/dist/types/src/components/List/index.d.ts +0 -4
  227. package/dist/types/src/components/List/index.d.ts.map +0 -1
  228. package/src/components/Input/constants.ts +0 -8
  229. package/src/components/List/List.stories.tsx +0 -234
  230. package/src/components/List/List.theme.ts +0 -47
  231. package/src/components/List/List.tsx +0 -171
  232. package/src/components/List/ListDropIndicator.tsx +0 -69
  233. package/src/components/List/Tree.stories.tsx +0 -87
  234. package/src/components/List/Tree.tsx +0 -74
  235. package/src/components/List/TreeDropIndicator.tsx +0 -70
  236. package/src/components/List/Treegrid.stories.tsx +0 -156
  237. package/src/components/List/Treegrid.theme.ts +0 -35
  238. package/src/components/List/Treegrid.tsx +0 -187
  239. package/src/components/List/index.ts +0 -7
@@ -5,37 +5,49 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React from 'react';
7
7
 
8
- import { withTheme } from '../../testing';
9
- import { Carousel } from './Carousel';
8
+ import { withLayout, withTheme } from '../../testing';
9
+ import { translations } from '../../translations';
10
+ import { Carousel, type CarouselTransition } from './Carousel';
10
11
 
11
12
  // Stable placeholder images so the story renders without network fixtures.
12
13
  const IMAGES = Array.from({ length: 5 }).map((_, index) => `https://placehold.co/640x360?text=Slide+${index + 1}`);
13
14
 
14
- type DefaultStoryProps = { count?: number };
15
+ type StoryArgs = {
16
+ count?: number;
17
+ transition?: CarouselTransition;
18
+ continuous?: boolean;
19
+ autoAdvance?: number;
20
+ };
15
21
 
16
- const DefaultStory = ({ count = IMAGES.length }: DefaultStoryProps) => {
22
+ const DefaultStory = ({ count = IMAGES.length, transition, continuous, autoAdvance }: StoryArgs) => {
17
23
  const images = IMAGES.slice(0, count);
18
24
  return (
19
- <Carousel.Root count={images.length}>
20
- <Carousel.Content classNames='max-w-[40rem]'>
21
- <Carousel.Previous />
22
- <Carousel.Viewport>
23
- {images.map((src, index) => (
24
- <Carousel.Slide key={src} index={index} src={src} alt={`Slide ${index + 1}`} />
25
- ))}
26
- </Carousel.Viewport>
27
- <Carousel.Next />
28
- <Carousel.Indicators />
29
- <Carousel.Caption>{(index) => `Slide ${index + 1} of ${images.length}`}</Carousel.Caption>
30
- </Carousel.Content>
31
- </Carousel.Root>
25
+ <div className='h-full flex items-center'>
26
+ <Carousel.Root count={images.length} transition={transition} continuous={continuous} autoAdvance={autoAdvance}>
27
+ <Carousel.Content>
28
+ <Carousel.Previous />
29
+ <Carousel.Viewport>
30
+ {images.map((src, index) => (
31
+ <Carousel.Slide key={src} index={index} src={src} alt={`Slide ${index + 1}`} />
32
+ ))}
33
+ </Carousel.Viewport>
34
+ <Carousel.Next />
35
+ <Carousel.Indicators />
36
+ <Carousel.Caption>{(index) => `Slide ${index + 1} of ${images.length}`}</Carousel.Caption>
37
+ </Carousel.Content>
38
+ </Carousel.Root>
39
+ </div>
32
40
  );
33
41
  };
34
42
 
35
43
  const meta = {
36
44
  title: 'ui/react-ui-core/components/Carousel',
37
45
  render: DefaultStory,
38
- decorators: [withTheme()],
46
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
47
+ parameters: {
48
+ layout: 'fullscreen',
49
+ translations,
50
+ },
39
51
  } satisfies Meta<typeof DefaultStory>;
40
52
 
41
53
  export default meta;
@@ -45,3 +57,15 @@ type Story = StoryObj<typeof meta>;
45
57
  export const Default: Story = {
46
58
  args: { count: 5 },
47
59
  };
60
+
61
+ export const Sliding: Story = {
62
+ args: { count: 5, transition: 'slide' },
63
+ };
64
+
65
+ export const Continuous: Story = {
66
+ args: { count: 5, transition: 'slide', continuous: true },
67
+ };
68
+
69
+ export const AutoAdvancing: Story = {
70
+ args: { count: 5, transition: 'slide', continuous: true, autoAdvance: 5_000 },
71
+ };
@@ -5,11 +5,16 @@
5
5
  import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
6
  import { createContext } from '@radix-ui/react-context';
7
7
  import React, {
8
+ Children,
8
9
  type KeyboardEvent,
9
10
  type PropsWithChildren,
10
11
  type ReactNode,
12
+ type TransitionEvent,
13
+ cloneElement,
14
+ isValidElement,
11
15
  useCallback,
12
16
  useEffect,
17
+ useRef,
13
18
  useState,
14
19
  } from 'react';
15
20
 
@@ -19,18 +24,33 @@ import { useTranslation } from '../../primitives';
19
24
  import { translationKey } from '../../translations';
20
25
  import { type ThemedClassName, composable, composableProps } from '../../util';
21
26
  import { IconButton } from '../Button';
22
- import { MediaPlayer, type MediaKind } from '../MediaPlayer';
27
+ import { type MediaKind, MediaPlayer } from '../MediaPlayer';
23
28
 
24
- // TODO(burdon): Move per-element class strings to `@dxos/ui-theme` (theme tokens)
25
- // so callers can re-theme via the same mechanism the rest of `react-ui` uses.
29
+ // TODO(burdon): Controller.
26
30
 
27
31
  //
28
32
  // Context
29
33
  //
30
34
 
35
+ /** Slide change behaviour: `none` hard-swaps the active slide, `slide` animates a horizontal track. */
36
+ export type CarouselTransition = 'none' | 'slide';
37
+
31
38
  type CarouselContextValue = {
32
39
  index: number;
33
40
  count: number;
41
+ transition: CarouselTransition;
42
+ /** When `transition === 'slide'`, wrap-around advances continue in the same direction (clone + snap). */
43
+ continuous: boolean;
44
+ /**
45
+ * Track position in slide units. Equals `index` except mid-wrap, when it points at a clone cell
46
+ * (`count` = trailing clone of the first slide, `-1` = leading clone of the last) so the track keeps
47
+ * moving in the travel direction. Settled back to the real index once the transition ends.
48
+ */
49
+ offset: number;
50
+ /** Whether the track transform should animate; disabled for the instantaneous post-wrap snap. */
51
+ animate: boolean;
52
+ /** Snap a clone cell back to its real slide once the wrap transition completes. */
53
+ settle: () => void;
34
54
  setIndex: (index: number) => void;
35
55
  next: () => void;
36
56
  prev: () => void;
@@ -50,58 +70,120 @@ export const useCarousel = (): CarouselContextValue => useCarouselContext('useCa
50
70
  export type CarouselRootProps = PropsWithChildren<{
51
71
  /** Total number of slides; drives auto-advance and indicator counts. */
52
72
  count: number;
53
- /** Whether to auto-advance slides on mount. Defaults to `false`. */
54
- autorun?: boolean;
55
- /** Auto-advance interval in milliseconds. Set 0 to disable. */
56
- intervalMs?: number;
73
+ /**
74
+ * Auto-advance interval in milliseconds. A positive value advances slides on its own until the user
75
+ * interacts with a control; omit (or `0`) to disable.
76
+ */
77
+ autoAdvance?: number;
57
78
  defaultIndex?: number;
79
+ /** Slide change behaviour. Defaults to `none` (hard swap); `slide` animates a horizontal track. */
80
+ transition?: CarouselTransition;
81
+ /**
82
+ * Wrap-around in the same travel direction (last → first slides forward, first → last slides back).
83
+ * Only applies when `transition === 'slide'`; otherwise wrap is an instant index change.
84
+ */
85
+ continuous?: boolean;
58
86
  }>;
59
87
 
60
88
  const CarouselRoot = ({
61
89
  children,
62
90
  count,
63
- autorun = false,
64
- intervalMs = 5_000,
91
+ autoAdvance = 0,
65
92
  defaultIndex = 0,
93
+ transition = 'none',
94
+ continuous = false,
66
95
  }: CarouselRootProps) => {
67
96
  const [index, setIndexState] = useState(defaultIndex);
68
- const [autoAdvance, setAutoAdvance] = useState(autorun);
97
+ const [offset, setOffset] = useState(defaultIndex);
98
+ const [animate, setAnimate] = useState(true);
99
+ const [autoEnabled, setAutoEnabled] = useState(autoAdvance > 0);
100
+
101
+ // Continuous wrap is only meaningful for the animated track with more than one slide.
102
+ const wraps = continuous && transition === 'slide' && count > 1;
103
+
104
+ // Latest index without re-creating the advance/auto-advance callbacks on every change.
105
+ const indexRef = useRef(index);
106
+ useEffect(() => {
107
+ indexRef.current = index;
108
+ }, [index]);
69
109
 
70
110
  // Reset to first slide if the slide count shrinks below the current index.
71
111
  useEffect(() => {
72
112
  if (index >= count) {
73
113
  setIndexState(0);
114
+ setOffset(0);
74
115
  }
75
116
  }, [count, index]);
76
117
 
118
+ // Step one slide in `delta` direction. `stopAuto` halts auto-advance on user interaction. In wrap
119
+ // mode an edge step targets a clone cell (offset `count` / `-1`) so the track travels in `delta`'s
120
+ // direction; `settle` snaps it back to the real index after the transition.
121
+ const advance = useCallback(
122
+ (delta: 1 | -1, stopAuto: boolean) => {
123
+ if (stopAuto) {
124
+ setAutoEnabled(false);
125
+ }
126
+ const current = indexRef.current;
127
+ const nextIndex = (current + delta + count) % count;
128
+ setAnimate(true);
129
+ if (wraps && delta === 1 && current === count - 1) {
130
+ setOffset(count);
131
+ } else if (wraps && delta === -1 && current === 0) {
132
+ setOffset(-1);
133
+ } else {
134
+ setOffset(nextIndex);
135
+ }
136
+ setIndexState(nextIndex);
137
+ },
138
+ [count, wraps],
139
+ );
140
+
77
141
  // Auto-advance — stops permanently once the user interacts with any control.
78
142
  useEffect(() => {
79
- if (!autoAdvance || count <= 1 || intervalMs <= 0) {
143
+ if (!autoEnabled || count <= 1 || autoAdvance <= 0) {
80
144
  return;
81
145
  }
82
- const handle = setInterval(() => setIndexState((i) => (i + 1) % count), intervalMs);
146
+ const handle = setInterval(() => advance(1, false), autoAdvance);
83
147
  return () => clearInterval(handle);
84
- }, [autoAdvance, count, intervalMs]);
148
+ }, [autoEnabled, count, autoAdvance, advance]);
85
149
 
86
150
  const setIndex = useCallback((next: number) => {
87
- setAutoAdvance(false);
151
+ setAutoEnabled(false);
152
+ setAnimate(true);
153
+ setOffset(next);
88
154
  setIndexState(next);
89
155
  }, []);
90
- const next = useCallback(() => {
91
- setAutoAdvance(false);
92
- setIndexState((i) => (i + 1) % count);
93
- }, [count]);
94
- const prev = useCallback(() => {
95
- setAutoAdvance(false);
96
- setIndexState((i) => (i - 1 + count) % count);
97
- }, [count]);
156
+ const next = useCallback(() => advance(1, true), [advance]);
157
+ const prev = useCallback(() => advance(-1, true), [advance]);
158
+
159
+ // Once the wrap transition lands on a clone, jump (without animating) to the matching real slide.
160
+ const settle = useCallback(() => {
161
+ if (offset === count) {
162
+ setAnimate(false);
163
+ setOffset(0);
164
+ } else if (offset === -1) {
165
+ setAnimate(false);
166
+ setOffset(count - 1);
167
+ }
168
+ }, [offset, count]);
98
169
 
99
170
  if (count === 0) {
100
171
  return null;
101
172
  }
102
173
 
103
174
  return (
104
- <CarouselProvider index={index} count={count} setIndex={setIndex} next={next} prev={prev}>
175
+ <CarouselProvider
176
+ index={index}
177
+ count={count}
178
+ transition={transition}
179
+ continuous={wraps}
180
+ offset={offset}
181
+ animate={animate}
182
+ settle={settle}
183
+ setIndex={setIndex}
184
+ next={next}
185
+ prev={prev}
186
+ >
105
187
  {children}
106
188
  </CarouselProvider>
107
189
  );
@@ -115,7 +197,6 @@ CarouselRoot.displayName = 'Carousel.Root';
115
197
 
116
198
  export type CarouselContentProps = ThemedClassName<PropsWithChildren<{}>>;
117
199
 
118
- // `composable` so a parent `<… asChild>` (Slot) is respected — the injected className/ref land on the grid.
119
200
  const CarouselContent = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => (
120
201
  // Rows are `[1fr, auto]`: row 1 (Previous|Viewport|Next) stretches when the parent
121
202
  // gives the carousel a definite height, and row 2 (Indicators / Caption) sticks to
@@ -143,7 +224,8 @@ export type CarouselViewportProps = ThemedClassName<PropsWithChildren<{}>>;
143
224
 
144
225
  const CarouselViewport = ({ children, classNames }: CarouselViewportProps) => {
145
226
  const { t } = useTranslation(translationKey);
146
- const { count, next, prev } = useCarousel();
227
+ const { count, transition, continuous, offset, animate, settle, next, prev } = useCarousel();
228
+
147
229
  const handleKeyDown = useCallback(
148
230
  (event: KeyboardEvent<HTMLDivElement>) => {
149
231
  if (count <= 1) {
@@ -160,6 +242,31 @@ const CarouselViewport = ({ children, classNames }: CarouselViewportProps) => {
160
242
  [count, next, prev],
161
243
  );
162
244
 
245
+ // In continuous mode bracket the track with clones — last before first, first after last — so a wrap
246
+ // step has a real cell to slide into before `settle` snaps back to the matching slide.
247
+ const slides = Children.toArray(children);
248
+ const first = slides[0];
249
+ const last = slides[slides.length - 1];
250
+ const trackChildren =
251
+ continuous && slides.length > 1
252
+ ? [
253
+ isValidElement(last) ? cloneElement(last, { key: 'clone-last' }) : last,
254
+ ...slides,
255
+ isValidElement(first) ? cloneElement(first, { key: 'clone-first' }) : first,
256
+ ]
257
+ : slides;
258
+ // Leading clone occupies the first cell in continuous mode, so shift the translate by one.
259
+ const translate = continuous ? -(offset + 1) * 100 : -offset * 100;
260
+
261
+ const handleTransitionEnd = useCallback(
262
+ (event: TransitionEvent<HTMLDivElement>) => {
263
+ if (event.target === event.currentTarget && event.propertyName === 'transform') {
264
+ settle();
265
+ }
266
+ },
267
+ [settle],
268
+ );
269
+
163
270
  return (
164
271
  <div
165
272
  // TODO(burdon): Move to ui-theme.
@@ -174,7 +281,24 @@ const CarouselViewport = ({ children, classNames }: CarouselViewportProps) => {
174
281
  aria-label={t('carousel-viewport.label')}
175
282
  onKeyDown={handleKeyDown}
176
283
  >
177
- {children}
284
+ {transition === 'slide' ? (
285
+ // Lay slides side-by-side in a flex track and translate by the active index. Each slide
286
+ // sizes itself to the viewport via `shrink-0 basis-full` (see `Carousel.Slide`).
287
+ // `motion-reduce` respects the user's reduced-motion preference; the snap after a wrap also
288
+ // disables the transition (`animate === false`).
289
+ <div
290
+ className={mx(
291
+ 'flex h-full motion-reduce:transition-none',
292
+ animate ? 'transition-transform duration-300 ease-out' : 'transition-none',
293
+ )}
294
+ style={{ transform: `translateX(${translate}%)` }}
295
+ onTransitionEnd={handleTransitionEnd}
296
+ >
297
+ {trackChildren}
298
+ </div>
299
+ ) : (
300
+ children
301
+ )}
178
302
  </div>
179
303
  );
180
304
  };
@@ -212,20 +336,30 @@ const CarouselSlide = ({
212
336
  muted,
213
337
  crossOrigin,
214
338
  }: CarouselSlideProps) => {
215
- const { index: active } = useCarousel();
216
- if (active !== index) {
339
+ const { index: active, transition } = useCarousel();
340
+ // In `slide` mode every slide stays mounted as a fixed-width track cell so the track can animate;
341
+ // in `none` mode only the active slide mounts, overlaid via absolute positioning.
342
+ if (transition !== 'slide' && active !== index) {
217
343
  return null;
218
344
  }
219
345
 
220
346
  return (
221
- <div className={mx('absolute inset-0 w-full h-full bg-baseSurface', classNames)}>
347
+ <div
348
+ className={mx(
349
+ transition === 'slide'
350
+ ? 'relative shrink-0 basis-full h-full bg-baseSurface'
351
+ : 'absolute inset-0 w-full h-full bg-baseSurface',
352
+ classNames,
353
+ )}
354
+ >
222
355
  <MediaPlayer
223
356
  src={src}
224
357
  kind={kind}
225
358
  alt={alt}
226
359
  classNames='w-full h-full'
227
360
  controls={controls}
228
- autoPlay={autoPlay}
361
+ // In `slide` mode every slide is mounted; only auto-play the active one to avoid off-screen playback.
362
+ autoPlay={autoPlay && active === index}
229
363
  loop={loop}
230
364
  muted={muted}
231
365
  crossOrigin={crossOrigin}
@@ -314,14 +448,16 @@ const CarouselIndicators = ({ classNames }: CarouselIndicatorsProps) => {
314
448
  key={i}
315
449
  role='tab'
316
450
  aria-selected={i === index}
317
- classNames={i === index ? 'text-primary-500' : 'text-description'}
451
+ classNames={mx(i === index ? 'text-primary-500' : 'text-description')}
452
+ variant='ghost'
453
+ density='sm'
454
+ size={3}
455
+ square
318
456
  icon={i === index ? 'ph--circle--fill' : 'ph--circle--regular'}
319
457
  iconOnly
320
458
  label={t('carousel-go-to.label', { index: i + 1 })}
321
459
  onClick={() => setIndex(i)}
322
460
  onFocus={() => setIndex(i)}
323
- size={3}
324
- variant='ghost'
325
461
  />
326
462
  ))}
327
463
  </div>
@@ -17,10 +17,9 @@ import { composableProps, slottable } from '../../util';
17
17
 
18
18
  const COLUMN_ROOT_NAME = 'Column.Root';
19
19
 
20
- type GutterSize = 'xs' | 'sm' | 'md' | 'lg';
20
+ type GutterSize = 'sm' | 'md' | 'lg';
21
21
 
22
22
  const gutterSizes: Record<GutterSize, string> = {
23
- xs: 'var(--dx-gutter-xs)',
24
23
  sm: 'var(--dx-gutter-sm)',
25
24
  md: 'var(--dx-gutter-md)',
26
25
  lg: 'var(--dx-gutter-lg)',
@@ -42,11 +41,9 @@ type ColumnRootProps = { gutter?: GutterSize };
42
41
  * - **Column.Row** — 3-col subgrid row (icons in gutters, content in center).
43
42
  *
44
43
  * Use `withColumn.center()` / `withColumn.bleed()` helpers to apply placement on slotted elements.
45
- *
46
- * Gutter sizes: `'sm'` for compact layouts (Dialog); `'md'` (default); `'lg'` for wider spacing.
47
44
  */
48
45
  const ColumnRoot = slottable<HTMLDivElement, ColumnRootProps>(
49
- ({ children, asChild, role, gutter = 'md', ...props }, forwardedRef) => {
46
+ ({ children, asChild, role, gutter = 'lg', ...props }, forwardedRef) => {
50
47
  const { className, ...rest } = composableProps(props);
51
48
  const Comp = asChild ? Slot : Primitive.div;
52
49
  const { tx } = useThemeContext();
@@ -60,7 +57,7 @@ const ColumnRoot = slottable<HTMLDivElement, ColumnRootProps>(
60
57
  ...rest.style,
61
58
  '--gutter': gutterSize,
62
59
  '--dx-col': '2 / span 1',
63
- gridTemplateColumns: [gutterSize, 'minmax(0,1fr)', gutterSize].join(' '),
60
+ 'gridTemplateColumns': [gutterSize, 'minmax(0,1fr)', gutterSize].join(' '),
64
61
  } as CSSProperties
65
62
  }
66
63
  className={tx('column.root', { gutter }, className)}
@@ -198,4 +195,4 @@ export const Column = {
198
195
  Center: ColumnCenter,
199
196
  };
200
197
 
201
- export type { ColumnRootProps, ColumnRowProps, ColumnBlockProps, ColumnBleedProps, ColumnCenterProps };
198
+ export type { ColumnBleedProps, ColumnBlockProps, ColumnCenterProps, ColumnRootProps, ColumnRowProps };
@@ -11,7 +11,7 @@ import { withTheme } from '../../testing';
11
11
  import { Button } from '../Button';
12
12
  import { AlertDialog } from './AlertDialog';
13
13
 
14
- type DefaultStoryProps = Partial<{
14
+ type StoryArgs = Partial<{
15
15
  title: string;
16
16
  description: string;
17
17
  openTrigger: string;
@@ -19,7 +19,7 @@ type DefaultStoryProps = Partial<{
19
19
  actionTrigger: string;
20
20
  }>;
21
21
 
22
- const DefaultStory = ({ title, description, openTrigger, cancelTrigger, actionTrigger }: DefaultStoryProps) => {
22
+ const DefaultStory = ({ title, description, openTrigger, cancelTrigger, actionTrigger }: StoryArgs) => {
23
23
  return (
24
24
  <AlertDialog.Root defaultOpen>
25
25
  <AlertDialog.Trigger asChild>
@@ -13,10 +13,10 @@ import { type ThemedClassName } from '../../util';
13
13
  import { Column } from '../Column';
14
14
  import {
15
15
  Dialog,
16
- type DialogHeaderProps,
17
- type DialogBodyProps,
18
16
  type DialogActionBarProps,
19
17
  type DialogActionIconButtonProps,
18
+ type DialogBodyProps,
19
+ type DialogHeaderProps,
20
20
  } from './Dialog';
21
21
 
22
22
  //
@@ -122,7 +122,7 @@ const AlertDialogContent: ForwardRefExoticComponent<AlertDialogContentProps> = f
122
122
  className={tx('dialog.content', { inOverlayLayout, size }, classNames)}
123
123
  ref={forwardedRef}
124
124
  >
125
- <Column.Root classNames='dx-expander' gutter='sm'>
125
+ <Column.Root classNames='dx-expander' gutter='lg'>
126
126
  {children}
127
127
  </Column.Root>
128
128
  </AlertDialogPrimitive.Content>
@@ -192,18 +192,18 @@ export const AlertDialog = {
192
192
  };
193
193
 
194
194
  export type {
195
- AlertDialogRootProps,
196
- AlertDialogTriggerProps,
197
- AlertDialogPortalProps,
198
- AlertDialogOverlayProps,
195
+ DialogActionBarProps as AlertDialogActionBarProps,
196
+ DialogActionIconButtonProps as AlertDialogActionIconButtonProps,
197
+ AlertDialogActionProps,
198
+ DialogBodyProps as AlertDialogBodyProps,
199
+ AlertDialogCancelProps,
199
200
  AlertDialogContentProps,
200
- AlertDialogTitleProps,
201
201
  AlertDialogDescriptionProps,
202
- AlertDialogCancelProps,
203
- AlertDialogActionProps,
204
202
  // Re-export shared types.
205
203
  DialogHeaderProps as AlertDialogHeaderProps,
206
- DialogBodyProps as AlertDialogBodyProps,
207
- DialogActionBarProps as AlertDialogActionBarProps,
208
- DialogActionIconButtonProps as AlertDialogActionIconButtonProps,
204
+ AlertDialogOverlayProps,
205
+ AlertDialogPortalProps,
206
+ AlertDialogRootProps,
207
+ AlertDialogTitleProps,
208
+ AlertDialogTriggerProps,
209
209
  };
@@ -13,7 +13,7 @@ import { Input } from '../Input';
13
13
  import { ScrollArea } from '../ScrollArea';
14
14
  import { Dialog, type DialogContentProps } from './Dialog';
15
15
 
16
- type DefaultStoryProps = Pick<DialogContentProps, 'size'> &
16
+ type StoryArgs = Pick<DialogContentProps, 'size'> &
17
17
  Partial<{
18
18
  title: string;
19
19
  description: string;
@@ -26,7 +26,7 @@ type DefaultStoryProps = Pick<DialogContentProps, 'size'> &
26
26
  * Standard Dialog with non-scrolling content in Dialog.Body.
27
27
  * Dialog.Body propagates the Column grid via subgrid. Children auto-center via --dx-col.
28
28
  */
29
- const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: DefaultStoryProps) => {
29
+ const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: StoryArgs) => {
30
30
  return (
31
31
  <Dialog.Root defaultOpen modal>
32
32
  <Dialog.Trigger asChild>
@@ -45,7 +45,6 @@ const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blo
45
45
  <Dialog.Body>
46
46
  <Dialog.Description>{description}</Dialog.Description>
47
47
  <Input.Root>
48
- <Input.Label>Value</Input.Label>
49
48
  <Input.TextInput placeholder='Enter value' />
50
49
  </Input.Root>
51
50
  </Dialog.Body>
@@ -65,7 +64,7 @@ const DefaultStory = ({ size, title, description, openTrigger, closeTrigger, blo
65
64
  * The ScrollArea breaks out of Body's gutter padding via `--gutter`
66
65
  * and applies its own asymmetric padding (accounting for scrollbar width).
67
66
  */
68
- const ScrollingStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: DefaultStoryProps) => {
67
+ const ScrollingStory = ({ size, title, description, openTrigger, closeTrigger, blockAlign }: StoryArgs) => {
69
68
  return (
70
69
  <Dialog.Root defaultOpen modal>
71
70
  <Dialog.Trigger asChild>
@@ -39,7 +39,7 @@ const header: ComponentFunction<DialogStyleProps> = (_props, ...etc) =>
39
39
  mx('dx-dialog__header flex pb-4 items-center justify-between', withColumn.center(), ...etc);
40
40
 
41
41
  const body: ComponentFunction<DialogStyleProps> = (_props, ...etc) =>
42
- mx('dx-dialog__body dx-expander', withColumn.propagate(), ...etc);
42
+ mx('dx-dialog__body dx-expander gap-y-2', withColumn.propagate(), ...etc);
43
43
 
44
44
  const actionBar: ComponentFunction<DialogStyleProps> = (_props, ...etc) =>
45
45
  mx('dx-dialog__actionbar flex items-center pt-4 gap-2', withColumn.center(), ...etc);
@@ -107,7 +107,7 @@ type DialogContentProps = ThemedClassName<ComponentPropsWithRef<typeof DialogPri
107
107
  };
108
108
 
109
109
  const DialogContent: ForwardRefExoticComponent<DialogContentProps> = forwardRef<HTMLDivElement, DialogContentProps>(
110
- ({ classNames, children, size = 'md', inOverlayLayout: propsInOverlayLayout, ...props }, forwardedRef) => {
110
+ ({ classNames, children, size = 'sm', inOverlayLayout: propsInOverlayLayout, ...props }, forwardedRef) => {
111
111
  const { tx } = useThemeContext();
112
112
  const { inOverlayLayout } = useOverlayLayoutContext(DIALOG_CONTENT_NAME);
113
113
 
@@ -127,7 +127,7 @@ const DialogContent: ForwardRefExoticComponent<DialogContentProps> = forwardRef<
127
127
  )}
128
128
  ref={forwardedRef}
129
129
  >
130
- <Column.Root classNames='dx-expander' gutter='sm'>
130
+ <Column.Root classNames='dx-expander' gutter='lg'>
131
131
  {children}
132
132
  </Column.Root>
133
133
  </DialogPrimitive.Content>
@@ -298,17 +298,17 @@ export const Dialog = {
298
298
  };
299
299
 
300
300
  export type {
301
- DialogRootProps,
302
- DialogTriggerProps,
303
- DialogPortalProps,
304
- DialogOverlayProps,
305
- DialogContentProps,
306
- DialogHeaderProps,
307
- DialogBodyProps,
308
- DialogTitleProps,
309
- DialogDescriptionProps,
310
301
  DialogActionBarProps,
311
- DialogCloseProps,
312
302
  DialogActionIconButtonAction,
313
303
  DialogActionIconButtonProps,
304
+ DialogBodyProps,
305
+ DialogCloseProps,
306
+ DialogContentProps,
307
+ DialogDescriptionProps,
308
+ DialogHeaderProps,
309
+ DialogOverlayProps,
310
+ DialogPortalProps,
311
+ DialogRootProps,
312
+ DialogTitleProps,
313
+ DialogTriggerProps,
314
314
  };
@@ -75,6 +75,29 @@ export const Static: Story = {
75
75
  },
76
76
  };
77
77
 
78
+ export const Custom_DXOS: Story = {
79
+ args: {
80
+ icon: 'dx--dxos--regular',
81
+ classNames: getSize(16),
82
+ },
83
+ };
84
+
85
+ /**
86
+ * Custom DXOS brand icons (the `dx--` set), sourced from `@dxos/brand` SVGs.
87
+ */
88
+ export const Brand: Story = {
89
+ args: {
90
+ icon: 'dx--dxos--regular',
91
+ },
92
+ render: () => (
93
+ <div className='flex gap-8'>
94
+ {(['dxos', 'echo', 'halo', 'mesh', 'dxns', 'kube'] as const).map((name) => (
95
+ <Icon key={name} icon={`dx--${name}--regular`} classNames={getSize(12)} />
96
+ ))}
97
+ </div>
98
+ ),
99
+ };
100
+
78
101
  export const Dynamic: Story = {
79
102
  args: {
80
103
  icon: 'ph--github-logo--regular',
@@ -33,7 +33,7 @@ const root: ComponentFunction<IconStyleProps> = ({ size }, etc) => {
33
33
  */
34
34
  const block: ComponentFunction<IconBlockStyleProps> = ({ compact, square }, ...etc) =>
35
35
  mx(
36
- 'grid place-items-center [&>img]:max-w-[1.5rem]',
36
+ 'grid place-items-center shrink-0 [&>img]:max-w-[1.5rem]',
37
37
  square ? 'aspect-square' : 'w-[var(--dx-rail-item)]',
38
38
  compact ? '' : 'h-[var(--dx-rail-item)]',
39
39
  ...etc,