@dxos/react-ui-stack 0.6.14-staging.e15392e → 0.7.1-staging.599df14

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 (92) hide show
  1. package/dist/lib/browser/index.mjs +493 -335
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs +3 -6
  5. package/dist/lib/browser/testing/index.mjs.map +3 -3
  6. package/dist/lib/node/index.cjs +477 -326
  7. package/dist/lib/node/index.cjs.map +4 -4
  8. package/dist/lib/node/meta.json +1 -1
  9. package/dist/lib/node/testing/index.cjs +3 -6
  10. package/dist/lib/node/testing/index.cjs.map +3 -3
  11. package/dist/lib/node-esm/index.mjs +493 -335
  12. package/dist/lib/node-esm/index.mjs.map +4 -4
  13. package/dist/lib/node-esm/meta.json +1 -1
  14. package/dist/lib/node-esm/testing/index.mjs +3 -6
  15. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  16. package/dist/types/src/components/LayoutControls.d.ts +19 -0
  17. package/dist/types/src/components/LayoutControls.d.ts.map +1 -0
  18. package/dist/types/src/components/MenuSignifier.d.ts +4 -0
  19. package/dist/types/src/components/MenuSignifier.d.ts.map +1 -0
  20. package/dist/types/src/components/Stack.d.ts +12 -12
  21. package/dist/types/src/components/Stack.d.ts.map +1 -1
  22. package/dist/types/src/components/Stack.stories.d.ts +6 -83
  23. package/dist/types/src/components/Stack.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/StackContext.d.ts +19 -0
  25. package/dist/types/src/components/StackContext.d.ts.map +1 -0
  26. package/dist/types/src/components/StackItem.d.ts +41 -0
  27. package/dist/types/src/components/StackItem.d.ts.map +1 -0
  28. package/dist/types/src/components/StackItemContent.d.ts +8 -0
  29. package/dist/types/src/components/StackItemContent.d.ts.map +1 -0
  30. package/dist/types/src/components/StackItemHeading.d.ts +8 -0
  31. package/dist/types/src/components/StackItemHeading.d.ts.map +1 -0
  32. package/dist/types/src/components/StackItemResizeHandle.d.ts +3 -0
  33. package/dist/types/src/components/StackItemResizeHandle.d.ts.map +1 -0
  34. package/dist/types/src/components/StackItemSigil.d.ts +31 -0
  35. package/dist/types/src/components/StackItemSigil.d.ts.map +1 -0
  36. package/dist/types/src/components/index.d.ts +2 -1
  37. package/dist/types/src/components/index.d.ts.map +1 -1
  38. package/dist/types/src/testing/EditorContent.d.ts +2 -2
  39. package/dist/types/src/testing/EditorContent.d.ts.map +1 -1
  40. package/dist/types/src/testing/stack-manager.d.ts +0 -1
  41. package/dist/types/src/testing/stack-manager.d.ts.map +1 -1
  42. package/dist/types/src/translations.d.ts +7 -8
  43. package/dist/types/src/translations.d.ts.map +1 -1
  44. package/package.json +20 -21
  45. package/src/components/LayoutControls.tsx +131 -0
  46. package/src/components/MenuSignifier.tsx +33 -0
  47. package/src/components/Stack.stories.tsx +109 -182
  48. package/src/components/Stack.tsx +61 -156
  49. package/src/components/StackContext.tsx +38 -0
  50. package/src/components/StackItem.tsx +173 -0
  51. package/src/components/StackItemContent.tsx +47 -0
  52. package/src/components/StackItemHeading.tsx +55 -0
  53. package/src/components/StackItemResizeHandle.tsx +115 -0
  54. package/src/components/StackItemSigil.tsx +170 -0
  55. package/src/components/index.ts +3 -2
  56. package/src/playwright/smoke.spec.ts +3 -3
  57. package/src/testing/EditorContent.tsx +4 -4
  58. package/src/testing/stack-manager.ts +3 -7
  59. package/src/translations.ts +7 -8
  60. package/dist/types/src/components/CaretDownUp.d.ts +0 -4
  61. package/dist/types/src/components/CaretDownUp.d.ts.map +0 -1
  62. package/dist/types/src/components/ContentTypes.stories.d.ts +0 -96
  63. package/dist/types/src/components/ContentTypes.stories.d.ts.map +0 -1
  64. package/dist/types/src/components/Deck.stories.d.ts +0 -19
  65. package/dist/types/src/components/Deck.stories.d.ts.map +0 -1
  66. package/dist/types/src/components/Section.d.ts +0 -53
  67. package/dist/types/src/components/Section.d.ts.map +0 -1
  68. package/dist/types/src/components/Section.stories.d.ts +0 -36
  69. package/dist/types/src/components/Section.stories.d.ts.map +0 -1
  70. package/dist/types/src/components/style-fragments.d.ts +0 -2
  71. package/dist/types/src/components/style-fragments.d.ts.map +0 -1
  72. package/dist/types/src/next/Stack.d.ts +0 -9
  73. package/dist/types/src/next/Stack.d.ts.map +0 -1
  74. package/dist/types/src/next/Stack.stories.d.ts +0 -8
  75. package/dist/types/src/next/Stack.stories.d.ts.map +0 -1
  76. package/dist/types/src/next/StackItem.d.ts +0 -14
  77. package/dist/types/src/next/StackItem.d.ts.map +0 -1
  78. package/dist/types/src/next/index.d.ts +0 -2
  79. package/dist/types/src/next/index.d.ts.map +0 -1
  80. package/dist/types/src/testing/TableContent.d.ts +0 -20
  81. package/dist/types/src/testing/TableContent.d.ts.map +0 -1
  82. package/src/components/CaretDownUp.tsx +0 -31
  83. package/src/components/ContentTypes.stories.tsx +0 -104
  84. package/src/components/Deck.stories.tsx +0 -362
  85. package/src/components/Section.stories.tsx +0 -50
  86. package/src/components/Section.tsx +0 -378
  87. package/src/components/style-fragments.ts +0 -5
  88. package/src/next/Stack.stories.tsx +0 -148
  89. package/src/next/Stack.tsx +0 -30
  90. package/src/next/StackItem.tsx +0 -78
  91. package/src/next/index.ts +0 -5
  92. package/src/testing/TableContent.tsx +0 -119
