@dxos/react-ui-mosaic 0.8.4-main.bc674ce → 0.8.4-main.c85a9c8dae

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 (186) hide show
  1. package/dist/lib/browser/chunk-7S4PIFKU.mjs +953 -0
  2. package/dist/lib/browser/chunk-7S4PIFKU.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +596 -70
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/playwright/index.mjs +138 -0
  7. package/dist/lib/browser/playwright/index.mjs.map +7 -0
  8. package/dist/lib/browser/testing/index.mjs +61 -232
  9. package/dist/lib/browser/testing/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/chunk-MK6KU66S.mjs +955 -0
  11. package/dist/lib/node-esm/chunk-MK6KU66S.mjs.map +7 -0
  12. package/dist/lib/node-esm/index.mjs +596 -70
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/playwright/index.mjs +140 -0
  16. package/dist/lib/node-esm/playwright/index.mjs.map +7 -0
  17. package/dist/lib/node-esm/testing/index.mjs +61 -232
  18. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  19. package/dist/types/src/components/Board/Board.d.ts +81 -0
  20. package/dist/types/src/components/Board/Board.d.ts.map +1 -0
  21. package/dist/types/src/components/{Mosaic/Mosaic.stories.d.ts → Board/Board.stories.d.ts} +15 -1
  22. package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/Board/Column.d.ts +40 -0
  24. package/dist/types/src/components/Board/Column.d.ts.map +1 -0
  25. package/dist/types/src/components/Board/Item.d.ts +9 -0
  26. package/dist/types/src/components/Board/Item.d.ts.map +1 -0
  27. package/dist/types/src/components/Board/index.d.ts +2 -0
  28. package/dist/types/src/components/Board/index.d.ts.map +1 -0
  29. package/dist/types/src/components/Focus/Focus.d.ts +5 -4
  30. package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
  31. package/dist/types/src/components/Focus/index.d.ts +0 -1
  32. package/dist/types/src/components/Focus/index.d.ts.map +1 -1
  33. package/dist/types/src/components/Mosaic/Container.d.ts +42 -0
  34. package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -0
  35. package/dist/types/src/components/Mosaic/Mosaic.d.ts +35 -118
  36. package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
  37. package/dist/types/src/components/Mosaic/Placeholder.d.ts +21 -0
  38. package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -0
  39. package/dist/types/src/components/Mosaic/Root.d.ts +36 -0
  40. package/dist/types/src/components/Mosaic/Root.d.ts.map +1 -0
  41. package/dist/types/src/components/Mosaic/Stack.d.ts +23 -0
  42. package/dist/types/src/components/Mosaic/Stack.d.ts.map +1 -0
  43. package/dist/types/src/components/{Stack → Mosaic}/Stack.stories.d.ts +2 -2
  44. package/dist/types/src/components/Mosaic/Stack.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/Mosaic/Tile.d.ts +48 -0
  46. package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -0
  47. package/dist/types/src/components/Mosaic/styles.d.ts.map +1 -1
  48. package/dist/types/src/components/Mosaic/types.d.ts +9 -2
  49. package/dist/types/src/components/Mosaic/types.d.ts.map +1 -1
  50. package/dist/types/src/components/index.d.ts +1 -4
  51. package/dist/types/src/components/index.d.ts.map +1 -1
  52. package/dist/types/src/hooks/index.d.ts +1 -0
  53. package/dist/types/src/hooks/index.d.ts.map +1 -1
  54. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts +15 -0
  55. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -0
  56. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +1 -1
  57. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useVisibleItems.d.ts +2 -2
  59. package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
  60. package/dist/types/src/playwright/board-manager.d.ts +45 -0
  61. package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
  62. package/dist/types/src/playwright/index.d.ts +9 -0
  63. package/dist/types/src/playwright/index.d.ts.map +1 -0
  64. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  65. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  66. package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
  67. package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
  68. package/dist/types/src/testing/CardContainer.d.ts +5 -4
  69. package/dist/types/src/testing/CardContainer.d.ts.map +1 -1
  70. package/dist/types/src/testing/DefaultStackTile.d.ts +2 -2
  71. package/dist/types/src/testing/DefaultStackTile.d.ts.map +1 -1
  72. package/dist/types/src/testing/index.d.ts +1 -1
  73. package/dist/types/src/testing/index.d.ts.map +1 -1
  74. package/dist/types/src/testing/types.d.ts +21 -0
  75. package/dist/types/src/testing/types.d.ts.map +1 -0
  76. package/dist/types/src/translations.d.ts +5 -1
  77. package/dist/types/src/translations.d.ts.map +1 -1
  78. package/dist/types/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +43 -28
  80. package/src/components/Board/Board.stories.tsx +273 -0
  81. package/src/components/Board/Board.tsx +191 -0
  82. package/src/components/Board/Column.tsx +300 -0
  83. package/src/components/Board/Item.tsx +116 -0
  84. package/src/components/{Layout → Board}/index.ts +1 -1
  85. package/src/components/Focus/Focus.tsx +26 -17
  86. package/src/components/Focus/index.ts +0 -2
  87. package/src/components/Mosaic/Container.tsx +289 -0
  88. package/src/components/Mosaic/Mosaic.tsx +35 -913
  89. package/src/components/Mosaic/Placeholder.tsx +133 -0
  90. package/src/components/Mosaic/Root.tsx +292 -0
  91. package/src/components/{Stack → Mosaic}/Stack.stories.tsx +30 -26
  92. package/src/components/Mosaic/Stack.tsx +246 -0
  93. package/src/components/Mosaic/Tile.tsx +242 -0
  94. package/src/components/Mosaic/extension.ts +1 -1
  95. package/src/components/Mosaic/styles.ts +9 -8
  96. package/src/components/Mosaic/types.ts +15 -3
  97. package/src/components/index.ts +1 -4
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/useContainerDebug.tsx +4 -2
  100. package/src/hooks/useDefaultColumnEventHandler.ts +35 -0
  101. package/src/hooks/useEventHandlerAdapter.ts +15 -6
  102. package/src/hooks/useVisibleItems.ts +7 -2
  103. package/src/playwright/board-manager.ts +154 -0
  104. package/src/playwright/index.ts +13 -0
  105. package/src/playwright/playwright.config.ts +17 -0
  106. package/src/playwright/smoke.spec.ts +138 -0
  107. package/src/testing/CardContainer.tsx +30 -18
  108. package/src/testing/DefaultStackTile.tsx +27 -16
  109. package/src/testing/index.ts +1 -1
  110. package/src/testing/types.ts +38 -0
  111. package/src/translations.ts +5 -1
  112. package/dist/lib/browser/chunk-UMBPEG4F.mjs +0 -1637
  113. package/dist/lib/browser/chunk-UMBPEG4F.mjs.map +0 -7
  114. package/dist/lib/browser/index.css +0 -13
  115. package/dist/lib/browser/index.css.map +0 -7
  116. package/dist/lib/browser/testing/index.css +0 -13
  117. package/dist/lib/browser/testing/index.css.map +0 -7
  118. package/dist/lib/node-esm/chunk-X6GV7SMK.mjs +0 -1639
  119. package/dist/lib/node-esm/chunk-X6GV7SMK.mjs.map +0 -7
  120. package/dist/lib/node-esm/index.css +0 -13
  121. package/dist/lib/node-esm/index.css.map +0 -7
  122. package/dist/lib/node-esm/testing/index.css +0 -13
  123. package/dist/lib/node-esm/testing/index.css.map +0 -7
  124. package/dist/types/src/components/Card/Card.d.ts +0 -121
  125. package/dist/types/src/components/Card/Card.d.ts.map +0 -1
  126. package/dist/types/src/components/Card/Card.stories.d.ts +0 -21
  127. package/dist/types/src/components/Card/Card.stories.d.ts.map +0 -1
  128. package/dist/types/src/components/Card/CardDragPreview.d.ts +0 -6
  129. package/dist/types/src/components/Card/CardDragPreview.d.ts.map +0 -1
  130. package/dist/types/src/components/Card/index.d.ts +0 -4
  131. package/dist/types/src/components/Card/index.d.ts.map +0 -1
  132. package/dist/types/src/components/Card/styles.d.ts +0 -13
  133. package/dist/types/src/components/Card/styles.d.ts.map +0 -1
  134. package/dist/types/src/components/Focus/styles.d.ts +0 -6
  135. package/dist/types/src/components/Focus/styles.d.ts.map +0 -1
  136. package/dist/types/src/components/Image/Image.d.ts +0 -14
  137. package/dist/types/src/components/Image/Image.d.ts.map +0 -1
  138. package/dist/types/src/components/Image/Image.stories.d.ts +0 -33
  139. package/dist/types/src/components/Image/Image.stories.d.ts.map +0 -1
  140. package/dist/types/src/components/Image/index.d.ts +0 -2
  141. package/dist/types/src/components/Image/index.d.ts.map +0 -1
  142. package/dist/types/src/components/Layout/Layout.d.ts +0 -33
  143. package/dist/types/src/components/Layout/Layout.d.ts.map +0 -1
  144. package/dist/types/src/components/Layout/Layout.stories.d.ts +0 -10
  145. package/dist/types/src/components/Layout/Layout.stories.d.ts.map +0 -1
  146. package/dist/types/src/components/Layout/index.d.ts +0 -2
  147. package/dist/types/src/components/Layout/index.d.ts.map +0 -1
  148. package/dist/types/src/components/Mosaic/Mosaic.stories.d.ts.map +0 -1
  149. package/dist/types/src/components/Scrollable/Scrollable.d.ts +0 -24
  150. package/dist/types/src/components/Scrollable/Scrollable.d.ts.map +0 -1
  151. package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts +0 -10
  152. package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts.map +0 -1
  153. package/dist/types/src/components/Scrollable/index.d.ts +0 -2
  154. package/dist/types/src/components/Scrollable/index.d.ts.map +0 -1
  155. package/dist/types/src/components/Stack/Stack.d.ts +0 -22
  156. package/dist/types/src/components/Stack/Stack.d.ts.map +0 -1
  157. package/dist/types/src/components/Stack/Stack.stories.d.ts.map +0 -1
  158. package/dist/types/src/components/Stack/index.d.ts +0 -2
  159. package/dist/types/src/components/Stack/index.d.ts.map +0 -1
  160. package/dist/types/src/testing/Board.d.ts +0 -45
  161. package/dist/types/src/testing/Board.d.ts.map +0 -1
  162. package/dist/types/src/testing/Slot.stories.d.ts +0 -14
  163. package/dist/types/src/testing/Slot.stories.d.ts.map +0 -1
  164. package/dist/types/src/testing/Virtualizer.stories.d.ts +0 -12
  165. package/dist/types/src/testing/Virtualizer.stories.d.ts.map +0 -1
  166. package/src/components/Card/Card.stories.tsx +0 -148
  167. package/src/components/Card/Card.tsx +0 -458
  168. package/src/components/Card/CardDragPreview.tsx +0 -23
  169. package/src/components/Card/index.ts +0 -8
  170. package/src/components/Card/styles.ts +0 -23
  171. package/src/components/Focus/styles.ts +0 -24
  172. package/src/components/Image/Image.stories.tsx +0 -86
  173. package/src/components/Image/Image.tsx +0 -223
  174. package/src/components/Image/index.ts +0 -5
  175. package/src/components/Layout/Layout.stories.tsx +0 -59
  176. package/src/components/Layout/Layout.tsx +0 -108
  177. package/src/components/Mosaic/Mosaic.stories.tsx +0 -113
  178. package/src/components/Scrollable/Scrollable.stories.tsx +0 -59
  179. package/src/components/Scrollable/Scrollable.tsx +0 -88
  180. package/src/components/Scrollable/index.ts +0 -5
  181. package/src/components/Scrollable/scrollable.css +0 -20
  182. package/src/components/Stack/Stack.tsx +0 -210
  183. package/src/components/Stack/index.ts +0 -5
  184. package/src/testing/Board.tsx +0 -278
  185. package/src/testing/Slot.stories.tsx +0 -101
  186. package/src/testing/Virtualizer.stories.tsx +0 -133
