@dxos/react-ui-stack 0.8.4-main.7ace549 → 0.8.4-main.8360d9e660

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 (109) hide show
  1. package/dist/lib/browser/index.mjs +743 -65
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +744 -65
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Stack/Stack.d.ts +5 -5
  8. package/dist/types/src/components/Stack/Stack.d.ts.map +1 -1
  9. package/dist/types/src/components/StackContext.d.ts +1 -1
  10. package/dist/types/src/components/StackContext.d.ts.map +1 -1
  11. package/dist/types/src/components/StackItem/MenuSignifier.d.ts.map +1 -1
  12. package/dist/types/src/components/StackItem/StackItem.d.ts +7 -11
  13. package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -1
  14. package/dist/types/src/components/StackItem/StackItem.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/StackItem/StackItemContent.d.ts +2 -45
  16. package/dist/types/src/components/StackItem/StackItemContent.d.ts.map +1 -1
  17. package/dist/types/src/components/StackItem/StackItemDragHandle.d.ts.map +1 -1
  18. package/dist/types/src/components/StackItem/StackItemHeading.d.ts +1 -1
  19. package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -1
  20. package/dist/types/src/components/StackItem/StackItemResizeHandle.d.ts +1 -1
  21. package/dist/types/src/components/StackItem/StackItemResizeHandle.d.ts.map +1 -1
  22. package/dist/types/src/components/StackItem/StackItemSigil.d.ts +2 -2
  23. package/dist/types/src/components/StackItem/StackItemSigil.d.ts.map +1 -1
  24. package/dist/types/src/components/index.d.ts +1 -2
  25. package/dist/types/src/components/index.d.ts.map +1 -1
  26. package/dist/types/src/components/{defs.d.ts → types.d.ts} +1 -1
  27. package/dist/types/src/components/types.d.ts.map +1 -0
  28. package/dist/types/src/hooks/useStackDropForElements.d.ts +7 -5
  29. package/dist/types/src/hooks/useStackDropForElements.d.ts.map +1 -1
  30. package/dist/types/src/index.d.ts +0 -1
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/translations.d.ts +2 -2
  33. package/dist/types/src/translations.d.ts.map +1 -1
  34. package/dist/types/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +41 -38
  36. package/src/components/Stack/Stack.stories.tsx +2 -2
  37. package/src/components/Stack/Stack.tsx +13 -11
  38. package/src/components/StackContext.tsx +1 -1
  39. package/src/components/StackItem/MenuSignifier.tsx +2 -9
  40. package/src/components/StackItem/StackItem.stories.tsx +7 -5
  41. package/src/components/StackItem/StackItem.tsx +25 -13
  42. package/src/components/StackItem/StackItemContent.tsx +23 -54
  43. package/src/components/StackItem/StackItemDragHandle.tsx +4 -3
  44. package/src/components/StackItem/StackItemHeading.tsx +14 -13
  45. package/src/components/StackItem/StackItemResizeHandle.tsx +1 -1
  46. package/src/components/StackItem/StackItemSigil.tsx +8 -5
  47. package/src/components/index.ts +2 -2
  48. package/src/hooks/useStackDropForElements.ts +17 -10
  49. package/src/index.ts +0 -3
  50. package/src/playwright/playwright.config.ts +1 -1
  51. package/src/translations.ts +1 -1
  52. package/dist/lib/browser/chunk-3F2KBXLP.mjs +0 -1482
  53. package/dist/lib/browser/chunk-3F2KBXLP.mjs.map +0 -7
  54. package/dist/lib/browser/testing/index.mjs +0 -31
  55. package/dist/lib/browser/testing/index.mjs.map +0 -7
  56. package/dist/lib/node-esm/chunk-SYKFLQGK.mjs +0 -1484
  57. package/dist/lib/node-esm/chunk-SYKFLQGK.mjs.map +0 -7
  58. package/dist/lib/node-esm/testing/index.mjs +0 -32
  59. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  60. package/dist/types/src/components/Image/Image.d.ts +0 -14
  61. package/dist/types/src/components/Image/Image.d.ts.map +0 -1
  62. package/dist/types/src/components/Image/Image.stories.d.ts +0 -33
  63. package/dist/types/src/components/Image/Image.stories.d.ts.map +0 -1
  64. package/dist/types/src/components/Image/index.d.ts +0 -2
  65. package/dist/types/src/components/Image/index.d.ts.map +0 -1
  66. package/dist/types/src/components/defs.d.ts.map +0 -1
  67. package/dist/types/src/components/deprecated/LayoutControls.d.ts +0 -19
  68. package/dist/types/src/components/deprecated/LayoutControls.d.ts.map +0 -1
  69. package/dist/types/src/exemplars/Card/Card.d.ts +0 -69
  70. package/dist/types/src/exemplars/Card/Card.d.ts.map +0 -1
  71. package/dist/types/src/exemplars/Card/Card.stories.d.ts +0 -21
  72. package/dist/types/src/exemplars/Card/Card.stories.d.ts.map +0 -1
  73. package/dist/types/src/exemplars/Card/CardDragPreview.d.ts +0 -6
  74. package/dist/types/src/exemplars/Card/CardDragPreview.d.ts.map +0 -1
  75. package/dist/types/src/exemplars/Card/fragments.d.ts +0 -13
  76. package/dist/types/src/exemplars/Card/fragments.d.ts.map +0 -1
  77. package/dist/types/src/exemplars/Card/index.d.ts +0 -4
  78. package/dist/types/src/exemplars/Card/index.d.ts.map +0 -1
  79. package/dist/types/src/exemplars/CardStack/CardStack.d.ts +0 -42
  80. package/dist/types/src/exemplars/CardStack/CardStack.d.ts.map +0 -1
  81. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts +0 -15
  82. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts.map +0 -1
  83. package/dist/types/src/exemplars/CardStack/CardStackDragPreview.d.ts +0 -9
  84. package/dist/types/src/exemplars/CardStack/CardStackDragPreview.d.ts.map +0 -1
  85. package/dist/types/src/exemplars/CardStack/index.d.ts +0 -3
  86. package/dist/types/src/exemplars/CardStack/index.d.ts.map +0 -1
  87. package/dist/types/src/exemplars/index.d.ts +0 -3
  88. package/dist/types/src/exemplars/index.d.ts.map +0 -1
  89. package/dist/types/src/testing/CardContainer.d.ts +0 -6
  90. package/dist/types/src/testing/CardContainer.d.ts.map +0 -1
  91. package/dist/types/src/testing/index.d.ts +0 -2
  92. package/dist/types/src/testing/index.d.ts.map +0 -1
  93. package/src/components/Image/Image.stories.tsx +0 -84
  94. package/src/components/Image/Image.tsx +0 -222
  95. package/src/components/Image/index.ts +0 -5
  96. package/src/components/deprecated/LayoutControls.tsx +0 -109
  97. package/src/exemplars/Card/Card.stories.tsx +0 -64
  98. package/src/exemplars/Card/Card.tsx +0 -210
  99. package/src/exemplars/Card/CardDragPreview.tsx +0 -22
  100. package/src/exemplars/Card/fragments.ts +0 -24
  101. package/src/exemplars/Card/index.ts +0 -7
  102. package/src/exemplars/CardStack/CardStack.stories.tsx +0 -173
  103. package/src/exemplars/CardStack/CardStack.tsx +0 -139
  104. package/src/exemplars/CardStack/CardStackDragPreview.tsx +0 -61
  105. package/src/exemplars/CardStack/index.ts +0 -6
  106. package/src/exemplars/index.ts +0 -6
  107. package/src/testing/CardContainer.tsx +0 -37
  108. package/src/testing/index.ts +0 -5
  109. /package/src/components/{defs.ts → types.ts} +0 -0