@@ -1,207 +1,134 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- // NOTE(thure): This unused import resolves the “likely not portable” TS error.
8
- // eslint-disable-next-line unused-imports/no-unused-imports
9
- import { type IconProps } from '@phosphor-icons/react';
10
- import React, { useEffect, useRef, useState } from 'react';
5
+ import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
6
+ import { type Meta, type StoryObj } from '@storybook/react';
7
+ import React, { useState, useCallback } from 'react';
11
8
 
12
9
  import { faker } from '@dxos/random';
13
- import { Mosaic, type MosaicDropEvent, type MosaicMoveEvent, type MosaicOperation, Path } from '@dxos/react-ui-mosaic';
14
- import { withLayout, withTheme } from '@dxos/storybook-utils';
15
-
16
- import { type StackSectionContent, type StackSectionItem } from './Section';
17
- import { Stack, type StackProps } from './Stack';
18
- import { TestObjectGenerator } from '../testing/generator';
10
+ import { withTheme } from '@dxos/storybook-utils';
19
11
 
20
- faker.seed(3);
12
+ import { Stack } from './Stack';
13
+ import { StackItem, type StackItemData } from './StackItem';
21
14
 
22
- const SimpleContent = ({ data }: { data: StackSectionContent & { title?: string } }) => (
23
- <div className='p-4 text-center'>{data.title ?? data.id}</div>
24
- );
15
+ type StoryStackItem = {
16
+ id: string;
17
+ title: string;
18
+ items?: StoryStackItem[];
19
+ };
25
20
 
