@dxos/react-ui-stack 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

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 (88) hide show
  1. package/dist/lib/browser/chunk-T4ZCIFCF.mjs +1448 -0
  2. package/dist/lib/browser/chunk-T4ZCIFCF.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +45 -1158
  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 +61 -0
  7. package/dist/lib/browser/playwright/index.mjs.map +7 -0
  8. package/dist/lib/browser/testing/index.mjs +25 -51
  9. package/dist/lib/browser/testing/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/chunk-G2QYUH52.mjs +1450 -0
  11. package/dist/lib/node-esm/chunk-G2QYUH52.mjs.map +7 -0
  12. package/dist/lib/node-esm/index.mjs +45 -1159
  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 +63 -0
  16. package/dist/lib/node-esm/playwright/index.mjs.map +7 -0
  17. package/dist/lib/node-esm/testing/index.mjs +24 -51
  18. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  19. package/dist/types/src/components/Image/Image.d.ts +14 -0
  20. package/dist/types/src/components/Image/Image.d.ts.map +1 -0
  21. package/dist/types/src/components/Image/Image.stories.d.ts +32 -0
  22. package/dist/types/src/components/Image/Image.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/Image/index.d.ts +2 -0
  24. package/dist/types/src/components/Image/index.d.ts.map +1 -0
  25. package/dist/types/src/components/Stack/Stack.d.ts +10 -2
  26. package/dist/types/src/components/Stack/Stack.d.ts.map +1 -1
  27. package/dist/types/src/components/Stack/Stack.stories.d.ts +12 -3
  28. package/dist/types/src/components/Stack/Stack.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/StackContext.d.ts +2 -1
  30. package/dist/types/src/components/StackContext.d.ts.map +1 -1
  31. package/dist/types/src/components/StackItem/StackItem.d.ts +7 -6
  32. package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -1
  33. package/dist/types/src/components/StackItem/StackItem.stories.d.ts +13 -5
  34. package/dist/types/src/components/StackItem/StackItem.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/StackItem/StackItemContent.d.ts +20 -10
  36. package/dist/types/src/components/StackItem/StackItemContent.d.ts.map +1 -1
  37. package/dist/types/src/components/StackItem/StackItemHeading.d.ts +1 -1
  38. package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -1
  39. package/dist/types/src/components/StackItem/StackItemResizeHandle.d.ts.map +1 -1
  40. package/dist/types/src/components/StackItem/StackItemSigil.d.ts.map +1 -1
  41. package/dist/types/src/components/index.d.ts +2 -1
  42. package/dist/types/src/components/index.d.ts.map +1 -1
  43. package/dist/types/src/exemplars/Card/Card.d.ts +21 -11
  44. package/dist/types/src/exemplars/Card/Card.d.ts.map +1 -1
  45. package/dist/types/src/exemplars/Card/Card.stories.d.ts +12 -4
  46. package/dist/types/src/exemplars/Card/Card.stories.d.ts.map +1 -1
  47. package/dist/types/src/exemplars/Card/fragments.d.ts +3 -2
  48. package/dist/types/src/exemplars/Card/fragments.d.ts.map +1 -1
  49. package/dist/types/src/exemplars/CardStack/CardStack.d.ts +3 -1
  50. package/dist/types/src/exemplars/CardStack/CardStack.d.ts.map +1 -1
  51. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts +9 -3
  52. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useStackDropForElements.d.ts +1 -1
  54. package/dist/types/src/hooks/useStackDropForElements.d.ts.map +1 -1
  55. package/dist/types/src/playwright/index.d.ts +2 -0
  56. package/dist/types/src/playwright/index.d.ts.map +1 -0
  57. package/dist/types/src/playwright/stack-manager.d.ts.map +1 -0
  58. package/dist/types/src/testing/CardContainer.d.ts +6 -0
  59. package/dist/types/src/testing/CardContainer.d.ts.map +1 -0
  60. package/dist/types/src/testing/index.d.ts +1 -1
  61. package/dist/types/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +36 -30
  63. package/src/components/Image/Image.stories.tsx +78 -0
  64. package/src/components/Image/Image.tsx +192 -0
  65. package/src/components/Image/index.ts +5 -0
  66. package/src/components/Stack/Stack.stories.tsx +8 -9
  67. package/src/components/Stack/Stack.tsx +215 -18
  68. package/src/components/StackContext.tsx +2 -1
  69. package/src/components/StackItem/StackItem.stories.tsx +16 -14
  70. package/src/components/StackItem/StackItem.tsx +26 -18
  71. package/src/components/StackItem/StackItemContent.tsx +21 -9
  72. package/src/components/StackItem/StackItemHeading.tsx +4 -8
  73. package/src/components/StackItem/StackItemResizeHandle.tsx +2 -1
  74. package/src/components/StackItem/StackItemSigil.tsx +2 -1
  75. package/src/components/index.ts +2 -1
  76. package/src/exemplars/Card/Card.stories.tsx +29 -43
  77. package/src/exemplars/Card/Card.tsx +52 -15
  78. package/src/exemplars/Card/fragments.ts +3 -2
  79. package/src/exemplars/CardStack/CardStack.stories.tsx +11 -10
  80. package/src/exemplars/CardStack/CardStack.tsx +12 -9
  81. package/src/hooks/useStackDropForElements.ts +1 -1
  82. package/src/playwright/index.ts +5 -0
  83. package/src/playwright/smoke.spec.ts +1 -1
  84. package/src/testing/CardContainer.tsx +37 -0
  85. package/src/testing/index.ts +1 -1
  86. package/dist/types/src/testing/stack-manager.d.ts.map +0 -1
  87. /package/dist/types/src/{testing → playwright}/stack-manager.d.ts +0 -0
  88. /package/src/{testing → playwright}/stack-manager.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-stack",