@@ -1,42 +0,0 @@
1
- import React from 'react';
2
- import { type StackProps } from '../../components';
3
- declare const cardStackHeading = "mli-2 order-first bg-transparent rounded-bs-md flex items-center";
4
- declare const cardStackFooter = "plb-2 mli-2 border-bs border-transparent [[data-scroll-separator-end=\"true\"]_&]:border-subduedSeparator";
5
- declare const cardStackContent = "shrink min-bs-0 bg-baseSurface border border-separator rounded-md grid dx-focus-ring-group-x-indicator kanban-drop";
6
- declare const cardStackRoot = "flex flex-col pli-2 plb-2";
7
- declare const cardStackItem = "contain-layout pli-2 plb-1 first-of-type:pbs-0 last-of-type:pbe-0";
8
- export declare const CardStack: {
9
- Root: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "className"> & {
10
- classNames?: import("@dxos/react-ui-types").ClassNameValue;
11
- } & {
12
- asChild?: boolean;
13
- } & React.RefAttributes<HTMLDivElement>>;
14
- Content: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "className"> & {
15
- classNames?: import("@dxos/react-ui-types").ClassNameValue;
16
- } & {
17
- asChild?: boolean;
18
- } & {
19
- footer?: boolean;
20
- } & React.RefAttributes<HTMLDivElement>>;
21
- Stack: React.ForwardRefExoticComponent<Omit<Omit<StackProps, "orientation" | "rail" | "size" | "separatorOnScroll">, "ref"> & React.RefAttributes<HTMLDivElement>>;
22
- Heading: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "className"> & {
23
- classNames?: import("@dxos/react-ui-types").ClassNameValue;
24
- } & {
25
- asChild?: boolean;
26
- } & React.RefAttributes<HTMLDivElement>>;
27
- Footer: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "className"> & {
28
- classNames?: import("@dxos/react-ui-types").ClassNameValue;
29
- } & {
30
- asChild?: boolean;
31
- } & React.RefAttributes<HTMLDivElement>>;
32
- DragHandle: React.ForwardRefExoticComponent<{
33
- toolbarItem?: boolean;
34
- } & React.RefAttributes<HTMLButtonElement>>;
35
- Item: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "className"> & {
36
- classNames?: import("@dxos/react-ui-types").ClassNameValue;
37
- } & {
38
- asChild?: boolean;
39
- } & React.RefAttributes<HTMLDivElement>>;
40
- };
41
- export { cardStackRoot, cardStackFooter, cardStackHeading, cardStackContent, cardStackItem };
42
- //# sourceMappingURL=CardStack.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardStack.d.ts","sourceRoot":"","sources":["../../../../../src/exemplars/CardStack/CardStack.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAoD,MAAM,OAAO,CAAC;AAKzE,OAAO,EAAS,KAAK,UAAU,EAAuC,MAAM,kBAAkB,CAAC;AA+B/F,QAAA,MAAM,gBAAgB,qEAAqE,CAAC;AAgB5F,QAAA,MAAM,eAAe,8GACsF,CAAC;AAgB5G,QAAA,MAAM,gBAAgB,uHACgG,CAAC;AAqBvH,QAAA,MAAM,aAAa,8BAA8B,CAAC;AAgBlD,QAAA,MAAM,aAAa,sEAAsE,CAAC;AAgB1F,eAAO,MAAM,SAAS;;;;kBAnHqE,OAAO;;;;;kBAAP,OAAO;;iBAiEvF,OAAO;;;;;;kBAjEyE,OAAO;;;;;kBAAP,OAAO;;;;;;;;kBAAP,OAAO;;CA2HjG,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC"}
@@ -1,15 +0,0 @@
1
- import { type StoryObj } from '@storybook/react-vite';
2
- import React from 'react';
3
- declare const CardStackStory: () => React.JSX.Element;
4
- declare const meta: {
5
- title: string;
6
- component: () => React.JSX.Element;
7
- decorators: import("@storybook/react").Decorator[];
8
- parameters: {
9
- layout: string;
10
- };
11
- };
12
- export default meta;
13
- type Story = StoryObj<typeof CardStackStory>;
14
- export declare const Default: Story;
15
- //# sourceMappingURL=CardStack.stories.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardStack.stories.d.ts","sourceRoot":"","sources":["../../../../../src/exemplars/CardStack/CardStack.stories.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAgC,MAAM,OAAO,CAAC;AAyBrD,QAAA,MAAM,cAAc,yBA8HnB,CAAC;AAEF,QAAA,MAAM,IAAI;;;;;;;CAO6B,CAAC;AAExC,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7C,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC"}
@@ -1,9 +0,0 @@
1
- import React, { type PropsWithChildren } from 'react';
2
- import { type StackProps } from '../../components';
3
- export declare const CardStackDragPreview: {
4
- Root: ({ children }: PropsWithChildren<{}>) => React.JSX.Element;
5
- Heading: ({ children }: PropsWithChildren<{}>) => React.JSX.Element;
6
- Content: ({ children, itemsCount, }: PropsWithChildren<Pick<StackProps, "itemsCount">>) => React.JSX.Element;
7
- Footer: ({ children }: PropsWithChildren<{}>) => React.JSX.Element;
8
- };
9
- //# sourceMappingURL=CardStackDragPreview.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardStackDragPreview.d.ts","sourceRoot":"","sources":["../../../../../src/exemplars/CardStack/CardStackDragPreview.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAKtD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AA8CnD,eAAO,MAAM,oBAAoB;yBA3Ce,iBAAiB,CAAC,EAAE,CAAC;4BAUlB,iBAAiB,CAAC,EAAE,CAAC;yCAmBrE,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;2BAUF,iBAAiB,CAAC,EAAE,CAAC;CAStE,CAAC"}
@@ -1,3 +0,0 @@
1
- export * from './CardStack';
2
- export * from './CardStackDragPreview';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/exemplars/CardStack/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC"}
@@ -1,3 +0,0 @@
1
- export * from './Card';
2
- export * from './CardStack';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/exemplars/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC"}
@@ -1,6 +0,0 @@
1
- import React, { type PropsWithChildren } from 'react';
2
- export declare const CardContainer: ({ children, icon, role, }: PropsWithChildren<{
3
- icon?: string;
4
- role?: string;
5
- }>) => React.JSX.Element;
6
- //# sourceMappingURL=CardContainer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardContainer.d.ts","sourceRoot":"","sources":["../../../../src/testing/CardContainer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAMtD,eAAO,MAAM,aAAa,GAAI,2BAI3B,iBAAiB,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,sBAsBrD,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './CardContainer';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/index.ts"],"names":[],"mappings":"AAIA,cAAc,iBAAiB,CAAC"}
@@ -1,84 +0,0 @@
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: 'is-[20rem]',
50
- },
51
- };
52
-
53
- export const Corners: Story = {
54
- args: {
55
- src: 'https://media.licdn.com/dms/image/v2/D4D0BAQEY4OiENeMR4A/company-logo_200_200/company-logo_200_200/0/1728648673877/moonfire_logo?e=1763596800&v=beta&t=_Jmhg-vu5uqUR88YiTbDFOC4ShlUbjk63_7-JQpgK9A',
56
- classNames: 'is-[20rem]',
57
- },
58
- };
59
-
60
- export const SVG: Story = {
61
- args: {
62
- src: 'https://dxos.network/bg-kube.svg',
63
- classNames: 'is-[20rem]',
64
- },
65
- };
66
-
67
- export const Many: Story = {
68
- args: {
69
- src: 'https://dxos.network/bg-kube.svg',
70
- },
71
- render: () => {
72
- const images = useMemo(
73
- () => Array.from({ length: 9 }, (_, i) => `https://picsum.photos/seed/${seed + i}/500/500`),
74
- [],
75
- );
76
- return (
77
- <div className='is-[60rem] grid grid-cols-3 grid-rows-3 gap-8'>
78
- {images.map((src, i) => (
79
- <Image key={i} src={src} classNames='is-[18rem] bs-[12rem]' />
80
- ))}
81
- </div>
82
- );
83
- },
84
- };
@@ -1,222 +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/react-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
- export const Image = ({
21
- classNames,
22
- src,
23
- alt = '',
24
- crossOrigin = 'anonymous',
25
- sampleSize = 64,
26
- contrast = 0.9,
27
- }: ImageProps) => {
28
- const [crossOriginState, setCrossOriginState] = useState<ImageProps['crossOrigin']>(crossOrigin);
29
- const [dominantColor, setDominantColor] = useState<string | undefined>(undefined);
30
- const [imageLoaded, setImageLoaded] = useState<boolean>(false);
31
- const canvasRef = useRef<HTMLCanvasElement>(null);
32
-
33
- // CORS not supported by server.
34
- const handleImageError = (): void => {
35
- setCrossOriginState(undefined);
36
- };
37
-
38
- const handleImageLoad = useCallback(
39
- ({ target }: SyntheticEvent<HTMLImageElement>): void => {
40
- const rgb = cache.get(src);
41
- if (rgb) {
42
- setDominantColor(rgb);
43
- setImageLoaded(true);
44
- return;
45
- }
46
-
47
- const img = target as HTMLImageElement;
48
- if (!canvasRef.current) {
49
- return;
50
- }
51
-
52
- try {
53
- const color = extractDominantColor(canvasRef.current, img, { sampleSize, contrast });
54
- if (color) {
55
- const rgb = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
56
- cache.set(src, rgb);
57
- setDominantColor(rgb);
58
- }
59
- } catch {
60
- setCrossOriginState(undefined);
61
- }
62
-
63
- setImageLoaded(true);
64
- },
65
- [sampleSize, contrast, src],
66
- );
67
-
68
- return (
69
- <div
70
- className={mx(`relative flex is-full justify-center overflow-hidden transition-all duration-700`, classNames)}
71
- style={{
72
- backgroundColor: dominantColor,
73
- }}
74
- >
75
- {/* Hidden canvas for color extraction. */}
76
- <canvas ref={canvasRef} style={{ display: 'none' }} aria-hidden='true' />
77
-
78
- {/* Background gradient overlay for smooth transition. */}
79
- <div
80
- className='absolute inset-0 pointer-events-none'
81
- style={{
82
- background: dominantColor
83
- ? `radial-gradient(circle at center, transparent 30%, ${dominantColor} 100%)`
84
- : undefined,
85
- transition: 'opacity 0.7s ease-in-out',
86
- opacity: 0.5,
87
- }}
88
- />
89
-
90
- <img
91
- src={src}
92
- alt={alt}
93
- crossOrigin={crossOriginState}
94
- onError={handleImageError}
95
- onLoad={handleImageLoad}
96
- className={mx('z-10 object-contain transition-opacity duration-500', classNames)}
97
- style={{
98
- opacity: imageLoaded ? 1 : 0,
99
- }}
100
- />
101
- </div>
102
- );
103
- };
104
-
105
- type ColorOptions = {
106
- sampleSize?: number;
107
- contrast?: number;
108
- };
109
-
110
- /**
111
- * Get dominant color from image (esp. from corners).
112
- */
113
- const extractDominantColor = (
114
- canvas: HTMLCanvasElement,
115
- img: HTMLImageElement,
116
- { sampleSize = 64, contrast = 0.95 }: ColorOptions,
117
- ): [number, number, number] | null => {
118
- const ctx = canvas.getContext('2d');
119
- if (!ctx) {
120
- return null;
121
- }
122
-
123
- // Draw the image scaled down.
124
- canvas.width = sampleSize;
125
- canvas.height = sampleSize;
126
- ctx.drawImage(img, 0, 0, sampleSize, sampleSize);
127
-
128
- // Get image data.
129
- const imageData = ctx.getImageData(0, 0, sampleSize, sampleSize);
130
- const pixels = imageData.data;
131
-
132
- // Check for transparent background.
133
- if (isTransparent(pixels, sampleSize)) {
134
- return null;
135
- }
136
-
137
- let r = 0;
138
- let g = 0;
139
- let b = 0;
140
- let totalWeight = 0;
141
-
142
- // Define corner sampling areas (e.g., 25% of each dimension from each corner).
143
- const cornerSize = Math.floor(sampleSize * 0.125);
144
-
145
- // Sample only pixels in corner areas.
146
- for (let y = 0; y < sampleSize; y++) {
147
- for (let x = 0; x < sampleSize; x++) {
148
- // Check if pixel is in any corner area.
149
- const isInTopLeft = x < cornerSize && y < cornerSize;
150
- const isInTopRight = x >= sampleSize - cornerSize && y < cornerSize;
151
- const isInBottomLeft = x < cornerSize && y >= sampleSize - cornerSize;
152
- const isInBottomRight = x >= sampleSize - cornerSize && y >= sampleSize - cornerSize;
153
- if (!isInTopLeft && !isInTopRight && !isInBottomLeft && !isInBottomRight) {
154
- continue; // Skip pixels not in corner areas.
155
- }
156
-
157
- const i = (y * sampleSize + x) * 4;
158
- const red = pixels[i];
159
- const green = pixels[i + 1];
160
- const blue = pixels[i + 2];
161
- const alpha = pixels[i + 3];
162
-
163
- // Skip transparent pixels.
164
- if (alpha === 0) continue;
165
-
166
- // Calculate saturation to weight vibrant colors more.
167
- const max = Math.max(red, green, blue);
168
- const min = Math.min(red, green, blue);
169
- const saturation = max === 0 ? 0 : (max - min) / max;
170
- const weight = 1 + saturation * 2;
171
-
172
- r += red * weight;
173
- g += green * weight;
174
- b += blue * weight;
175
- totalWeight += weight;
176
- }
177
- }
178
-
179
- if (totalWeight > 0) {
180
- // Slightly darken the color for better contrast.
181
- r = Math.round(Math.round(r / totalWeight) * contrast);
182
- g = Math.round(Math.round(g / totalWeight) * contrast);
183
- b = Math.round(Math.round(b / totalWeight) * contrast);
184
- return [r, g, b];
185
- }
186
-
187
- return null;
188
- };
189
-
190
- /**
191
- * Detects if an image has a transparent background by examining edge pixels.
192
- * @param pixels - Image pixel data from canvas
193
- * @param sampleSize - Size of the sampled image
194
- * @param threshold - Percentage threshold for considering background transparent (default: 0.5)
195
- * @returns True if the image has a transparent background
196
- */
197
- const isTransparent = (pixels: Uint8ClampedArray, sampleSize: number, threshold: number = 0.5): boolean => {
198
- let edgeTransparentPixels = 0;
199
- const edgePixels = sampleSize * 4 - 4; // Perimeter minus corners counted twice.
200
-
201
- for (let x = 0; x < sampleSize; x++) {
202
- // Top edge.
203
- const topIndex = x * 4;
204
- if (pixels[topIndex + 3] === 0) edgeTransparentPixels++;
205
-
206
- // Bottom edge.
207
- const bottomIndex = ((sampleSize - 1) * sampleSize + x) * 4;
208
- if (pixels[bottomIndex + 3] === 0) edgeTransparentPixels++;
209
- }
210
-
211
- for (let y = 1; y < sampleSize - 1; y++) {
212
- // Left edge.
213
- const leftIndex = y * sampleSize * 4;
214
- if (pixels[leftIndex + 3] === 0) edgeTransparentPixels++;
215
-
216
- // Right edge.
217
- const rightIndex = (y * sampleSize + sampleSize - 1) * 4;
218
- if (pixels[rightIndex + 3] === 0) edgeTransparentPixels++;
219
- }
220
-
221
- return edgeTransparentPixels / edgePixels > threshold;
222
- };
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- export * from './Image';
@@ -1,109 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import React, { forwardRef } from 'react';
6
-
7
- import { ButtonGroup, type ButtonGroupProps, type ButtonProps, IconButton, useTranslation } from '@dxos/react-ui';
8
-
9
- import { translationKey } from '../../translations';
10
-
11
- export type LayoutControlEvent = 'solo' | 'close' | `${'pin' | 'increment'}-${'start' | 'end'}`;
12
- export type LayoutControlHandler = (event: LayoutControlEvent) => void;
13
-
14
- export type LayoutCapabilities = {
15
- incrementStart?: boolean;
16
- incrementEnd?: boolean;
17
- solo?: boolean;
18
- };
19
-
20
- export type LayoutControlsProps = Omit<ButtonGroupProps, 'onClick'> & {
21
- onClick?: LayoutControlHandler;
22
- variant?: 'hide-disabled' | 'default';
23
- close?: boolean | 'minify-start' | 'minify-end';
24
- capabilities: LayoutCapabilities;
25
- isSolo?: boolean;
26
- pin?: 'start' | 'end' | 'both';
27
- };
28
-
29
- const LayoutControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> & { label: string; icon: string }) => {
30
- return <IconButton iconOnly icon={icon} label={label} tooltipSide='bottom' variant='ghost' {...props} />;
31
- };
32
-
33
- export const LayoutControls = forwardRef<HTMLDivElement, LayoutControlsProps>(
34
- (
35
- { onClick, variant = 'default', capabilities: can, isSolo, pin, close = false, children, ...props },
36
- forwardedRef,
37
- ) => {
38
- const { t } = useTranslation(translationKey);
39
- const buttonClassNames = variant === 'hide-disabled' ? 'disabled:hidden !p-1' : '!p-1';
40
-
41
- return (
42
- <ButtonGroup {...props} ref={forwardedRef}>
43
- {pin && !isSolo && ['both', 'start'].includes(pin) && (
44
- <LayoutControl
45
- label={t('pin start label')}
46
- variant='ghost'
47
- classNames={buttonClassNames}
48
- onClick={() => onClick?.('pin-start')}
49
- icon='ph--caret-line-left--regular'
50
- />
51
- )}
52
-
53
- {can.solo && (
54
- <LayoutControl
55
- label={t('solo layout label')}
56
- classNames={buttonClassNames}
57
- onClick={() => onClick?.('solo')}
58
- icon={isSolo ? 'ph--arrows-in--regular' : 'ph--arrows-out--regular'}
59
- />
60
- )}
61
-
62
- {!isSolo && can.solo && (
63
- <>
64
- <LayoutControl
65
- label={t('increment start label')}
66
- disabled={!can.incrementStart}
67
- classNames={buttonClassNames}
68
- onClick={() => onClick?.('increment-start')}
69
- icon='ph--caret-left--regular'
70
- />
71
- <LayoutControl
72
- label={t('increment end label')}
73
- disabled={!can.incrementEnd}
74
- classNames={buttonClassNames}
75
- onClick={() => onClick?.('increment-end')}
76
- icon='ph--caret-right--regular'
77
- />
78
- </>
79
- )}
80
-
81
- {pin && !isSolo && ['both', 'end'].includes(pin) && (
82
- <LayoutControl
83
- label={t('pin end label')}
84
- classNames={buttonClassNames}
85
- onClick={() => onClick?.('pin-end')}
86
- icon='ph--caret-line-right--regular'
87
- />
88
- )}
89
-
90
- {close && !isSolo && (
91
- <LayoutControl
92
- label={t(`${typeof close === 'string' ? 'minify' : 'close'} label`)}
93
- classNames={buttonClassNames}
94
- onClick={() => onClick?.('close')}
95
- data-testid='layoutHeading.close'
96
- icon={
97
- close === 'minify-start'
98
- ? 'ph--caret-line-left--regular'
99
- : close === 'minify-end'
100
- ? 'ph--caret-line-right--regular'
101
- : 'ph--x--regular'
102
- }
103
- />
104
- )}
105
- {children}
106
- </ButtonGroup>
107
- );
108
- },
109
- );
@@ -1,64 +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 { faker } from '@dxos/random';
9
- import { withTheme } from '@dxos/react-ui/testing';
10
-
11
- import { Card } from './Card';
12
-
13
- faker.seed(0);
14
-
15
- type CardStoryProps = {
16
- title: string;
17
- description: string;
18
- image: string;
19
- showImage: boolean;
20
- showIcon: boolean;
21
- };
22
-
23
- const DefaultStory = ({ title, description, image, showImage, showIcon }: CardStoryProps) => {
24
- return (
25
- <div className='max-is-md'>
26
- <Card.StaticRoot>
27
- <Card.Toolbar>
28
- <Card.DragHandle toolbarItem />
29
- <Card.ToolbarSeparator variant='gap' />
30
- <Card.ToolbarIconButton iconOnly variant='ghost' icon='ph--x--regular' label={'remove card label'} />
31
- </Card.Toolbar>
32
- {showImage && <Card.Poster alt={title} image={image} />}
33
- {!showImage && showIcon && <Card.Poster alt={title} icon='ph--building-office--regular' />}
34
- <Card.Heading>{title}</Card.Heading>
35
- {description && <Card.Text classNames='line-clamp-2'>{description}</Card.Text>}
36
- </Card.StaticRoot>
37
- </div>
38
- );
39
- };
40
-
41
- const meta = {
42
- title: 'ui/react-ui-stack/Card',
43
- render: DefaultStory,
44
- decorators: [withTheme],
45
- parameters: {
46
- layout: 'centered',
47
- },
48
- } satisfies Meta<typeof DefaultStory>;
49
-
50
- export default meta;
51
-
52
- type Story = StoryObj<typeof meta>;
53
-
54
- const image = faker.image.url();
55
-
56
- export const Default: Story = {
57
- args: {
58
- title: faker.commerce.productName(),
59
- description: faker.lorem.paragraph(),
60
- image,
61
- showImage: true,
62
- showIcon: true,
63
- },
64
- };