26
- const ComplexContent = ({
27
- data,
28
- }: StackSectionItem & { data: StackSectionContent & { title?: string; body?: string; image?: string } }) => {
29
- useEffect(() => () => console.log('[ComplexContent]', 'unmount'), []);
21
+ const KanbanBlock = ({ item }: { item: StoryStackItem }) => {
30
22
  return (
31
- <div className='flex'>
32
- <div className='grow p-4'>
33
- <h1>{data.title ?? data.id}</h1>
34
- {data.body && <p>{data.body}</p>}
35
- </div>
36
- {data.image && <img src={data.image} />}
23
+ <div className='overflow-hidden'>
24
+ <p className='place-content-center p-4'>{item.title}</p>
37
25
  </div>
38
26
  );
39
27
  };
40
28
 
41
- export default {
42
- title: 'ui/react-ui-stack/Stack',
43
- component: Stack,
44
- decorators: [withTheme],
45
- render: ({ debug, ...args }: DemoStackProps & { debug: boolean }) => {
46
- return (
47
- <Mosaic.Root debug={debug}>
48
- <Mosaic.DragOverlay />
49
- <DemoStack {...args} />
50
- </Mosaic.Root>
51
- );
52
- },
53
- };
29
+ const StorybookStack = () => {
30
+ const [columns, setColumns] = useState<StoryStackItem[]>(
31
+ faker.helpers.multiple(
32
+ () =>
33
+ ({
34
+ id: faker.string.uuid(),
35
+ title: faker.lorem.paragraph(),
36
+ items: faker.helpers.multiple(
37
+ () =>
38
+ ({
39
+ id: faker.string.uuid(),
40
+ title: faker.lorem.paragraph(),
41
+ }) satisfies StoryStackItem,
42
+ { count: { min: 32, max: 64 } },
43
+ ),
44
+ }) satisfies StoryStackItem,
45
+ { count: 8 },
46
+ ),
47
+ );
54
48
 
55
- export const Empty = {
56
- args: {
57
- SectionContent: SimpleContent,
58
- count: 0,
59
- },
60
- };
49
+ const reorderItem = useCallback((source: StackItemData, target: StackItemData, closestEdge: Edge | null) => {
50
+ setColumns((prevColumns) => {
51
+ const newColumns = [...prevColumns];
52
+ const sourceColumn = newColumns.find(
53
+ (col) => col.id === source.id || col.items?.some((card) => card.id === source.id),
54
+ );
55
+ const targetColumn = newColumns.find(
56
+ (col) => col.id === target.id || col.items?.some((card) => card.id === target.id),
57
+ );
58
+
59
+ if (sourceColumn && targetColumn) {
60
+ if (source.type === 'column' && target.type === 'column') {
61
+ // Reordering columns
62
+ const sourceIndex = newColumns.findIndex((col) => col.id === source.id);
63
+ const targetIndex = newColumns.findIndex((col) => col.id === target.id);
64
+ const [movedColumn] = newColumns.splice(sourceIndex, 1);
65
+ const insertIndex = closestEdge === 'right' ? targetIndex + 1 : targetIndex;
66
+ newColumns.splice(insertIndex, 0, movedColumn);
67
+ } else {
68
+ // Reordering cards within a column
69
+ const sourceCardIndex = sourceColumn.items?.findIndex((card) => card.id === source.id);
70
+ const targetCardIndex = targetColumn.items?.findIndex((card) => card.id === target.id);
71
+ if (
72
+ typeof sourceCardIndex === 'number' &&
73
+ typeof targetCardIndex === 'number' &&
74
+ sourceColumn.items &&
75
+ targetColumn.items
76
+ ) {
77
+ const [movedCard] = sourceColumn.items.splice(sourceCardIndex, 1);
78
+
79
+ let insertIndex;
80
+ if (sourceColumn === targetColumn && sourceCardIndex < targetCardIndex) {
81
+ insertIndex = closestEdge === 'bottom' ? targetCardIndex : targetCardIndex - 1;
82
+ } else {
83
+ insertIndex = closestEdge === 'bottom' ? targetCardIndex + 1 : targetCardIndex;
84
+ }
85
+ targetColumn.items.splice(insertIndex, 0, movedCard);
86
+ }
87
+ }
88
+ }
61
89
 
62
- export const Simple = {
63
- args: {
64
- SectionContent: SimpleContent,
65
- types: ['document'],
66
- debug: true,
67
- },
68
- };
90
+ return newColumns;
91
+ });
92
+ }, []);
69
93
 
70
- export const Complex = {
71
- args: {
72
- SectionContent: ComplexContent,
73
- types: ['document', 'image'],
74
- debug: true,
75
- },
94
+ return (
95
+ <main className='fixed inset-0'>
96
+ <Stack orientation='horizontal' size='contain'>
97
+ {columns.map((column) => (
98
+ <StackItem.Root key={column.id} item={column} onRearrange={reorderItem}>
99
+ <StackItem.Heading>
100
+ <StackItem.ResizeHandle />
101
+ </StackItem.Heading>
102
+ <Stack orientation='vertical' size='contain'>
103
+ {column.items?.map((card) => (
104
+ <StackItem.Root key={card.id} item={card} onRearrange={reorderItem}>
105
+ <StackItem.Heading>
106
+ <StackItem.ResizeHandle />
107
+ </StackItem.Heading>
108
+ <KanbanBlock item={card} />
109
+ </StackItem.Root>
110
+ ))}
111
+ </Stack>
112
+ </StackItem.Root>
113
+ ))}
114
+ </Stack>
115
+ </main>
116
+ );
76
117
  };
77
118
 
78
- export const Transfer = {
79
- args: {
80
- SectionContent: SimpleContent,
81
- types: ['document'],
82
- count: 8,
83
- className: 'w-[400px]',
84
- },
85
- render: ({ debug, ...args }: DemoStackProps & { debug: boolean }) => {
86
- return (
87
- <Mosaic.Root debug={debug}>
88
- <Mosaic.DragOverlay />
89
- <div className='flex grow justify-center p-4' data-testid='stack-transfer'>
90
- <div className='grid grid-cols-2 gap-4'>
91
- <DemoStack {...args} id='stack-1' />
92
- <DemoStack {...args} id='stack-2' />
93
- </div>
94
- </div>
95
- </Mosaic.Root>
96
- );
97
- },
98
- decorators: [withTheme, withLayout({ fullscreen: true })],
99
- };
119
+ type Story = StoryObj<typeof StorybookStack>;
100
120
 
101
- export const Copy = {
121
+ export const Default: Story = {
102
122
  args: {
103
- SectionContent: SimpleContent,
104
- types: ['document'],
105
- className: 'w-[400px]',
106
- },
107
- render: ({ debug, ...args }: DemoStackProps & { debug: boolean }) => {
108
- return (
109
- <Mosaic.Root debug={debug}>
110
- <Mosaic.DragOverlay debug={debug} />
111
- <div className='flex grow justify-center p-4' data-testid='stack-copy'>
112
- <div className='grid grid-cols-2 gap-4'>
113
- <DemoStack {...args} id='stack-1' />
114
- <DemoStack {...args} id='stack-2' operation='copy' count={0} />
115
- </div>
116
- </div>
117
- </Mosaic.Root>
118
- );
123
+ orientation: 'horizontal',
119
124
  },
120
- decorators: [withTheme, withLayout({ fullscreen: true })],
121
125
  };
122
126
 
123
- export type DemoStackProps = StackProps & {
124
- types?: string[];
125
- count?: number;
126
- operation?: MosaicOperation;
127
+ const meta: Meta<typeof StorybookStack> = {
128
+ title: 'ui/react-ui-stack-next/Stack',
129
+ component: StorybookStack,
130
+ decorators: [withTheme],
131
+ argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
127
132
  };
128
133
 
129
- const DemoStack = ({
130
- id = 'stack',
131
- SectionContent,
132
- types,
133
- count = 8,
134
- operation = 'transfer',
135
- classNames,
136
- }: DemoStackProps) => {
137
- const [items, setItems] = useState<StackSectionItem[]>(() => {
138
- const generator = new TestObjectGenerator({ types });
139
- return generator.createObjects({ length: count }).map((object) => ({
140
- id: faker.string.uuid(),
141
- object,
142
- }));
143
- });
144
-
145
- const itemsRef = useRef(items);
146
-
147
- const handleOver = ({ active, over }: MosaicMoveEvent<number>) => {
148
- if (operation === 'reject') {
149
- return 'reject';
150
- }
151
-
152
- if (Path.parent(active.path) === Path.parent(over.path)) {
153
- return 'rearrange';
154
- }
155
-
156
- // TODO(wittjosiah): Items is stale here for some inexplicable reason, so ref helps.
157
- const exists = itemsRef.current.findIndex((item) => item.id === active.item.id) >= 0;
158
-
159
- if (!exists) {
160
- return operation;
161
- } else {
162
- return 'reject';
163
- }
164
- };
165
-
166
- const handleDrop = ({ operation, active, over }: MosaicDropEvent<number>) => {
167
- setItems((items) => {
168
- if (
169
- (active.path === Path.create(id, active.item.id) || active.path === id) &&
170
- (operation !== 'copy' || over.path === Path.create(id, over.item.id) || over.path === id)
171
- ) {
172
- items.splice(active.position!, 1);
173
- }
174
-
175
- if (over.path === Path.create(id, over.item.id)) {
176
- items.splice(over.position!, 0, active.item as StackSectionItem);
177
- } else if (over.path === id) {
178
- items.push(active.item as StackSectionItem);
179
- }
180
-
181
- const i = [...items];
182
- itemsRef.current = i;
183
- return i;
184
- });
185
- };
186
-
187
- const handleRemove = (path: string) => {
188
- setItems((items) => {
189
- const newItems = items.filter((item) => item.id !== Path.last(path));
190
- itemsRef.current = newItems;
191
- return newItems;
192
- });
193
- };
194
-
195
- return (
196
- <Stack
197
- id={id}
198
- classNames={classNames}
199
- data-testid={id}
200
- SectionContent={SectionContent}
201
- items={items}
202
- onOver={handleOver}
203
- onDrop={handleDrop}
204
- onDeleteSection={handleRemove}
205
- />
206
- );
207
- };
134
+ export default meta;
@@ -1,173 +1,78 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useArrowNavigationGroup, useFocusableGroup } from '@fluentui/react-tabster';
6
- import React, { type ReactNode, forwardRef, useCallback } from 'react';
7
- import { useResizeDetector } from 'react-resize-detector';
5
+ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
+ import React, { Children, type CSSProperties, type ComponentPropsWithRef, forwardRef } from 'react';
8
7
 
9
- import { List, useTranslation } from '@dxos/react-ui';
10
- import {
11
- type MosaicContainerProps,
12
- type MosaicTileComponent,
13
- Mosaic,
14
- Path,
15
- useContainer,
16
- useItemsWithPreview,
17
- useMosaic,
18
- } from '@dxos/react-ui-mosaic';
19
- import { dropRingInner } from '@dxos/react-ui-theme';
8
+ import { type ThemedClassName } from '@dxos/react-ui';
9
+ import { mx } from '@dxos/react-ui-theme';
20
10
 
21
- import {
22
- type CollapsedSections,
23
- type AddSectionPosition,
24
- SectionTile,
25
- type StackContextValue,
26
- type StackItem,
27
- type StackSectionContent,
28
- type StackSectionItem,
29
- } from './Section';
30
- import { stackColumns } from './style-fragments';
31
- import { translationKey } from '../translations';
11
+ import { type StackContextValue, StackContext } from './StackContext';
32
12
 
33
- export type Direction = 'horizontal' | 'vertical';
13
+ export type Orientation = 'horizontal' | 'vertical';
14
+ export type Size = 'intrinsic' | 'contain';
34
15
 
35
- export type { CollapsedSections, AddSectionPosition };
16
+ export type StackProps = Omit<ThemedClassName<ComponentPropsWithRef<'div'>>, 'aria-orientation'> &
17
+ Partial<StackContextValue> & { itemsCount?: number };
36
18
 
37
- export const DEFAULT_TYPE = 'stack-section';
19
+ export const railGridHorizontal = 'grid-rows-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
38
20
 
39
- export type StackProps<TData extends StackSectionContent = StackSectionContent> = Omit<
40
- MosaicContainerProps<TData, number>,
41
- 'debug' | 'Component'
42
- > &
43
- Omit<StackContextValue<TData>, 'setCollapsedSections'> & {
44
- items?: StackSectionItem[];
45
- separation?: boolean; // TODO(burdon): Style.
46
- onCollapseSection?: (id: string, collapsed: boolean) => void;
47
- emptyComponent?: ReactNode;
48
- };
21
+ export const railGridVertical = 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
49
22
 
50
- export const Stack = ({
51
- id,
52
- type = DEFAULT_TYPE,
53
- SectionContent,
54
- items = [],
55
- separation = true,
56
- transform,
57
- onOver,
58
- onDrop,
59
- onAddSection,
60
- onDeleteSection,
61
- onNavigateToSection,
62
- onCollapseSection,
63
- ...props
64
- }: StackProps) => {
65
- const { ref: containerRef, width = 0 } = useResizeDetector<HTMLDivElement>({ refreshRate: 200 });
66
- const { operation, overItem } = useMosaic();
67
- const itemsWithPreview = useItemsWithPreview({ path: id, items });
68
-
69
- const getOverlayStyle = useCallback(() => ({ width }), [width]);
70
- const getOverlayProps = useCallback(
71
- () => ({ itemContext: { transform, SectionContent } }),
72
- [transform, SectionContent],
73
- );
74
-
75
- // TODO(thure): The root cause of the discrepancy between `activeNodeRect.top` and `overlayNodeRect.top` in Composer
76
- // in particular is not yet known, so this solution may may backfire in unforeseeable cases.
77
- const stackModifier = useCallback<Exclude<MosaicContainerProps['modifier'], undefined>>(
78
- (_activeItem, { transform, activeNodeRect, overlayNodeRect }) => {
79
- if (activeNodeRect && overlayNodeRect) {
80
- transform.y += activeNodeRect?.top - overlayNodeRect?.top;
81
- }
82
- return transform;
23
+ export const Stack = forwardRef<HTMLDivElement, StackProps>(
24
+ (
25
+ {
26
+ children,
27
+ classNames,
28
+ style,
29
+ orientation = 'vertical',
30
+ rail = true,
31
+ separators = true,
32
+ size = 'intrinsic',
33
+ itemsCount = Children.count(children),
34
+ ...props
83
35
  },
84
- [],
85
- );
86
-
87
- return (
88
- <Mosaic.Container
89
- {...{
90
- id,
91
- type,
92
- Component: SectionTile as MosaicTileComponent<StackSectionItem, HTMLDivElement>,
93
- getOverlayStyle,
94
- getOverlayProps,
95
- onOver,
96
- onDrop,
97
- modifier: stackModifier,
98
- }}
99
- >
100
- <Mosaic.DroppableTile
101
- path={id}
102
- type={type}
103
- item={{ id, items: itemsWithPreview }}
104
- // TODO(wittjosiah): Should this actually be a context?
105
- itemContext={{
106
- separation,
107
- transform,
108
- onDeleteSection,
109
- onNavigateToSection,
110
- onAddSection,
111
- onCollapseSection,
112
- SectionContent,
113
- }}
114
- isOver={
115
- overItem &&
116
- !!overItem.path &&
117
- Path.hasRoot(overItem.path, id) &&
118
- (operation === 'copy' || operation === 'transfer')
119
- }
120
- Component={StackTile}
121
- {...props}
122
- ref={containerRef}
123
- />
124
- </Mosaic.Container>
125
- );
126
- };
36
+ forwardedRef,
37
+ ) => {
38
+ const arrowNavigationGroup = useArrowNavigationGroup({ axis: orientation });
127
39
 
128
- const StackTile: MosaicTileComponent<StackItem, HTMLOListElement, Pick<StackProps, 'emptyComponent'>> = forwardRef(
129
- ({ classNames, path, isOver, item: { items }, itemContext, type: _type, emptyComponent, ...props }, forwardedRef) => {
130
- const { t } = useTranslation(translationKey);
131
- const { Component, type } = useContainer();
132
- const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
133
- const { activeItem } = useMosaic();
40
+ const styles: CSSProperties = {
41
+ [orientation === 'horizontal' ? 'gridTemplateColumns' : 'gridTemplateRows']: `repeat(${itemsCount}, min-content)`,
42
+ ...style,
43
+ };
134
44
 
135
- // NOTE(thure): Ensure “groupper” is available, but no need to use it here.
136
- const _group = useFocusableGroup();
137
-
138
- // NOTE: Keep outer padding the same as MarkdownMain.
139
45
  return (
140
- <List
141
- ref={forwardedRef}
142
- classNames={['grid relative', stackColumns, isOver && dropRingInner, classNames]}
143
- {...(!activeItem && domAttributes)}
144
- {...props}
145
- >
146
- {items.length > 0 ? (
147
- <Mosaic.SortableContext items={items} direction='vertical'>
148
- {items.map((item, index) => (
149
- <Mosaic.SortableTile
150
- key={item.id}
151
- item={item}
152
- itemContext={itemContext}
153
- path={path}
154
- type={type}
155
- position={index}
156
- Component={Component}
157
- />
158
- ))}
159
- </Mosaic.SortableContext>
160
- ) : emptyComponent !== undefined ? (
161
- <>{emptyComponent}</>
162
- ) : (
163
- <p
164
- className='grid col-span-2 text-center p-4 border border-dashed border-neutral-500/50 rounded'
165
- data-testid='stack.empty'
166
- >
167
- {t('empty stack message')}
168
- </p>
169
- )}
170
- </List>
46
+ <StackContext.Provider value={{ orientation, rail, size, separators }}>
47
+ <div
48
+ {...props}
49
+ {...arrowNavigationGroup}
50
+ className={mx(
51
+ 'grid relative',
52
+ rail
53
+ ? orientation === 'horizontal'
54
+ ? railGridHorizontal
55
+ : railGridVertical
56
+ : orientation === 'horizontal'
57
+ ? 'grid-rows-1'
58
+ : 'grid-cols-1',
59
+ size === 'contain' &&
60
+ (orientation === 'horizontal'
61
+ ? 'overflow-x-auto min-bs-0 bs-full max-bs-full'
62
+ : 'overflow-y-auto min-is-0 is-full max-is-full'),
63
+ separators && (orientation === 'horizontal' ? 'divide-separator divide-x' : 'divide-separator divide-y'),
64
+ classNames,
65
+ )}
66
+ aria-orientation={orientation}
67
+ style={styles}
68
+ ref={forwardedRef}
69
+ >
70
+ {children}
71
+ </div>
72
+ </StackContext.Provider>
171
73
  );
172
74
  },
173
75
  );
76
+
77
+ export { StackContext };
78
+ export type { StackContextValue };
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { createContext, useContext } from 'react';
6
+
7
+ import { type Orientation, type Size } from './Stack';
8
+ import { type StackItemSize } from './StackItem';
9
+
10
+ export type StackContextValue = {
11
+ orientation: Orientation;
12
+ separators: boolean;
13
+ rail: boolean;
14
+ size: Size;
15
+ };
16
+
17
+ export const StackContext = createContext<StackContextValue>({
18
+ orientation: 'vertical',
19
+ rail: true,
20
+ size: 'intrinsic',
21
+ separators: true,
22
+ });
23
+
24
+ export const useStack = () => useContext(StackContext);
25
+
26
+ type StackItemContextValue = {
27
+ selfDragHandleRef: (element: HTMLDivElement | null) => void;
28
+ size: StackItemSize;
29
+ setSize: (nextSize: StackItemSize, commit?: boolean) => void;
30
+ };
31
+
32
+ export const StackItemContext = createContext<StackItemContextValue>({
33
+ selfDragHandleRef: () => {},
34
+ size: 'min-content',
35
+ setSize: () => {},
36
+ });
37
+
38
+ export const useStackItem = () => useContext(StackItemContext);