3
- "version": "0.8.4-main.84f28bd",
3
+ "version": "0.8.4-main.ae835ea",
4
4
  "description": "A stack component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -14,18 +14,23 @@
14
14
  "browser": "./dist/lib/browser/index.mjs",
15
15
  "node": "./dist/lib/node-esm/index.mjs"
16
16
  },
17
+ "./playwright": {
18
+ "types": "./dist/types/src/playwright/index.d.ts",
19
+ "browser": "./dist/lib/browser/playwright/index.mjs",
20
+ "node": "./dist/lib/node-esm/playwright/index.mjs"
21
+ },
17
22
  "./testing": {
18
23
  "types": "./dist/types/src/testing/index.d.ts",
19
24
  "browser": "./dist/lib/browser/testing/index.mjs",
20
- "node": {
21
- "require": "./dist/lib/node/testing/index.cjs",
22
- "default": "./dist/lib/node-esm/testing/index.mjs"
23
- }
25
+ "node": "./dist/lib/node-esm/testing/index.mjs"
24
26
  }
25
27
  },
26
28
  "types": "dist/types/src/index.d.ts",
27
29
  "typesVersions": {
28
30
  "*": {
31
+ "playwright": [
32
+ "dist/types/src/playwright/index.d.ts"
33
+ ],
29
34
  "testing": [
30
35
  "dist/types/src/testing/index.d.ts"
31
36
  ]
@@ -49,35 +54,36 @@
49
54
  "@radix-ui/react-slot": "1.1.2",
50
55
  "@radix-ui/react-use-controllable-state": "1.1.0",
51
56
  "react-resize-detector": "^11.0.1",
52
- "@dxos/echo-schema": "0.8.4-main.84f28bd",
53
- "@dxos/keyboard": "0.8.4-main.84f28bd",
54
- "@dxos/react-ui-attention": "0.8.4-main.84f28bd",
55
- "@dxos/util": "0.8.4-main.84f28bd",
56
- "@dxos/live-object": "0.8.4-main.84f28bd",
57
- "@dxos/react-ui-dnd": "0.8.4-main.84f28bd"
57
+ "@dxos/echo": "0.8.4-main.ae835ea",
58
+ "@dxos/keyboard": "0.8.4-main.ae835ea",
59
+ "@dxos/live-object": "0.8.4-main.ae835ea",
60
+ "@dxos/react-ui-attention": "0.8.4-main.ae835ea",
61
+ "@dxos/storybook-utils": "0.8.4-main.ae835ea",
62
+ "@dxos/util": "0.8.4-main.ae835ea",
63
+ "@dxos/react-ui-dnd": "0.8.4-main.ae835ea"
58
64
  },
59
65
  "devDependencies": {
60
- "@types/react": "~18.2.0",
61
- "@types/react-dom": "~18.2.0",
62
- "react": "~18.2.0",
63
- "react-dom": "~18.2.0",
64
- "vite": "5.4.7",
65
- "@dxos/echo-schema": "0.8.4-main.84f28bd",
66
- "@dxos/client": "0.8.4-main.84f28bd",
67
- "@dxos/app-graph": "0.8.4-main.84f28bd",
68
- "@dxos/random": "0.8.4-main.84f28bd",
69
- "@dxos/react-ui": "0.8.4-main.84f28bd",
70
- "@dxos/react-ui-theme": "0.8.4-main.84f28bd",
71
- "@dxos/storybook-utils": "0.8.4-main.84f28bd",
72
- "@dxos/test-utils": "0.8.4-main.84f28bd"
66
+ "@types/react": "~19.2.2",
67
+ "@types/react-dom": "~19.2.2",
68
+ "react": "~19.2.0",
69
+ "react-dom": "~19.2.0",
70
+ "vite": "7.1.9",
71
+ "@dxos/client": "0.8.4-main.ae835ea",
72
+ "@dxos/echo": "0.8.4-main.ae835ea",
73
+ "@dxos/random": "0.8.4-main.ae835ea",
74
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
75
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
76
+ "@dxos/storybook-utils": "0.8.4-main.ae835ea",
77
+ "@dxos/test-utils": "0.8.4-main.ae835ea",
78
+ "@dxos/app-graph": "0.8.4-main.ae835ea"
73
79
  },
74
80
  "peerDependencies": {
75
- "react": "~18.2.0",
76
- "react-dom": "~18.2.0",
77
- "@dxos/client": "0.8.4-main.84f28bd",
78
- "@dxos/random": "0.8.4-main.84f28bd",
79
- "@dxos/react-ui": "0.8.4-main.84f28bd",
80
- "@dxos/react-ui-theme": "0.8.4-main.84f28bd"
81
+ "react": "^19.0.0",
82
+ "react-dom": "^19.0.0",
83
+ "@dxos/client": "0.8.4-main.ae835ea",
84
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
85
+ "@dxos/random": "0.8.4-main.ae835ea",
86
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea"
81
87
  },
82
88
  "publishConfig": {
83
89
  "access": "public"
@@ -0,0 +1,78 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { faker } from '@dxos/random';
9
+ import { withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { Image } from './Image';
12
+
13
+ const seed = Math.random();
14
+
15
+ faker.seed(seed);
16
+
17
+ const meta = {
18
+ title: 'ui/react-ui-stack/Image',
19
+ component: Image,
20
+ render: (args) => (
21
+ <div className='absolute inset-0 flex place-items-center'>
22
+ <Image {...args} />
23
+ </div>
24
+ ),
25
+ decorators: [withTheme],
26
+ parameters: {
27
+ layout: 'centered',
28
+ },
29
+ } satisfies Meta<typeof Image>;
30
+
31
+ export default meta;
32
+
33
+ type Story = StoryObj<typeof meta>;
34
+
35
+ export const Default: Story = {
36
+ args: {
37
+ src: faker.image.url(),
38
+ },
39
+ };
40
+
41
+ /**
42
+ * Access to image at 'https://dxos.network/dxos-logotype-blue.png'
43
+ * from origin 'http://localhost:9009' has been blocked by CORS policy:
44
+ * No 'Access-Control-Allow-Origin' header is present on the requested resource.
45
+ */
46
+ export const Cors: Story = {
47
+ args: {
48
+ src: 'https://dxos.network/dxos-logotype-blue.png',
49
+ classNames: 'w-[20rem]',
50
+ },
51
+ };
52
+
53
+ export const SVG: Story = {
54
+ args: {
55
+ src: 'https://dxos.network/bg-kube.svg',
56
+ classNames: 'w-[20rem]',
57
+ },
58
+ };
59
+
60
+ export const Many: Story = {
61
+ args: {
62
+ src: 'https://dxos.network/bg-kube.svg',
63
+ },
64
+ render: () => {
65
+ const images = useMemo(
66
+ () => Array.from({ length: 9 }, (_, i) => `https://picsum.photos/seed/${seed + i}/500/500`),
67
+ [],
68
+ );
69
+ console.log(images);
70
+ return (
71
+ <div className='is-[60rem] grid grid-cols-3 grid-rows-3 gap-8'>
72
+ {images.map((src, i) => (
73
+ <Image key={i} src={src} classNames='is-[18rem] bs-[12rem]' />
74
+ ))}
75
+ </div>
76
+ );
77
+ },
78
+ };
@@ -0,0 +1,192 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type SyntheticEvent, useRef, useState } from 'react';
6
+
7
+ import { type ThemedClassName } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/react-ui-theme';
9
+
10
+ export type ImageProps = ThemedClassName<
11
+ {
12
+ src: string;
13
+ alt?: string;
14
+ crossOrigin?: 'anonymous' | 'use-credentials' | '';
15
+ } & ColorOptions
16
+ >;
17
+
18
+ export const Image = ({
19
+ classNames,
20
+ src,
21
+ alt = '',
22
+ crossOrigin = 'anonymous',
23
+ sampleSize = 64,
24
+ contrast = 0.95,
25
+ }: ImageProps) => {
26
+ const [crossOriginState, setCrossOriginState] = useState<ImageProps['crossOrigin']>(crossOrigin);
27
+ const [dominantColor, setDominantColor] = useState<string | undefined>(undefined);
28
+ const [imageLoaded, setImageLoaded] = useState<boolean>(false);
29
+ const canvasRef = useRef<HTMLCanvasElement>(null);
30
+
31
+ // CORS not supported by server.
32
+ const handleImageError = (): void => {
33
+ setCrossOriginState(undefined);
34
+ };
35
+
36
+ const handleImageLoad = ({ target }: SyntheticEvent<HTMLImageElement>): void => {
37
+ const img = target as HTMLImageElement;
38
+ if (!canvasRef.current) {
39
+ return;
40
+ }
41
+
42
+ try {
43
+ const color = extractDominantColor(canvasRef.current, img, { sampleSize, contrast });
44
+ if (color) {
45
+ setDominantColor(`rgb(${color[0]}, ${color[1]}, ${color[2]})`);
46
+ }
47
+ } catch {
48
+ setCrossOriginState(undefined);
49
+ }
50
+
51
+ setImageLoaded(true);
52
+ };
53
+
54
+ return (
55
+ <div
56
+ className={mx(`relative flex is-full justify-center overflow-hidden transition-all duration-700`, classNames)}
57
+ style={{
58
+ backgroundColor: dominantColor,
59
+ }}
60
+ >
61
+ {/* Hidden canvas for color extraction. */}
62
+ <canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden='true' />
63
+
64
+ {/* Background gradient overlay for smooth transition. */}
65
+ <div
66
+ className='absolute inset-0 pointer-events-none'
67
+ style={{
68
+ background: dominantColor
69
+ ? `radial-gradient(circle at center, transparent 30%, ${dominantColor} 100%)`
70
+ : undefined,
71
+ transition: 'opacity 0.7s ease-in-out',
72
+ opacity: 0.5,
73
+ }}
74
+ />
75
+
76
+ <img
77
+ src={src}
78
+ alt={alt}
79
+ crossOrigin={crossOriginState}
80
+ onError={handleImageError}
81
+ onLoad={handleImageLoad}
82
+ className={mx('z-10 object-contain transition-opacity duration-500', classNames)}
83
+ style={{
84
+ opacity: imageLoaded ? 1 : 0,
85
+ }}
86
+ />
87
+ </div>
88
+ );
89
+ };
90
+
91
+ type ColorOptions = {
92
+ sampleSize?: number;
93
+ contrast?: number;
94
+ };
95
+
96
+ // TODO(burdon): Cache?
97
+ const extractDominantColor = (
98
+ canvas: HTMLCanvasElement,
99
+ img: HTMLImageElement,
100
+ { sampleSize = 64, contrast = 0.95 }: ColorOptions,
101
+ ): [number, number, number] | null => {
102
+ const ctx = canvas.getContext('2d');
103
+ if (!ctx) {
104
+ return null;
105
+ }
106
+
107
+ // Draw the image scaled down.
108
+ canvas.width = sampleSize;
109
+ canvas.height = sampleSize;
110
+ ctx.drawImage(img, 0, 0, sampleSize, sampleSize);
111
+
112
+ // Get image data.
113
+ const imageData = ctx.getImageData(0, 0, sampleSize, sampleSize);
114
+ const pixels = imageData.data;
115
+
116
+ // Check for transparent background.
117
+ if (isTransparent(pixels, sampleSize)) {
118
+ return null;
119
+ }
120
+
121
+ let r = 0;
122
+ let g = 0;
123
+ let b = 0;
124
+ let totalWeight = 0;
125
+
126
+ // Calculate average color with more weight to vibrant colors.
127
+ for (let i = 0; i < pixels.length; i += 4) {
128
+ const red = pixels[i];
129
+ const green = pixels[i + 1];
130
+ const blue = pixels[i + 2];
131
+ const alpha = pixels[i + 3];
132
+
133
+ // Skip transparent pixels.
134
+ if (alpha === 0) continue;
135
+
136
+ // Calculate saturation to weight vibrant colors more.
137
+ const max = Math.max(red, green, blue);
138
+ const min = Math.min(red, green, blue);
139
+ // Give more weight to saturated colors.
140
+ const saturation = max === 0 ? 0 : (max - min) / max;
141
+ const weight = 1 + saturation * 2;
142
+
143
+ r += red * weight;
144
+ g += green * weight;
145
+ b += blue * weight;
146
+ totalWeight += weight;
147
+ }
148
+
149
+ if (totalWeight > 0) {
150
+ // Slightly darken the color for better contrast.
151
+ r = Math.round(Math.round(r / totalWeight) * contrast);
152
+ g = Math.round(Math.round(g / totalWeight) * contrast);
153
+ b = Math.round(Math.round(b / totalWeight) * contrast);
154
+ return [r, g, b];
155
+ }
156
+
157
+ return null;
158
+ };
159
+
160
+ /**
161
+ * Detects if an image has a transparent background by examining edge pixels.
162
+ * @param pixels - Image pixel data from canvas
163
+ * @param sampleSize - Size of the sampled image
164
+ * @param threshold - Percentage threshold for considering background transparent (default: 0.5)
165
+ * @returns True if the image has a transparent background
166
+ */
167
+ const isTransparent = (pixels: Uint8ClampedArray, sampleSize: number, threshold: number = 0.5): boolean => {
168
+ let edgeTransparentPixels = 0;
169
+ const edgePixels = sampleSize * 4 - 4; // Perimeter minus corners counted twice.
170
+
171
+ for (let x = 0; x < sampleSize; x++) {
172
+ // Top edge.
173
+ const topIndex = x * 4;
174
+ if (pixels[topIndex + 3] === 0) edgeTransparentPixels++;
175
+
176
+ // Bottom edge.
177
+ const bottomIndex = ((sampleSize - 1) * sampleSize + x) * 4;
178
+ if (pixels[bottomIndex + 3] === 0) edgeTransparentPixels++;
179
+ }
180
+
181
+ for (let y = 1; y < sampleSize - 1; y++) {
182
+ // Left edge.
183
+ const leftIndex = y * sampleSize * 4;
184
+ if (pixels[leftIndex + 3] === 0) edgeTransparentPixels++;
185
+
186
+ // Right edge.
187
+ const rightIndex = (y * sampleSize + sampleSize - 1) * 4;
188
+ if (pixels[rightIndex + 3] === 0) edgeTransparentPixels++;
189
+ }
190
+
191
+ return edgeTransparentPixels / edgePixels > threshold;
192
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './Image';
@@ -2,18 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
8
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
9
- import React, { useState, useCallback } from 'react';
7
+ import React, { useCallback, useState } from 'react';
10
8
 
11
9
  import { faker } from '@dxos/random';
12
- import { withTheme } from '@dxos/storybook-utils';
10
+ import { withTheme } from '@dxos/react-ui/testing';
13
11
 
14
- import { Stack } from './Stack';
15
- import { StackItem } from '../StackItem';
16
12
  import { type StackItemData } from '../defs';
13
+ import { StackItem } from '../StackItem';
14
+
15
+ import { Stack } from './Stack';
17
16
 
18
17
  type StoryStackItem = {
19
18
  id: string;
@@ -129,12 +128,12 @@ const DefaultStory = () => {
129
128
  );
130
129
  };
131
130
 
132
- const meta: Meta<typeof DefaultStory> = {
131
+ const meta = {
133
132
  title: 'ui/react-ui-stack/Stack',
134
133
  component: DefaultStory,
135
- decorators: [withTheme],
136
134
  argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
137
- };
135
+ decorators: [withTheme],
136
+ } satisfies Meta<typeof DefaultStory>;
138
137
 
139
138
  export default meta;
140
139