@dxos/react-ui-stack 0.6.14-staging.e15392e → 0.7.0

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 (91) hide show
  1. package/dist/lib/browser/index.mjs +494 -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 +478 -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 +494 -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 +9 -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 +8 -8
  43. package/package.json +19 -20
  44. package/src/components/LayoutControls.tsx +131 -0
  45. package/src/components/MenuSignifier.tsx +33 -0
  46. package/src/components/Stack.stories.tsx +109 -182
  47. package/src/components/Stack.tsx +61 -156
  48. package/src/components/StackContext.tsx +38 -0
  49. package/src/components/StackItem.tsx +173 -0
  50. package/src/components/StackItemContent.tsx +49 -0
  51. package/src/components/StackItemHeading.tsx +55 -0
  52. package/src/components/StackItemResizeHandle.tsx +115 -0
  53. package/src/components/StackItemSigil.tsx +170 -0
  54. package/src/components/index.ts +3 -2
  55. package/src/playwright/smoke.spec.ts +3 -3
  56. package/src/testing/EditorContent.tsx +4 -4
  57. package/src/testing/stack-manager.ts +3 -7
  58. package/src/translations.ts +8 -8
  59. package/dist/types/src/components/CaretDownUp.d.ts +0 -4
  60. package/dist/types/src/components/CaretDownUp.d.ts.map +0 -1
  61. package/dist/types/src/components/ContentTypes.stories.d.ts +0 -96
  62. package/dist/types/src/components/ContentTypes.stories.d.ts.map +0 -1
  63. package/dist/types/src/components/Deck.stories.d.ts +0 -19
  64. package/dist/types/src/components/Deck.stories.d.ts.map +0 -1
  65. package/dist/types/src/components/Section.d.ts +0 -53
  66. package/dist/types/src/components/Section.d.ts.map +0 -1
  67. package/dist/types/src/components/Section.stories.d.ts +0 -36
  68. package/dist/types/src/components/Section.stories.d.ts.map +0 -1
  69. package/dist/types/src/components/style-fragments.d.ts +0 -2
  70. package/dist/types/src/components/style-fragments.d.ts.map +0 -1
  71. package/dist/types/src/next/Stack.d.ts +0 -9
  72. package/dist/types/src/next/Stack.d.ts.map +0 -1
  73. package/dist/types/src/next/Stack.stories.d.ts +0 -8
  74. package/dist/types/src/next/Stack.stories.d.ts.map +0 -1
  75. package/dist/types/src/next/StackItem.d.ts +0 -14
  76. package/dist/types/src/next/StackItem.d.ts.map +0 -1
  77. package/dist/types/src/next/index.d.ts +0 -2
  78. package/dist/types/src/next/index.d.ts.map +0 -1
  79. package/dist/types/src/testing/TableContent.d.ts +0 -20
  80. package/dist/types/src/testing/TableContent.d.ts.map +0 -1
  81. package/src/components/CaretDownUp.tsx +0 -31
  82. package/src/components/ContentTypes.stories.tsx +0 -104
  83. package/src/components/Deck.stories.tsx +0 -362
  84. package/src/components/Section.stories.tsx +0 -50
  85. package/src/components/Section.tsx +0 -378
  86. package/src/components/style-fragments.ts +0 -5
  87. package/src/next/Stack.stories.tsx +0 -148
  88. package/src/next/Stack.tsx +0 -30
  89. package/src/next/StackItem.tsx +0 -78
  90. package/src/next/index.ts +0 -5
  91. package/src/testing/TableContent.tsx +0 -119