@@ -1,223 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import React, { type SyntheticEvent, useCallback, useRef, useState } from 'react';
6
-
7
- import { type ThemedClassName } from '@dxos/react-ui';
8
- import { mx } from '@dxos/ui-theme';
9
-
10
- const cache = new Map<string, string>();
11
-
12
- export type ImageProps = ThemedClassName<
13
- {
14
- src: string;
15
- alt?: string;
16
- crossOrigin?: 'anonymous' | 'use-credentials' | '';
17
- } & ColorOptions
18
- >;
19
-
20
- // TODO(burdon): Option for neutral background color.
21
- export const Image = ({
22
- classNames,
23
- src,
24
- alt = '',
25
- crossOrigin = 'anonymous',
26
- sampleSize = 64,
27
- contrast = 0.9,
28
- }: ImageProps) => {
29
- const [crossOriginState, setCrossOriginState] = useState<ImageProps['crossOrigin']>(crossOrigin);
30
- const [dominantColor, setDominantColor] = useState<string | undefined>(undefined);
31
- const [imageLoaded, setImageLoaded] = useState<boolean>(false);
32
- const canvasRef = useRef<HTMLCanvasElement>(null);
33
-
34
- // CORS not supported by server.
35
- const handleImageError = (): void => {
36
- setCrossOriginState(undefined);
37
- };
38
-
39
- const handleImageLoad = useCallback(
40
- ({ target }: SyntheticEvent<HTMLImageElement>): void => {
41
- const rgb = cache.get(src);
42
- if (rgb) {
43
- setDominantColor(rgb);
44
- setImageLoaded(true);
45
- return;
46
- }
47
-
48
- const img = target as HTMLImageElement;
49
- if (!canvasRef.current) {
50
- return;
51
- }
52
-
53
- try {
54
- const color = extractDominantColor(canvasRef.current, img, { sampleSize, contrast });
55
- if (color) {
56
- const rgb = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
57
- cache.set(src, rgb);
58
- setDominantColor(rgb);
59
- }
60
- } catch {
61
- setCrossOriginState(undefined);
62
- }
63
-
64
- setImageLoaded(true);
65
- },
66
- [sampleSize, contrast, src],
67
- );
68
-
69
- return (
70
- <div
71
- className={mx(`relative flex is-full justify-center overflow-hidden transition-all duration-700`, classNames)}
72
- style={{
73
- backgroundColor: dominantColor,
74
- }}
75
- >
76
- {/* Hidden canvas for color extraction. */}
77
- <canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden='true' />
78
-
79
- {/* Background gradient overlay for smooth transition. */}
80
- <div
81
- className='absolute inset-0 pointer-events-none'
82
- style={{
83
- background: dominantColor
84
- ? `radial-gradient(circle at center, transparent 30%, ${dominantColor} 100%)`
85
- : undefined,
86
- transition: 'opacity 0.7s ease-in-out',
87
- opacity: 0.5,
88
- }}
89
- />
90
-
91
- <img
92
- src={src}
93
- alt={alt}
94
- crossOrigin={crossOriginState}
95
- onError={handleImageError}
96
- onLoad={handleImageLoad}
97
- className={mx('z-10 object-contain transition-opacity duration-500', classNames)}
98
- style={{
99
- opacity: imageLoaded ? 1 : 0,
100
- }}
101
- />
102
- </div>
103
- );
104
- };
105
-
106
- type ColorOptions = {
107
- sampleSize?: number;
108
- contrast?: number;
109
- };
110
-
111
- /**
112
- * Get dominant color from image (esp. from corners).
113
- */
114
- const extractDominantColor = (
115
- canvas: HTMLCanvasElement,
116
- img: HTMLImageElement,
117
- { sampleSize = 64, contrast = 0.95 }: ColorOptions,
118
- ): [number, number, number] | null => {
119
- const ctx = canvas.getContext('2d');
120
- if (!ctx) {
121
- return null;
122
- }
123
-
124
- // Draw the image scaled down.
125
- canvas.width = sampleSize;
126
- canvas.height = sampleSize;
127
- ctx.drawImage(img, 0, 0, sampleSize, sampleSize);
128
-
129
- // Get image data.
130
- const imageData = ctx.getImageData(0, 0, sampleSize, sampleSize);
131
- const pixels = imageData.data;
132
-
133
- // Check for transparent background.
134
- if (isTransparent(pixels, sampleSize)) {
135
- return null;
136
- }
137
-
138
- let r = 0;
139
- let g = 0;
140
- let b = 0;
141
- let totalWeight = 0;
142
-
143
- // Define corner sampling areas (e.g., 25% of each dimension from each corner).
144
- const cornerSize = Math.floor(sampleSize * 0.125);
145
-
146
- // Sample only pixels in corner areas.
147
- for (let y = 0; y < sampleSize; y++) {
148
- for (let x = 0; x < sampleSize; x++) {
149
- // Check if pixel is in any corner area.
150
- const isInTopLeft = x < cornerSize && y < cornerSize;
151
- const isInTopRight = x >= sampleSize - cornerSize && y < cornerSize;
152
- const isInBottomLeft = x < cornerSize && y >= sampleSize - cornerSize;
153
- const isInBottomRight = x >= sampleSize - cornerSize && y >= sampleSize - cornerSize;
154
- if (!isInTopLeft && !isInTopRight && !isInBottomLeft && !isInBottomRight) {
155
- continue; // Skip pixels not in corner areas.
156
- }
157
-
158
- const i = (y * sampleSize + x) * 4;
159
- const red = pixels[i];
160
- const green = pixels[i + 1];
161
- const blue = pixels[i + 2];
162
- const alpha = pixels[i + 3];
163
-
164
- // Skip transparent pixels.
165
- if (alpha === 0) continue;
166
-
167
- // Calculate saturation to weight vibrant colors more.
168
- const max = Math.max(red, green, blue);
169
- const min = Math.min(red, green, blue);
170
- const saturation = max === 0 ? 0 : (max - min) / max;
171
- const weight = 1 + saturation * 2;
172
-
173
- r += red * weight;
174
- g += green * weight;
175
- b += blue * weight;
176
- totalWeight += weight;
177
- }
178
- }
179
-
180
- if (totalWeight > 0) {
181
- // Slightly darken the color for better contrast.
182
- r = Math.round(Math.round(r / totalWeight) * contrast);
183
- g = Math.round(Math.round(g / totalWeight) * contrast);
184
- b = Math.round(Math.round(b / totalWeight) * contrast);
185
- return [r, g, b];
186
- }
187
-
188
- return null;
189
- };
190
-
191
- /**
192
- * Detects if an image has a transparent background by examining edge pixels.
193
- * @param pixels - Image pixel data from canvas
194
- * @param sampleSize - Size of the sampled image
195
- * @param threshold - Percentage threshold for considering background transparent (default: 0.5)
196
- * @returns True if the image has a transparent background
197
- */
198
- const isTransparent = (pixels: Uint8ClampedArray, sampleSize: number, threshold: number = 0.5): boolean => {
199
- let edgeTransparentPixels = 0;
200
- const edgePixels = sampleSize * 4 - 4; // Perimeter minus corners counted twice.
201
-
202
- for (let x = 0; x < sampleSize; x++) {
203
- // Top edge.
204
- const topIndex = x * 4;
205
- if (pixels[topIndex + 3] === 0) edgeTransparentPixels++;
206
-
207
- // Bottom edge.
208
- const bottomIndex = ((sampleSize - 1) * sampleSize + x) * 4;
209
- if (pixels[bottomIndex + 3] === 0) edgeTransparentPixels++;
210
- }
211
-
212
- for (let y = 1; y < sampleSize - 1; y++) {
213
- // Left edge.
214
- const leftIndex = y * sampleSize * 4;
215
- if (pixels[leftIndex + 3] === 0) edgeTransparentPixels++;
216
-
217
- // Right edge.
218
- const rightIndex = (y * sampleSize + sampleSize - 1) * 4;
219
- if (pixels[rightIndex + 3] === 0) edgeTransparentPixels++;
220
- }
221
-
222
- return edgeTransparentPixels / edgePixels > threshold;
223
- };
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- export * from './Image';
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React from 'react';
7
-
8
- import { Input, Toolbar } from '@dxos/react-ui';
9
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
-
11
- import { Scrollable } from '../Scrollable';
12
-
13
- import { Layout } from './Layout';
14
-
15
- const DefaultStory = ({ count }: { count: number }) => {
16
- return (
17
- <Layout.Main toolbar statusbar>
18
- <Toolbar.Root classNames='gap-2'>
19
- <Toolbar.IconButton icon='ph--plus--regular' variant='primary' label='Add' />
20
- <Input.Root>
21
- <Input.TextInput placeholder='Search' />
22
- </Input.Root>
23
- <Toolbar.IconButton icon='ph--dots-three-vertical--regular' iconOnly label='Menu' />
24
- </Toolbar.Root>
25
- <Scrollable axis='vertical'>
26
- <Layout.Flex role='list' column classNames='pli-2 plb-1 gap-1'>
27
- {Array.from({ length: count }).map((_, index) => (
28
- <div key={index} role='listitem' className='pli-2 plb-1 border border-separator'>
29
- {index}
30
- </div>
31
- ))}
32
- </Layout.Flex>
33
- </Scrollable>
34
- <Toolbar.Root classNames='justify-between'>
35
- <Toolbar.IconButton variant='ghost' icon='ph--house--regular' iconOnly label='Add' size={4} />
36
- <Toolbar.IconButton variant='ghost' icon='ph--alarm--regular' iconOnly label='Status' size={4} />
37
- </Toolbar.Root>
38
- </Layout.Main>
39
- );
40
- };
41
-
42
- const meta: Meta<typeof DefaultStory> = {
43
- title: 'ui/react-ui-mosaic/Layout',
44
- component: DefaultStory,
45
- decorators: [withTheme, withLayout({ layout: 'column' })],
46
- parameters: {
47
- layout: 'fullscreen',
48
- },
49
- };
50
-
51
- export default meta;
52
-
53
- type Story = StoryObj<typeof meta>;
54
-
55
- export const Default: Story = {
56
- args: {
57
- count: 100,
58
- },
59
- };
@@ -1,108 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import React, { type HTMLAttributes, type PropsWithChildren, forwardRef, useMemo } from 'react';
6
-
7
- import { type ThemedClassName } from '@dxos/react-ui';
8
- import { mx } from '@dxos/ui-theme';
9
-
10
- //
11
- // Main
12
- //
13
-
14
- type MainProps = ThemedClassName<
15
- PropsWithChildren<{
16
- role?: string;
17
- toolbar?: boolean;
18
- statusbar?: boolean;
19
- }>
20
- >;
21
-
22
- // TODO(burdon): Borders.
23
- const Main = forwardRef<HTMLDivElement, MainProps>(
24
- ({ classNames, children, role, toolbar, statusbar }, forwardedRef) => {
25
- const style = useMemo(
26
- () => ({
27
- gridTemplateRows: [toolbar && 'var(--toolbar-size)', '1fr', statusbar && 'var(--statusbar-size)']
28
- .filter(Boolean)
29
- .join(' '),
30
- }),
31
- [toolbar, statusbar],
32
- );
33
-
34
- return (
35
- <div
36
- ref={forwardedRef}
37
- role={role ?? 'none'}
38
- style={style}
39
- className={mx(
40
- 'bs-full is-full grid grid-cols-[100%] overflow-hidden',
41
- toolbar && '[&>.dx-toolbar]:relative [&>.dx-toolbar]:border-be [&>.dx-toolbar]:border-subduedSeparator',
42
- classNames,
43
- )}
44
- >
45
- {children}
46
- </div>
47
- );
48
- },
49
- );
50
-
51
- //
52
- // Container
53
- //
54
-
55
- type ContainerProps = ThemedClassName<
56
- PropsWithChildren<{
57
- role?: string;
58
- scrollable?: boolean;
59
- }>
60
- >;
61
-
62
- const Container = forwardRef<HTMLDivElement, ContainerProps>(
63
- ({ classNames, children, role, scrollable }, forwardedRef) => {
64
- return (
65
- <div
66
- ref={forwardedRef}
67
- role={role ?? 'none'}
68
- className={mx('grid bs-full', scrollable ? 'overflow-y-auto' : 'overflow-hidden', classNames)}
69
- >
70
- {children}
71
- </div>
72
- );
73
- },
74
- );
75
-
76
- //
77
- // Flex
78
- //
79
-
80
- type FlexProps = ThemedClassName<
81
- HTMLAttributes<HTMLDivElement> & {
82
- column?: boolean;
83
- grow?: boolean;
84
- }
85
- >;
86
-
87
- const Flex = ({ children, classNames, role, column, grow }: FlexProps) => {
88
- return (
89
- <div
90
- role={role ?? 'none'}
91
- className={mx('flex', column && 'flex-col', grow && 'flex-1 overflow-hidden', classNames)}
92
- >
93
- {children}
94
- </div>
95
- );
96
- };
97
-
98
- //
99
- // Layout
100
- //
101
-
102
- export const Layout = {
103
- Main,
104
- Container,
105
- Flex,
106
- };
107
-
108
- export type { MainProps as LayoutMainProps, ContainerProps as LayoutContainerProps, FlexProps as LayoutFlexProps };
@@ -1,113 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React from 'react';
7
-
8
- import { type Database, Filter, Obj, Ref } from '@dxos/echo';
9
- import { faker } from '@dxos/random';
10
- import { useQuery } from '@dxos/react-client/echo';
11
- import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
12
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
13
- import { mx } from '@dxos/ui-theme';
14
-
15
- import { Board, TestColumn, TestItem } from '../../testing';
16
- import { Focus } from '../Focus';
17
-
18
- import { Mosaic } from './Mosaic';
19
-
20
- faker.seed(999);
21
-
22
- const createTestData = (db: Database.Database, columnCount: number) => {
23
- Array.from({ length: columnCount }).forEach((_, i) => {
24
- db.add(
25
- Obj.make(TestColumn, {
26
- items: Array.from({ length: faker.number.int({ min: 0, max: 20 }) }).map((_, j) => {
27
- const item = db.add(
28
- Obj.make(TestItem, {
29
- name: faker.lorem.sentence(3),
30
- description: faker.lorem.paragraph(1),
31
- label: `${String.fromCharCode(65 + i)}-${j}`,
32
- }),
33
- );
34
-
35
- return Ref.make(item);
36
- }),
37
- }),
38
- );
39
- });
40
- };
41
-
42
- type StoryProps = {
43
- columns?: number;
44
- debug?: boolean;
45
- };
46
-
47
- const DefaultStory = ({ debug = false }: StoryProps) => {
48
- const { space } = useClientStory();
49
- const columns = useQuery(space?.db, Filter.type(TestColumn));
50
-
51
- if (columns.length === 0) {
52
- return <></>;
53
- }
54
-
55
- return (
56
- <Mosaic.Root asChild debug={debug}>
57
- <div className={mx('grid overflow-hidden', debug && 'grid-cols-[1fr_20rem] gap-2')}>
58
- <Board.Root id='board' columns={columns} debug={debug} />
59
- {debug && (
60
- <Focus.Group classNames='flex flex-col gap-2 overflow-hidden'>
61
- <Board.Debug classNames='p-2' />
62
- </Focus.Group>
63
- )}
64
- </div>
65
- </Mosaic.Root>
66
- );
67
- };
68
-
69
- const meta = {
70
- title: 'ui/react-ui-mosaic/Mosaic',
71
- render: DefaultStory,
72
- decorators: [
73
- withTheme,
74
- withLayout({ layout: 'fullscreen' }),
75
- withClientProvider({
76
- types: [TestColumn, TestItem],
77
- createIdentity: true,
78
- createSpace: true,
79
- onCreateSpace: ({ space }, context) => {
80
- const columnCount = (context.args as StoryProps).columns ?? 4;
81
- createTestData(space.db, columnCount);
82
- },
83
- }),
84
- ],
85
- parameters: {
86
- layout: 'fullscreen',
87
- },
88
- argTypes: {
89
- columns: {
90
- control: 'number',
91
- },
92
- debug: {
93
- control: 'boolean',
94
- },
95
- },
96
- } satisfies Meta<typeof DefaultStory>;
97
-
98
- export default meta;
99
-
100
- type Story = StoryObj<typeof meta>;
101
-
102
- export const Default: Story = {
103
- args: {
104
- columns: 5,
105
- },
106
- };
107
-
108
- export const Debug: Story = {
109
- args: {
110
- debug: true,
111
- columns: 2,
112
- },
113
- };
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import React from 'react';
7
-
8
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
- import { mx } from '@dxos/ui-theme';
10
-
11
- import { Scrollable, type ScrollableProps } from './Scrollable';
12
-
13
- const DefaultStory = (props: ScrollableProps) => {
14
- return (
15
- <Scrollable {...props} classNames='bs-full is-full'>
16
- <div role='none' className={mx('bg-cubes', props.axis === 'vertical' ? 'bs-[200rem]' : 'bs-full is-[200rem]')} />
17
- </Scrollable>
18
- );
19
- };
20
-
21
- const meta: Meta<typeof Scrollable> = {
22
- title: 'ui/react-ui-mosaic/Scrollable',
23
- component: Scrollable,
24
- render: (args) => <DefaultStory {...args} />,
25
- decorators: [withTheme, withLayout({ layout: 'fullscreen' })],
26
- parameters: {
27
- layout: 'fullscreen',
28
- },
29
- };
30
-
31
- export default meta;
32
-
33
- type Story = StoryObj<typeof meta>;
34
-
35
- export const Vertical: Story = {
36
- args: {
37
- axis: 'vertical',
38
- },
39
- };
40
-
41
- export const Horizontal: Story = {
42
- args: {
43
- axis: 'horizontal',
44
- },
45
- };
46
-
47
- export const VerticalPadding: Story = {
48
- args: {
49
- axis: 'vertical',
50
- padding: true,
51
- },
52
- };
53
-
54
- export const HorizontalPadding: Story = {
55
- args: {
56
- axis: 'horizontal',
57
- padding: true,
58
- },
59
- };
@@ -1,88 +0,0 @@
1
- //
2
- // Copyright 2026 DXOS.org
3
- //
4
-
5
- import { type EventListeners } from 'overlayscrollbars';
6
- import {
7
- OverlayScrollbarsComponent,
8
- type OverlayScrollbarsComponentProps,
9
- type OverlayScrollbarsComponentRef,
10
- } from 'overlayscrollbars-react';
11
- import React, { type RefCallback, forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
12
-
13
- import { type ThemedClassName } from '@dxos/react-ui';
14
- import { mx } from '@dxos/ui-theme';
15
-
16
- import { type Axis } from '../Mosaic';
17
-
18
- import 'overlayscrollbars/styles/overlayscrollbars.css';
19
- import './scrollable.css';
20
-
21
- const defaultOptions: ScrollableProps['options'] = {
22
- scrollbars: {
23
- autoHide: 'leave',
24
- autoHideDelay: 1_000,
25
- autoHideSuspend: true,
26
- },
27
- };
28
-
29
- export type ScrollableProps = ThemedClassName<Omit<OverlayScrollbarsComponentProps, 'ref'>> & {
30
- axis?: Axis;
31
- padding?: boolean;
32
- onScroll?: (event: Event) => void;
33
- viewportRef?: RefCallback<HTMLElement | null>;
34
- };
35
-
36
- /**
37
- * https://www.npmjs.com/package/overlayscrollbars-react
38
- */
39
- export const Scrollable = forwardRef<HTMLDivElement, ScrollableProps>(
40
- (
41
- { classNames, axis = 'vertical', padding, options: optionsProp = defaultOptions, onScroll, viewportRef, ...props },
42
- forwardedRef,
43
- ) => {
44
- const osRef = useRef<OverlayScrollbarsComponentRef>(null);
45
- const options = useMemo(() => {
46
- const options = { ...optionsProp };
47
- if (axis) {
48
- options.overflow = {
49
- x: axis === 'horizontal' ? 'scroll' : 'hidden',
50
- y: axis === 'vertical' ? 'scroll' : 'hidden',
51
- };
52
- }
53
-
54
- return options;
55
- }, [axis, optionsProp]);
56
-
57
- useImperativeHandle(forwardedRef, () => osRef.current?.getElement() as HTMLDivElement, [osRef]);
58
-
59
- useEffect(() => {
60
- const instance = osRef.current?.osInstance();
61
- if (viewportRef) {
62
- viewportRef(instance?.elements().viewport ?? null);
63
- }
64
- }, [osRef, viewportRef]);
65
-
66
- const events = useMemo<EventListeners | null>(() => {
67
- if (!onScroll) {
68
- return null;
69
- }
70
-
71
- return {
72
- scroll: (_, event: Event) => {
73
- onScroll(event);
74
- },
75
- } satisfies EventListeners;
76
- }, [onScroll]);
77
-
78
- return (
79
- <OverlayScrollbarsComponent
80
- {...props}
81
- className={mx(padding && (axis === 'vertical' ? 'pli-3' : 'pbe-3'), classNames)}
82
- options={options}
83
- events={events}
84
- ref={osRef}
85
- />
86
- );
87
- },
88
- );
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- export * from './Scrollable';
@@ -1,20 +0,0 @@
1
- /**
2
- * Override OverlayScrollbars theme variables.
3
- * This file should be imported after 'overlayscrollbars/styles/overlayscrollbars.css'
4
- * to ensure proper CSS cascade order.
5
- *
6
- * https://www.npmjs.com/package/overlayscrollbars
7
- * https://github.com/KingSora/OverlayScrollbars/blob/master/packages/overlayscrollbars/src/styles/scrollbars.scss
8
- */
9
- /** TODO(burdon): Tailwind. */
10
- .os-theme-dark {
11
- --os-size: 6px;
12
- --os-padding-perpendicular: 0px;
13
- --os-padding-axis: 0px;
14
- --os-track-border-radius: 0px;
15
- --os-handle-interactive-area-offset: 0px;
16
- --os-handle-border-radius: 0px;
17
- --os-handle-bg: rgba(128, 128, 128, 0.4);
18
- --os-handle-bg-hover: rgba(128, 128, 128, 0.5);
19
- --os-handle-bg-active: rgba(128, 128, 128, 0.7);
20
- }