@@ -1,378 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { useFocusableGroup, useTabsterAttributes } from '@fluentui/react-tabster';
6
- import { ArrowLineDown, ArrowLineUp, ArrowSquareOut, CaretUpDown, Trash } from '@phosphor-icons/react';
7
- import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
8
- import React, {
9
- forwardRef,
10
- useState,
11
- type ForwardRefExoticComponent,
12
- type RefAttributes,
13
- type FC,
14
- type PropsWithChildren,
15
- type ComponentPropsWithRef,
16
- } from 'react';
17
-
18
- import {
19
- Button,
20
- DropdownMenu,
21
- Icon,
22
- type Label,
23
- List,
24
- ListItem,
25
- ScrollArea,
26
- type ThemedClassName,
27
- Toolbar,
28
- toLocalizedString,
29
- useTranslation,
30
- } from '@dxos/react-ui';
31
- import { useAttendableAttributes } from '@dxos/react-ui-attention';
32
- import { DropDownMenuDragHandleTrigger, resizeHandle, resizeHandleHorizontal } from '@dxos/react-ui-deck';
33
- import {
34
- type MosaicActiveType,
35
- type MosaicDataItem,
36
- type MosaicTileComponent,
37
- type MosaicTileProps,
38
- useMosaic,
39
- } from '@dxos/react-ui-mosaic';
40
- import {
41
- focusRing,
42
- getSize,
43
- hoverableControlItem,
44
- hoverableControls,
45
- hoverableFocusedWithinControls,
46
- mx,
47
- } from '@dxos/react-ui-theme';
48
-
49
- import { CaretDownUp } from './CaretDownUp';
50
- import { stackColumns } from './style-fragments';
51
- import { translationKey } from '../translations';
52
-
53
- const sectionActionDimensions = 'p-1 shrink-0 min-bs-0 is-[--rail-action] bs-min';
54
-
55
- export type StackSectionContent = MosaicDataItem;
56
-
57
- export type CollapsedSections = Record<string, boolean>;
58
-
59
- export type AddSectionPosition = 'before' | 'after' | 'beforeAll' | 'afterAll';
60
-
61
- export type StackContextValue<TData extends StackSectionContent = StackSectionContent> = {
62
- SectionContent: FC<{ data: TData }>;
63
- separation?: boolean;
64
- isResizable?: boolean;
65
- transform?: (item: MosaicDataItem, type?: string) => StackSectionItem;
66
- onDeleteSection?: (path: string) => void;
67
- onAddSection?: (path: string, position: AddSectionPosition) => void;
68
- onNavigateToSection?: (object: MosaicDataItem) => void;
69
- onCollapseSection?: (id: string, collapsed: boolean) => void;
70
- };
71
-
72
- export type StackItem = MosaicDataItem & {
73
- items: StackSectionItem[];
74
- };
75
-
76
- export type StackSectionItem = MosaicDataItem & {
77
- object: StackSectionContent;
78
- // TODO(wittjosiah): Use effect schema? Share schema with echo.
79
- view?: {
80
- title?: string;
81
- size?: SectionSize;
82
- height?: number;
83
- collapsed?: boolean;
84
- custom?: Record<string, any>;
85
- };
86
- // TODO(wittjosiah): Common type? Factor out?
87
- metadata?: {
88
- icon?: string;
89
- placeholder?: Label;
90
- viewActions?: (item: StackSectionItem) => StackAction;
91
- };
92
- };
93
-
94
- export type StackAction = {
95
- icon: string;
96
- label: Label;
97
- onClick: () => void;
98
- };
99
-
100
- export type SectionSize = 'intrinsic' | 'extrinsic';
101
-
102
- export type SectionProps = PropsWithChildren<
103
- {
104
- // Data props.
105
- id: string;
106
- title: string;
107
-
108
- // Tile props.
109
- active?: MosaicActiveType;
110
- } & Pick<
111
- MosaicTileProps,
112
- 'draggableProps' | 'draggableStyle' | 'onDelete' | 'onNavigate' | 'onAddAfter' | 'onAddBefore'
113
- > &
114
- Pick<StackContextValue, 'separation' | 'isResizable' | 'onCollapseSection'> &
115
- Pick<Required<StackSectionItem>['view'], 'collapsed' | 'size'> &
116
- Pick<Required<StackSectionItem>['metadata'], 'icon'>
117
- >;
118
-
119
- const resizeHandleStyles = mx(resizeHandle, resizeHandleHorizontal, 'is-full bs-[--rail-action] col-start-2');
120
-
121
- export const Section: ForwardRefExoticComponent<SectionProps & RefAttributes<HTMLLIElement>> = forwardRef<
122
- HTMLLIElement,
123
- SectionProps
124
- >(
125
- (
126
- {
127
- id,
128
- title,
129
- icon = 'ph--placeholder--regular',
130
- size = 'intrinsic',
131
- collapsed,
132
- active,
133
- isResizable,
134
- draggableProps,
135
- draggableStyle,
136
- onDelete,
137
- onNavigate,
138
- onAddBefore,
139
- onAddAfter,
140
- onCollapseSection,
141
- children,
142
- },
143
- forwardedRef,
144
- ) => {
145
- const { t } = useTranslation(translationKey);
146
- const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
147
- const sectionActionsToolbar = useTabsterAttributes({
148
- groupper: {},
149
- focusable: {},
150
- mover: { cyclic: true, direction: 1, memorizeCurrent: false },
151
- });
152
- const sectionContentGroup = useFocusableGroup({});
153
- const attendableAttrs = useAttendableAttributes(id);
154
-
155
- return (
156
- <CollapsiblePrimitive.Root
157
- asChild
158
- open={!collapsed}
159
- onOpenChange={(nextOpen) => onCollapseSection?.(id, !nextOpen)}
160
- >
161
- <ListItem.Root
162
- ref={forwardedRef}
163
- id={id}
164
- {...attendableAttrs}
165
- classNames={[
166
- 'grid col-span-2 group/section',
167
- active === 'overlay' ? stackColumns : 'grid-cols-subgrid snap-start',
168
- ]}
169
- style={draggableStyle}
170
- >
171
- <div
172
- role='none'
173
- className={mx(
174
- 'grid col-span-2 grid-cols-subgrid',
175
- 'bg-base attention-surface',
176
- hoverableControls,
177
- hoverableFocusedWithinControls,
178
- (active === 'origin' || active === 'rearrange' || active === 'destination') && 'opacity-0',
179
- )}
180
- >
181
- <div
182
- role='toolbar'
183
- aria-orientation='vertical'
184
- aria-label={t('section controls label')}
185
- {...(!active && { tabIndex: 0 })}
186
- {...(!active && sectionActionsToolbar)}
187
- className={mx(
188
- 'grid grid-cols-subgrid ch-focus-ring rounded-sm grid-rows-[min-content_min-content_1fr] m-1',
189
- 'group-has-[[role=toolbar][aria-orientation=horizontal]]/section:pbs-[--rail-action]',
190
- )}
191
- >
192
- <div role='none' className='sticky -block-start-px bg-[--sticky-bg]'>
193
- <DropdownMenu.Root
194
- {...{
195
- open: optionsMenuOpen,
196
- onOpenChange: setOptionsMenuOpen,
197
- }}
198
- >
199
- <DropDownMenuDragHandleTrigger active={!!active} variant='ghost' classNames='m-0' {...draggableProps}>
200
- <Icon icon={icon} size={5} classNames='transition-opacity' />
201
- </DropDownMenuDragHandleTrigger>
202
- <DropdownMenu.Portal>
203
- <DropdownMenu.Content>
204
- <DropdownMenu.Viewport>
205
- {collapsed ? (
206
- <DropdownMenu.Item onClick={onNavigate} data-testid='section.navigate-to'>
207
- <ArrowSquareOut className={mx(getSize(5), 'mr-2')} />
208
- <span className='grow'>{t('navigate to section label')}</span>
209
- </DropdownMenu.Item>
210
- ) : (
211
- <CollapsiblePrimitive.Trigger asChild>
212
- <DropdownMenu.Item>
213
- <CaretDownUp className={mx(getSize(5), 'mr-2')} />
214
- <span className='grow'>{t('collapse label')}</span>
215
- </DropdownMenu.Item>
216
- </CollapsiblePrimitive.Trigger>
217
- )}
218
- <DropdownMenu.Item onClick={onAddBefore} data-testid='section.add-before'>
219
- <ArrowLineUp className={mx(getSize(5), 'mr-2')} />
220
- <span className='grow'>{t('add section before label')}</span>
221
- </DropdownMenu.Item>
222
- <DropdownMenu.Item onClick={onAddAfter} data-testid='section.add-after'>
223
- <ArrowLineDown className={mx(getSize(5), 'mr-2')} />
224
- <span className='grow'>{t('add section after label')}</span>
225
- </DropdownMenu.Item>
226
- <DropdownMenu.Item onClick={() => onDelete?.()} data-testid='section.remove'>
227
- <Trash className={mx(getSize(5), 'mr-2')} />
228
- <span className='grow'>{t('remove section label')}</span>
229
- </DropdownMenu.Item>
230
- </DropdownMenu.Viewport>
231
- <DropdownMenu.Arrow />
232
- </DropdownMenu.Content>
233
- </DropdownMenu.Portal>
234
- </DropdownMenu.Root>
235
- {collapsed ? (
236
- <CollapsiblePrimitive.Trigger asChild>
237
- <Button variant='ghost' classNames={sectionActionDimensions}>
238
- <span className='sr-only'>{t('expand label')}</span>
239
- <CaretUpDown className={getSize(4)} />
240
- </Button>
241
- </CollapsiblePrimitive.Trigger>
242
- ) : (
243
- <Button
244
- variant='ghost'
245
- classNames={sectionActionDimensions}
246
- onClick={onNavigate}
247
- data-testid='section.navigate-to'
248
- >
249
- <ArrowSquareOut className={mx(getSize(4))} />
250
- <span className='sr-only'>{t('navigate to section label')}</span>
251
- </Button>
252
- )}
253
- </div>
254
- </div>
255
-
256
- {/* Main content */}
257
-
258
- <ListItem.Heading
259
- classNames={collapsed ? ['grid grid-rows-subgrid grid-cols-subgrid items-center', focusRing] : 'sr-only'}
260
- {...(collapsed && { ...sectionContentGroup, tabIndex: 0 })}
261
- >
262
- {/*
263
- TODO(thure): This needs to be made extensible; Markdown document titles especially are difficult.
264
- Using `Surface` in a UI package like this would be unprecedented and needs motivation.
265
- Refactoring to use subcomponents is complicated by sections being a sortable Mosaic Tile.
266
- Reevaluate when work on collections (Folders, Stacks, etc) settles.
267
- */}
268
- <span className='truncate'>{title}</span>
269
- </ListItem.Heading>
270
- {size === 'intrinsic' ? (
271
- <CollapsiblePrimitive.Content
272
- {...(!collapsed && {
273
- ...sectionContentGroup,
274
- tabIndex: 0,
275
- })}
276
- // TODO(burdon): Add margin to fragment?
277
- className={mx(focusRing, 'm-[2px]')}
278
- >
279
- {children}
280
- </CollapsiblePrimitive.Content>
281
- ) : (
282
- <CollapsiblePrimitive.Content asChild>
283
- <ScrollArea.Root
284
- type='always'
285
- {...(!collapsed && { ...sectionContentGroup, tabIndex: 0 })}
286
- classNames={mx(focusRing, 'is-full has-[[data-radix-scroll-area-viewport]]:pbe-4')}
287
- >
288
- <ScrollArea.Viewport>{children}</ScrollArea.Viewport>
289
- <ScrollArea.Scrollbar
290
- orientation='horizontal'
291
- variant='coarse'
292
- classNames='hidden has-[div]:flex !inline-end-[max(.25rem,var(--radix-scroll-area-corner-width))]'
293
- >
294
- <ScrollArea.Thumb />
295
- </ScrollArea.Scrollbar>
296
- <ScrollArea.Scrollbar orientation='vertical' variant='coarse' classNames='hidden has-[div]:flex'>
297
- <ScrollArea.Thumb />
298
- </ScrollArea.Scrollbar>
299
- <ScrollArea.Corner />
300
- </ScrollArea.Root>
301
- </CollapsiblePrimitive.Content>
302
- )}
303
- </div>
304
- {isResizable && !collapsed && (
305
- <button className={resizeHandleStyles}>
306
- <span className='sr-only'>{t('resize section label')}</span>
307
- </button>
308
- )}
309
- </ListItem.Root>
310
- </CollapsiblePrimitive.Root>
311
- );
312
- },
313
- );
314
-
315
- export type SectionToolbarProps = ThemedClassName<ComponentPropsWithRef<'div'>>;
316
-
317
- // TODO(burdon): block-start
318
- export const sectionToolbarLayout =
319
- 'bs-[--rail-action] bg-[--sticky-bg] sticky block-start-0 __-block-start-px transition-opacity';
320
-
321
- export const SectionToolbar = ({ children, classNames }: SectionToolbarProps) => {
322
- return (
323
- <Toolbar.Root orientation='horizontal' classNames={[sectionToolbarLayout, hoverableControlItem, classNames]}>
324
- {children}
325
- </Toolbar.Root>
326
- );
327
- };
328
-
329
- export const SectionTile: MosaicTileComponent<
330
- StackSectionItem,
331
- HTMLLIElement
332
- // TODO(wittjosiah): If props is specified there is a type error with Mosaic.Container.
333
- // { itemContext: StackContextValue }
334
- > = forwardRef(({ path, type, active, draggableStyle, draggableProps, item, itemContext }, forwardedRef) => {
335
- const { t } = useTranslation(translationKey);
336
- const { activeItem } = useMosaic();
337
-
338
- const separation = !!itemContext?.separation;
339
- const isResizable = !!itemContext?.isResizable;
340
- const { transform, onDeleteSection, onNavigateToSection, onAddSection, onCollapseSection, SectionContent } =
341
- itemContext as StackContextValue;
342
-
343
- const transformedItem = transform
344
- ? transform(
345
- item,
346
- // TODO(wittjosiah): `active` doesn't always seem to be accurate here.
347
- activeItem?.item.id === item.id ? activeItem?.type : type,
348
- )
349
- : item;
350
-
351
- const placeholder = transformedItem.metadata?.placeholder ?? ['untitled section title', { ns: translationKey }];
352
- const title = transformedItem.view?.title ?? toLocalizedString(placeholder, t);
353
-
354
- const section = (
355
- <Section
356
- ref={forwardedRef}
357
- title={title}
358
- id={transformedItem.id}
359
- size={transformedItem.view?.size}
360
- icon={transformedItem.metadata?.icon}
361
- collapsed={transformedItem.view?.collapsed}
362
- separation={separation}
363
- active={active}
364
- draggableProps={draggableProps}
365
- draggableStyle={draggableStyle}
366
- onCollapseSection={onCollapseSection}
367
- isResizable={isResizable}
368
- onDelete={() => onDeleteSection?.(path)}
369
- onNavigate={() => onNavigateToSection?.(transformedItem)}
370
- onAddAfter={() => onAddSection?.(path, 'after')}
371
- onAddBefore={() => onAddSection?.(path, 'before')}
372
- >
373
- {SectionContent && <SectionContent data={transformedItem.object} />}
374
- </Section>
375
- );
376
-
377
- return active === 'overlay' ? <List>{section}</List> : section;
378
- });
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export const stackColumns = 'grid-cols-[var(--rail-size)_calc(100%-var(--rail-size))]';
@@ -1,148 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
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';
8
-
9
- import { withTheme } from '@dxos/storybook-utils';
10
-
11
- import { Stack } from './Stack';
12
- import { StackItem } from './StackItem';
13
-
14
- type CardItem = {
15
- id: string;
16
- type: 'card';
17
- content: string;
18
- };
19
-
20
- type ColumnItem = {
21
- id: string;
22
- type: 'column';
23
- title: string;
24
- cards: CardItem[];
25
- };
26
-
27
- const KanbanBlock = ({ item }: { item: CardItem }) => {
28
- return (
29
- <div className='is-64 bs-24 bg-input rounded-lg border border-separator shadow-sm grid place-content-center'>
30
- <span className='text-sm font-medium'>{item.content}</span>
31
- </div>
32
- );
33
- };
34
-
35
- const StorybookStack = () => {
36
- const [columns, setColumns] = useState<ColumnItem[]>([
37
- {
38
- id: 'col-0',
39
- type: 'column',
40
- title: 'To Do',
41
- cards: [
42
- { id: 'banana', type: 'card', content: 'Banana' },
43
- { id: 'pickle', type: 'card', content: 'Pickle' },
44
- { id: 'wombat', type: 'card', content: 'Wombat' },
45
- { id: 'kazoo', type: 'card', content: 'Kazoo' },
46
- ],
47
- },
48
- {
49
- id: 'col-1',
50
- type: 'column',
51
- title: 'In Progress',
52
- cards: [
53
- { id: 'noodle', type: 'card', content: 'Noodle' },
54
- { id: 'squish', type: 'card', content: 'Squish' },
55
- { id: 'wobble', type: 'card', content: 'Wobble' },
56
- { id: 'floof', type: 'card', content: 'Floof' },
57
- ],
58
- },
59
- {
60
- id: 'col-2',
61
- type: 'column',
62
- title: 'Done',
63
- cards: [
64
- { id: 'snorkel', type: 'card', content: 'Snorkel' },
65
- { id: 'bloop', type: 'card', content: 'Bloop' },
66
- { id: 'wiggle', type: 'card', content: 'Wiggle' },
67
- { id: 'zoop', type: 'card', content: 'Zoop' },
68
- ],
69
- },
70
- ]);
71
-
72
- const reorderItem = useCallback((sourceId: string, targetId: string, closestEdge: Edge | null) => {
73
- setColumns((prevColumns) => {
74
- const newColumns = [...prevColumns];
75
- const sourceColumn = newColumns.find(
76
- (col) => col.id === sourceId || col.cards.some((card) => card.id === sourceId),
77
- );
78
- const targetColumn = newColumns.find(
79
- (col) => col.id === targetId || col.cards.some((card) => card.id === targetId),
80
- );
81
-
82
- if (sourceColumn && targetColumn) {
83
- if (sourceId.startsWith('col-') && targetId.startsWith('col-')) {
84
- // Reordering columns
85
- const sourceIndex = newColumns.findIndex((col) => col.id === sourceId);
86
- const targetIndex = newColumns.findIndex((col) => col.id === targetId);
87
- const [movedColumn] = newColumns.splice(sourceIndex, 1);
88
- const insertIndex = closestEdge === 'right' ? targetIndex + 1 : targetIndex;
89
- newColumns.splice(insertIndex, 0, movedColumn);
90
- } else {
91
- // Reordering cards within a column
92
- const sourceCardIndex = sourceColumn.cards.findIndex((card) => card.id === sourceId);
93
- const targetCardIndex = targetColumn.cards.findIndex((card) => card.id === targetId);
94
- const [movedCard] = sourceColumn.cards.splice(sourceCardIndex, 1);
95
-
96
- let insertIndex;
97
- if (sourceColumn === targetColumn && sourceCardIndex < targetCardIndex) {
98
- insertIndex = closestEdge === 'bottom' ? targetCardIndex : targetCardIndex - 1;
99
- } else {
100
- insertIndex = closestEdge === 'bottom' ? targetCardIndex + 1 : targetCardIndex;
101
- }
102
- targetColumn.cards.splice(insertIndex, 0, movedCard);
103
- }
104
- }
105
-
106
- return newColumns;
107
- });
108
- }, []);
109
-
110
- return (
111
- <Stack orientation={'horizontal'} classNames='gap-1'>
112
- {columns.map((column) => (
113
- <StackItem
114
- key={column.id}
115
- item={column}
116
- orientation={'horizontal'}
117
- classNames='p-4 bg-deck rounded-md'
118
- onReorder={reorderItem}
119
- >
120
- <Stack orientation={'vertical'} classNames='gap-1'>
121
- {column.cards.map((card) => (
122
- <StackItem key={card.id} item={card} orientation={'vertical'} onReorder={reorderItem}>
123
- <KanbanBlock item={card} />
124
- </StackItem>
125
- ))}
126
- </Stack>
127
- </StackItem>
128
- ))}
129
- </Stack>
130
- );
131
- };
132
-
133
- type Story = StoryObj<typeof StorybookStack>;
134
-
135
- export const Default: Story = {
136
- args: {
137
- orientation: 'horizontal',
138
- },
139
- };
140
-
141
- const meta: Meta<typeof StorybookStack> = {
142
- title: 'ui/react-ui-stack-next/Stack',
143
- component: StorybookStack,
144
- decorators: [withTheme],
145
- argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
146
- };
147
-
148
- export default meta;
@@ -1,30 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import React, { Children, type CSSProperties, type ComponentPropsWithoutRef } from 'react';
6
-
7
- import { type ThemedClassName } from '@dxos/react-ui';
8
- import { mx } from '@dxos/react-ui-theme';
9
-
10
- type Orientation = 'horizontal' | 'vertical';
11
-
12
- export type StackProps = Omit<ThemedClassName<ComponentPropsWithoutRef<'div'>>, 'aria-orientation'> & {
13
- orientation?: Orientation;
14
- };
15
-
16
- export const Stack = ({ children, classNames, style, orientation, ...props }: StackProps) => {
17
- const childrenCount = Children.count(children);
18
-
19
- const styles: CSSProperties = {
20
- [orientation === 'horizontal' ? 'gridTemplateColumns' : 'gridTemplateRows']:
21
- `repeat(${childrenCount}, min-content)`,
22
- ...style,
23
- };
24
-
25
- return (
26
- <div className={mx('grid relative', classNames)} aria-orientation={orientation} style={styles} {...props}>
27
- {children}
28
- </div>
29
- );
30
- };
@@ -1,78 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
6
- import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
7
- import {
8
- attachClosestEdge,
9
- type Edge,
10
- extractClosestEdge,
11
- } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
12
- import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box';
13
- import React, { useEffect, useRef, useState, type ComponentPropsWithoutRef } from 'react';
14
-
15
- import { type ThemedClassName } from '@dxos/react-ui';
16
- import { mx } from '@dxos/react-ui-theme';
17
-
18
- import { type StackProps } from './Stack';
19
-
20
- export type StackItemProps = Omit<ThemedClassName<ComponentPropsWithoutRef<'div'>>, 'aria-orientation'> & {
21
- item: { id: string; type: 'column' | 'card' };
22
- orientation?: StackProps['orientation'];
23
- onReorder: (sourceId: string, targetId: string, closestEdge: Edge | null) => void;
24
- };
25
-
26
- export const StackItem = ({ item, children, classNames, orientation, onReorder, ...props }: StackItemProps) => {
27
- const ref = useRef<HTMLDivElement>(null);
28
- const [closestEdge, setEdge] = useState<Edge | null>(null);
29
-
30
- useEffect(() => {
31
- if (!ref.current) {
32
- return;
33
- }
34
-
35
- const element = ref.current;
36
-
37
- return combine(
38
- draggable({ element, getInitialData: () => ({ id: item.id, type: item.type }) }),
39
- dropTargetForElements({
40
- element,
41
- getData: ({ input, element }) => {
42
- return attachClosestEdge(
43
- { id: item.id, type: item.type },
44
- { input, element, allowedEdges: orientation === 'vertical' ? ['top', 'bottom'] : ['left', 'right'] },
45
- );
46
- },
47
- onDragEnter: ({ self, source }) => {
48
- if (source.data.type === self.data.type) {
49
- setEdge(extractClosestEdge(self.data));
50
- }
51
- },
52
- onDrag: ({ self, source }) => {
53
- if (source.data.type === self.data.type) {
54
- setEdge(extractClosestEdge(self.data));
55
- }
56
- },
57
- onDragLeave: () => setEdge(null),
58
- onDrop: ({ self, source }) => {
59
- setEdge(null);
60
- if (source.data.type === self.data.type) {
61
- onReorder(source.data.id as string, self.data.id as string, extractClosestEdge(self.data));
62
- }
63
- },
64
- }),
65
- );
66
- }, [orientation, item, onReorder]);
67
-
68
- return (
69
- <div
70
- ref={ref}
71
- className={mx('relative', orientation === 'horizontal' ? 'grid-cols-subgrid' : 'grid-rows-subgrid', classNames)}
72
- {...props}
73
- >
74
- {children}
75
- {closestEdge && <DropIndicator edge={closestEdge} />}
76
- </div>
77
- );
78
- };
package/src/next/index.ts DELETED
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './Stack';