@dxos/react-ui-stack 0.8.2-staging.7ac8446 → 0.8.3-main.672df60
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.
- package/dist/lib/browser/index.mjs +960 -419
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +949 -408
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node-esm/index.mjs +960 -419
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/components/{Stack.d.ts → Stack/Stack.d.ts} +4 -1
- package/dist/types/src/components/Stack/Stack.d.ts.map +1 -0
- package/dist/types/src/components/Stack/Stack.stories.d.ts +9 -0
- package/dist/types/src/components/Stack/Stack.stories.d.ts.map +1 -0
- package/dist/types/src/components/Stack/index.d.ts +2 -0
- package/dist/types/src/components/Stack/index.d.ts.map +1 -0
- package/dist/types/src/components/StackContext.d.ts +14 -10
- package/dist/types/src/components/StackContext.d.ts.map +1 -1
- package/dist/types/src/components/StackItem/MenuSignifier.d.ts.map +1 -0
- package/dist/types/src/components/{StackItem.d.ts → StackItem/StackItem.d.ts} +15 -6
- package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -0
- package/dist/types/src/components/StackItem/StackItem.stories.d.ts +8 -0
- package/dist/types/src/components/StackItem/StackItem.stories.d.ts.map +1 -0
- package/dist/types/src/components/StackItem/StackItemContent.d.ts.map +1 -0
- package/dist/types/src/components/{StackItemDragHandle.d.ts → StackItem/StackItemDragHandle.d.ts} +1 -1
- package/dist/types/src/components/StackItem/StackItemDragHandle.d.ts.map +1 -0
- package/dist/types/src/components/{StackItemHeading.d.ts → StackItem/StackItemHeading.d.ts} +4 -2
- package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -0
- package/dist/types/src/components/StackItem/StackItemResizeHandle.d.ts.map +1 -0
- package/dist/types/src/components/StackItem/StackItemSigil.d.ts.map +1 -0
- package/dist/types/src/components/StackItem/index.d.ts +2 -0
- package/dist/types/src/components/StackItem/index.d.ts.map +1 -0
- package/dist/types/src/components/defs.d.ts +18 -0
- package/dist/types/src/components/defs.d.ts.map +1 -0
- package/dist/types/src/components/deprecated/LayoutControls.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/exemplars/Card/Card.d.ts +58 -0
- package/dist/types/src/exemplars/Card/Card.d.ts.map +1 -0
- package/dist/types/src/exemplars/Card/Card.stories-todo.d.ts +1 -0
- package/dist/types/src/exemplars/Card/Card.stories-todo.d.ts.map +1 -0
- package/dist/types/src/exemplars/Card/CardDragPreview.d.ts +6 -0
- package/dist/types/src/exemplars/Card/CardDragPreview.d.ts.map +1 -0
- package/dist/types/src/exemplars/Card/fragments.d.ts +6 -0
- package/dist/types/src/exemplars/Card/fragments.d.ts.map +1 -0
- package/dist/types/src/exemplars/Card/index.d.ts +3 -0
- package/dist/types/src/exemplars/Card/index.d.ts.map +1 -0
- package/dist/types/src/exemplars/CardStack/CardStack.d.ts +34 -0
- package/dist/types/src/exemplars/CardStack/CardStack.d.ts.map +1 -0
- package/dist/types/src/exemplars/CardStack/CardStack.stories-todo.d.ts +1 -0
- package/dist/types/src/exemplars/CardStack/CardStack.stories-todo.d.ts.map +1 -0
- package/dist/types/src/exemplars/CardStack/CardStackDragPreview.d.ts +9 -0
- package/dist/types/src/exemplars/CardStack/CardStackDragPreview.d.ts.map +1 -0
- package/dist/types/src/exemplars/CardStack/index.d.ts +3 -0
- package/dist/types/src/exemplars/CardStack/index.d.ts.map +1 -0
- package/dist/types/src/exemplars/index.d.ts +3 -0
- package/dist/types/src/exemplars/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useStackDropForElements.d.ts +4 -4
- package/dist/types/src/hooks/useStackDropForElements.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/stack-manager.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -20
- package/src/components/{Stack.stories.tsx → Stack/Stack.stories.tsx} +29 -17
- package/src/components/{Stack.tsx → Stack/Stack.tsx} +55 -5
- package/src/components/Stack/index.ts +5 -0
- package/src/components/StackContext.tsx +21 -13
- package/src/components/StackItem/StackItem.stories.tsx +49 -0
- package/src/components/{StackItem.tsx → StackItem/StackItem.tsx} +90 -11
- package/src/components/{StackItemContent.tsx → StackItem/StackItemContent.tsx} +2 -1
- package/src/components/{StackItemDragHandle.tsx → StackItem/StackItemDragHandle.tsx} +2 -2
- package/src/components/{StackItemHeading.tsx → StackItem/StackItemHeading.tsx} +10 -6
- package/src/components/{StackItemResizeHandle.tsx → StackItem/StackItemResizeHandle.tsx} +1 -1
- package/src/components/{StackItemSigil.tsx → StackItem/StackItemSigil.tsx} +3 -15
- package/src/components/StackItem/index.ts +5 -0
- package/src/components/defs.ts +26 -0
- package/src/components/{LayoutControls.tsx → deprecated/LayoutControls.tsx} +3 -23
- package/src/components/index.ts +2 -2
- package/src/exemplars/Card/Card.stories-todo.tsx +135 -0
- package/src/exemplars/Card/Card.tsx +178 -0
- package/src/exemplars/Card/CardDragPreview.tsx +22 -0
- package/src/exemplars/Card/fragments.ts +14 -0
- package/src/exemplars/Card/index.ts +6 -0
- package/src/exemplars/CardStack/CardStack.stories-todo.tsx +80 -0
- package/src/exemplars/CardStack/CardStack.tsx +118 -0
- package/src/exemplars/CardStack/CardStackDragPreview.tsx +61 -0
- package/src/exemplars/CardStack/index.ts +6 -0
- package/src/exemplars/index.ts +6 -0
- package/src/hooks/useStackDropForElements.ts +7 -6
- package/src/index.ts +4 -0
- package/src/testing/stack-manager.ts +6 -6
- package/src/translations.ts +1 -0
- package/dist/types/src/components/LayoutControls.d.ts.map +0 -1
- package/dist/types/src/components/MenuSignifier.d.ts.map +0 -1
- package/dist/types/src/components/Stack.d.ts.map +0 -1
- package/dist/types/src/components/Stack.stories.d.ts +0 -8
- package/dist/types/src/components/Stack.stories.d.ts.map +0 -1
- package/dist/types/src/components/StackItem.d.ts.map +0 -1
- package/dist/types/src/components/StackItemContent.d.ts.map +0 -1
- package/dist/types/src/components/StackItemDragHandle.d.ts.map +0 -1
- package/dist/types/src/components/StackItemHeading.d.ts.map +0 -1
- package/dist/types/src/components/StackItemResizeHandle.d.ts.map +0 -1
- package/dist/types/src/components/StackItemSigil.d.ts.map +0 -1
- /package/dist/types/src/components/{MenuSignifier.d.ts → StackItem/MenuSignifier.d.ts} +0 -0
- /package/dist/types/src/components/{StackItemContent.d.ts → StackItem/StackItemContent.d.ts} +0 -0
- /package/dist/types/src/components/{StackItemResizeHandle.d.ts → StackItem/StackItemResizeHandle.d.ts} +0 -0
- /package/dist/types/src/components/{StackItemSigil.d.ts → StackItem/StackItemSigil.d.ts} +0 -0
- /package/dist/types/src/components/{LayoutControls.d.ts → deprecated/LayoutControls.d.ts} +0 -0
- /package/src/components/{MenuSignifier.tsx → StackItem/MenuSignifier.tsx} +0 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
5
7
|
import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
6
8
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
7
9
|
import React, { useState, useCallback } from 'react';
|
|
@@ -10,8 +12,8 @@ import { faker } from '@dxos/random';
|
|
|
10
12
|
import { withTheme } from '@dxos/storybook-utils';
|
|
11
13
|
|
|
12
14
|
import { Stack } from './Stack';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
+
import { StackItem } from '../StackItem';
|
|
16
|
+
import { type StackItemData } from '../defs';
|
|
15
17
|
|
|
16
18
|
type StoryStackItem = {
|
|
17
19
|
id: string;
|
|
@@ -27,7 +29,7 @@ const KanbanBlock = ({ item }: { item: StoryStackItem }) => {
|
|
|
27
29
|
);
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
const
|
|
32
|
+
const DefaultStory = () => {
|
|
31
33
|
const [columns, setColumns] = useState<StoryStackItem[]>(
|
|
32
34
|
faker.helpers.multiple(
|
|
33
35
|
() =>
|
|
@@ -95,14 +97,24 @@ const StorybookStack = () => {
|
|
|
95
97
|
return (
|
|
96
98
|
<main className='fixed inset-0'>
|
|
97
99
|
<Stack orientation='horizontal' size='contain' onRearrange={reorderItem}>
|
|
98
|
-
{columns.map((column) => (
|
|
99
|
-
<StackItem.Root
|
|
100
|
+
{columns.map((column, columnIndex, columnsArray) => (
|
|
101
|
+
<StackItem.Root
|
|
102
|
+
key={column.id}
|
|
103
|
+
item={column}
|
|
104
|
+
prevSiblingId={columnIndex > 0 ? columnsArray[columnIndex - 1].id : undefined}
|
|
105
|
+
nextSiblingId={columnIndex < columnsArray.length - 1 ? columnsArray[columnIndex + 1].id : undefined}
|
|
106
|
+
>
|
|
100
107
|
<StackItem.Heading>
|
|
101
108
|
<StackItem.ResizeHandle />
|
|
102
109
|
</StackItem.Heading>
|
|
103
110
|
<Stack orientation='vertical' size='contain'>
|
|
104
|
-
{column.items?.map((card) => (
|
|
105
|
-
<StackItem.Root
|
|
111
|
+
{column.items?.map((card, cardIndex, cardsArray) => (
|
|
112
|
+
<StackItem.Root
|
|
113
|
+
key={card.id}
|
|
114
|
+
item={card}
|
|
115
|
+
prevSiblingId={cardIndex > 0 ? cardsArray[cardIndex - 1].id : undefined}
|
|
116
|
+
nextSiblingId={cardIndex < cardsArray.length - 1 ? cardsArray[cardIndex + 1].id : undefined}
|
|
117
|
+
>
|
|
106
118
|
<StackItem.Heading>
|
|
107
119
|
<StackItem.ResizeHandle />
|
|
108
120
|
</StackItem.Heading>
|
|
@@ -117,19 +129,19 @@ const StorybookStack = () => {
|
|
|
117
129
|
);
|
|
118
130
|
};
|
|
119
131
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
export const Default: Story = {
|
|
123
|
-
args: {
|
|
124
|
-
orientation: 'horizontal',
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const meta: Meta<typeof StorybookStack> = {
|
|
132
|
+
const meta: Meta<typeof DefaultStory> = {
|
|
129
133
|
title: 'ui/react-ui-stack/Stack',
|
|
130
|
-
component:
|
|
134
|
+
component: DefaultStory,
|
|
131
135
|
decorators: [withTheme],
|
|
132
136
|
argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
|
|
133
137
|
};
|
|
134
138
|
|
|
135
139
|
export default meta;
|
|
140
|
+
|
|
141
|
+
type Story = StoryObj<typeof DefaultStory>;
|
|
142
|
+
|
|
143
|
+
export const Default: Story = {
|
|
144
|
+
args: {
|
|
145
|
+
orientation: 'horizontal',
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -4,19 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
|
|
6
6
|
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
7
|
-
import React, {
|
|
7
|
+
import React, {
|
|
8
|
+
Children,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
type ComponentPropsWithRef,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useState,
|
|
13
|
+
useMemo,
|
|
14
|
+
useCallback,
|
|
15
|
+
useEffect,
|
|
16
|
+
} from 'react';
|
|
8
17
|
|
|
9
18
|
import { type ThemedClassName, ListItem } from '@dxos/react-ui';
|
|
10
19
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
20
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
21
|
+
import { useStackDropForElements } from '../../hooks';
|
|
22
|
+
import { StackContext } from '../StackContext';
|
|
23
|
+
import { type StackContextValue } from '../defs';
|
|
14
24
|
|
|
15
25
|
export type Orientation = 'horizontal' | 'vertical';
|
|
16
26
|
export type Size = 'intrinsic' | 'contain' | 'contain-fit-content';
|
|
17
27
|
|
|
18
28
|
export type StackProps = Omit<ThemedClassName<ComponentPropsWithRef<'div'>>, 'aria-orientation'> &
|
|
19
|
-
Partial<StackContextValue> & {
|
|
29
|
+
Partial<StackContextValue> & {
|
|
30
|
+
itemsCount?: number;
|
|
31
|
+
getDropElement?: (stackElement: HTMLDivElement) => HTMLDivElement;
|
|
32
|
+
separatorOnScroll?: number;
|
|
33
|
+
};
|
|
20
34
|
|
|
21
35
|
export const railGridHorizontal = 'grid-rows-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
|
|
22
36
|
export const railGridVertical = 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
|
|
@@ -40,6 +54,8 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
40
54
|
size = 'intrinsic',
|
|
41
55
|
onRearrange,
|
|
42
56
|
itemsCount = Children.count(children),
|
|
57
|
+
getDropElement,
|
|
58
|
+
separatorOnScroll,
|
|
43
59
|
...props
|
|
44
60
|
},
|
|
45
61
|
forwardedRef,
|
|
@@ -58,12 +74,29 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
58
74
|
|
|
59
75
|
const { dropping } = useStackDropForElements({
|
|
60
76
|
id: props.id,
|
|
61
|
-
element: stackElement,
|
|
77
|
+
element: getDropElement && stackElement ? getDropElement(stackElement) : stackElement,
|
|
78
|
+
scrollElement: stackElement,
|
|
62
79
|
selfDroppable,
|
|
63
80
|
orientation,
|
|
64
81
|
onRearrange,
|
|
65
82
|
});
|
|
66
83
|
|
|
84
|
+
const handleScroll = useCallback(() => {
|
|
85
|
+
if (stackElement && Number.isFinite(separatorOnScroll)) {
|
|
86
|
+
const scrollPosition = orientation === 'horizontal' ? stackElement.scrollLeft : stackElement.scrollTop;
|
|
87
|
+
const scrollSize = orientation === 'horizontal' ? stackElement.scrollWidth : stackElement.scrollHeight;
|
|
88
|
+
const clientSize = orientation === 'horizontal' ? stackElement.clientWidth : stackElement.clientHeight;
|
|
89
|
+
const separatorHost = stackElement.closest('[data-scroll-separator]');
|
|
90
|
+
if (separatorHost) {
|
|
91
|
+
separatorHost.setAttribute('data-scroll-separator', String(scrollPosition > separatorOnScroll!));
|
|
92
|
+
separatorHost.setAttribute(
|
|
93
|
+
'data-scroll-separator-end',
|
|
94
|
+
String(scrollSize - (scrollPosition + clientSize) > separatorOnScroll!),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, [stackElement, separatorOnScroll, orientation]);
|
|
99
|
+
|
|
67
100
|
const gridClasses = useMemo(() => {
|
|
68
101
|
if (!rail) {
|
|
69
102
|
return orientation === 'horizontal' ? 'grid-rows-1 pli-1' : 'grid-cols-1 plb-1';
|
|
@@ -75,6 +108,22 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
75
108
|
}
|
|
76
109
|
}, [rail, orientation, size]);
|
|
77
110
|
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!(stackElement && Number.isFinite(separatorOnScroll))) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const observer = new MutationObserver(() => {
|
|
117
|
+
handleScroll();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
observer.observe(stackElement, { childList: true, subtree: true });
|
|
121
|
+
|
|
122
|
+
return () => {
|
|
123
|
+
observer.disconnect();
|
|
124
|
+
};
|
|
125
|
+
}, [stackElement, handleScroll]);
|
|
126
|
+
|
|
78
127
|
return (
|
|
79
128
|
<StackContext.Provider value={{ orientation, rail, size, onRearrange }}>
|
|
80
129
|
<div
|
|
@@ -93,6 +142,7 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
93
142
|
aria-orientation={orientation}
|
|
94
143
|
style={styles}
|
|
95
144
|
ref={composedItemRef}
|
|
145
|
+
{...(Number.isFinite(separatorOnScroll) && { onScroll: handleScroll })}
|
|
96
146
|
>
|
|
97
147
|
{children}
|
|
98
148
|
{selfDroppable && dropping && (
|
|
@@ -2,22 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
6
5
|
import { createContext, useContext } from 'react';
|
|
7
6
|
|
|
8
|
-
import { type Size as DndSize } from '@dxos/react-ui-dnd';
|
|
9
|
-
|
|
10
7
|
import { type Orientation, type Size } from './Stack';
|
|
11
|
-
|
|
12
|
-
export type StackItemSize = DndSize;
|
|
13
|
-
|
|
14
|
-
export type StackItemData = { id: string; type: 'column' | 'card' };
|
|
15
|
-
|
|
16
|
-
export type StackItemRearrangeHandler<Data extends { id: string } = StackItemData> = (
|
|
17
|
-
source: Data,
|
|
18
|
-
target: Data,
|
|
19
|
-
closestEdge: Edge | null,
|
|
20
|
-
) => void;
|
|
8
|
+
import { type StackItemSize, type StackItemRearrangeHandler } from './defs';
|
|
21
9
|
|
|
22
10
|
export type StackContextValue = {
|
|
23
11
|
orientation: Orientation;
|
|
@@ -34,16 +22,36 @@ export const StackContext = createContext<StackContextValue>({
|
|
|
34
22
|
|
|
35
23
|
export const useStack = () => useContext(StackContext);
|
|
36
24
|
|
|
25
|
+
export type ItemDragState =
|
|
26
|
+
| {
|
|
27
|
+
type: 'idle';
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: 'preview';
|
|
31
|
+
container: HTMLElement;
|
|
32
|
+
item: any;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
type: 'is-dragging';
|
|
36
|
+
item: any;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const idle: ItemDragState = { type: 'idle' };
|
|
40
|
+
|
|
37
41
|
export type StackItemContextValue = {
|
|
38
42
|
selfDragHandleRef: (element: HTMLDivElement | null) => void;
|
|
39
43
|
size: StackItemSize;
|
|
40
44
|
setSize: (nextSize: StackItemSize, commit?: boolean) => void;
|
|
45
|
+
state: ItemDragState;
|
|
46
|
+
setState: (state: ItemDragState) => void;
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
export const StackItemContext = createContext<StackItemContextValue>({
|
|
44
50
|
selfDragHandleRef: () => {},
|
|
45
51
|
size: 'min-content',
|
|
46
52
|
setSize: () => {},
|
|
53
|
+
state: idle,
|
|
54
|
+
setState: () => {},
|
|
47
55
|
});
|
|
48
56
|
|
|
49
57
|
export const useStackItem = () => useContext(StackItemContext);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { Icon, DropdownMenu } from '@dxos/react-ui';
|
|
11
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
12
|
+
|
|
13
|
+
import { StackItem } from './StackItem';
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof StackItem.Root> = {
|
|
16
|
+
title: 'ui/react-ui-stack/StackItem',
|
|
17
|
+
component: StackItem.Root,
|
|
18
|
+
render: (args) => (
|
|
19
|
+
<StackItem.Root role='section' {...args} classNames='w-[20rem] border border-separator'>
|
|
20
|
+
<StackItem.Heading>
|
|
21
|
+
<span className='sr-only'>Title</span>
|
|
22
|
+
<div role='none' className='sticky -block-start-px bg-[--sticky-bg] p-1 is-full'>
|
|
23
|
+
<DropdownMenu.Root>
|
|
24
|
+
<DropdownMenu.Trigger asChild>
|
|
25
|
+
<StackItem.SigilButton>
|
|
26
|
+
<Icon icon={'ph--dots-three--regular'} size={5} />
|
|
27
|
+
</StackItem.SigilButton>
|
|
28
|
+
</DropdownMenu.Trigger>
|
|
29
|
+
</DropdownMenu.Root>
|
|
30
|
+
</div>
|
|
31
|
+
</StackItem.Heading>
|
|
32
|
+
<StackItem.Content classNames='p-2'>Content</StackItem.Content>
|
|
33
|
+
</StackItem.Root>
|
|
34
|
+
),
|
|
35
|
+
decorators: [withTheme],
|
|
36
|
+
parameters: {
|
|
37
|
+
layout: 'centered',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
|
|
43
|
+
type Story = StoryObj<typeof StackItem.Root>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
item: { id: '1' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
6
|
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
7
|
import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
|
|
8
|
-
import {
|
|
8
|
+
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
9
9
|
import {
|
|
10
10
|
attachClosestEdge,
|
|
11
11
|
extractClosestEdge,
|
|
@@ -13,13 +13,20 @@ import {
|
|
|
13
13
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
14
14
|
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
15
15
|
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
16
|
-
import React, {
|
|
16
|
+
import React, {
|
|
17
|
+
forwardRef,
|
|
18
|
+
useLayoutEffect,
|
|
19
|
+
useState,
|
|
20
|
+
type ComponentPropsWithRef,
|
|
21
|
+
useCallback,
|
|
22
|
+
type ReactNode,
|
|
23
|
+
} from 'react';
|
|
24
|
+
import { createPortal } from 'react-dom';
|
|
17
25
|
|
|
18
26
|
import { type ThemedClassName, ListItem } from '@dxos/react-ui';
|
|
19
27
|
import { resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';
|
|
20
28
|
import { mx } from '@dxos/react-ui-theme';
|
|
21
29
|
|
|
22
|
-
import { useStack, StackItemContext, type StackItemSize, type StackItemData } from './StackContext';
|
|
23
30
|
import { StackItemContent, type StackItemContentProps } from './StackItemContent';
|
|
24
31
|
import { StackItemDragHandle, type StackItemDragHandleProps } from './StackItemDragHandle';
|
|
25
32
|
import {
|
|
@@ -36,15 +43,19 @@ import {
|
|
|
36
43
|
type StackItemSigilButtonProps,
|
|
37
44
|
StackItemSigilButton,
|
|
38
45
|
} from './StackItemSigil';
|
|
46
|
+
import { useStack, StackItemContext, idle, type ItemDragState, useStackItem } from '../StackContext';
|
|
47
|
+
import { type StackItemSize, type StackItemData } from '../defs';
|
|
39
48
|
|
|
40
49
|
// NOTE: 48rem fills the screen on a MacbookPro with the sidebars closed.
|
|
41
50
|
export const DEFAULT_HORIZONTAL_SIZE = 48 satisfies StackItemSize;
|
|
42
51
|
export const DEFAULT_VERTICAL_SIZE = 'min-content' satisfies StackItemSize;
|
|
43
52
|
export const DEFAULT_EXTRINSIC_SIZE = DEFAULT_HORIZONTAL_SIZE satisfies StackItemSize;
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
type StackItemRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> & {
|
|
46
55
|
item: Omit<StackItemData, 'type'>;
|
|
47
56
|
order?: number;
|
|
57
|
+
prevSiblingId?: string;
|
|
58
|
+
nextSiblingId?: string;
|
|
48
59
|
size?: StackItemSize;
|
|
49
60
|
onSizeChange?: (nextSize: StackItemSize) => void;
|
|
50
61
|
role?: 'article' | 'section';
|
|
@@ -62,6 +73,8 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
62
73
|
onSizeChange,
|
|
63
74
|
role,
|
|
64
75
|
order,
|
|
76
|
+
prevSiblingId,
|
|
77
|
+
nextSiblingId,
|
|
65
78
|
style,
|
|
66
79
|
disableRearrange,
|
|
67
80
|
focusIndicatorVariant = 'over-all',
|
|
@@ -72,6 +85,8 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
72
85
|
const [itemElement, itemRef] = useState<HTMLDivElement | null>(null);
|
|
73
86
|
const [selfDragHandleElement, selfDragHandleRef] = useState<HTMLDivElement | null>(null);
|
|
74
87
|
const [closestEdge, setEdge] = useState<Edge | null>(null);
|
|
88
|
+
const [sourceId, setSourceId] = useState<string | null>(null);
|
|
89
|
+
const [dragState, setDragState] = useState<ItemDragState>(idle);
|
|
75
90
|
const { orientation, rail, onRearrange } = useStack();
|
|
76
91
|
const [size = orientation === 'horizontal' ? DEFAULT_HORIZONTAL_SIZE : DEFAULT_VERTICAL_SIZE, setInternalSize] =
|
|
77
92
|
useState(propsSize);
|
|
@@ -104,18 +119,28 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
104
119
|
getInitialData: () => ({ id: item.id, type }),
|
|
105
120
|
onGenerateDragPreview: ({ nativeSetDragImage, source, location }) => {
|
|
106
121
|
document.body.setAttribute('data-drag-preview', 'true');
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
122
|
+
const offsetFn = preserveOffsetOnSource({ element: source.element, input: location.current.input });
|
|
123
|
+
const rect = source.element.getBoundingClientRect();
|
|
124
|
+
setCustomNativeDragPreview({
|
|
125
|
+
nativeSetDragImage,
|
|
126
|
+
getOffset: ({ container }) => {
|
|
127
|
+
return offsetFn({ container });
|
|
128
|
+
},
|
|
129
|
+
render: ({ container }) => {
|
|
130
|
+
container.style.width = rect.width + 'px';
|
|
131
|
+
setDragState({ type: 'preview', container, item });
|
|
132
|
+
return () => {};
|
|
133
|
+
},
|
|
110
134
|
});
|
|
111
|
-
nativeSetDragImage?.(source.element, x, y);
|
|
112
135
|
},
|
|
113
136
|
onDragStart: () => {
|
|
114
137
|
document.body.removeAttribute('data-drag-preview');
|
|
115
138
|
itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'active');
|
|
139
|
+
setDragState({ type: 'is-dragging', item });
|
|
116
140
|
},
|
|
117
141
|
onDrop: () => {
|
|
118
142
|
itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'idle');
|
|
143
|
+
setDragState(idle);
|
|
119
144
|
},
|
|
120
145
|
}),
|
|
121
146
|
dropTargetForElements({
|
|
@@ -129,16 +154,22 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
129
154
|
onDragEnter: ({ self, source }) => {
|
|
130
155
|
if (source.data.type === self.data.type) {
|
|
131
156
|
setEdge(extractClosestEdge(self.data));
|
|
157
|
+
setSourceId(source.data.id as string);
|
|
132
158
|
}
|
|
133
159
|
},
|
|
134
160
|
onDrag: ({ self, source }) => {
|
|
135
161
|
if (source.data.type === self.data.type) {
|
|
136
162
|
setEdge(extractClosestEdge(self.data));
|
|
163
|
+
setSourceId(source.data.id as string);
|
|
137
164
|
}
|
|
138
165
|
},
|
|
139
|
-
onDragLeave: () =>
|
|
166
|
+
onDragLeave: () => {
|
|
167
|
+
setEdge(null);
|
|
168
|
+
setSourceId(null);
|
|
169
|
+
},
|
|
140
170
|
onDrop: ({ self, source }) => {
|
|
141
171
|
setEdge(null);
|
|
172
|
+
setSourceId(null);
|
|
142
173
|
if (source.data.type === self.data.type) {
|
|
143
174
|
onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
|
|
144
175
|
}
|
|
@@ -149,8 +180,42 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
149
180
|
|
|
150
181
|
const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
|
|
151
182
|
|
|
183
|
+
// Determine if the drop would result in any changes
|
|
184
|
+
const shouldShowDropIndicator = () => {
|
|
185
|
+
if (!closestEdge || !sourceId) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Don't show indicator when dragged item is over itself
|
|
190
|
+
if (sourceId === item.id) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Don't show indicator when dragged item is over the trailing edge of its previous sibling
|
|
195
|
+
const isTrailingEdgeOfPrevSibling =
|
|
196
|
+
prevSiblingId !== undefined &&
|
|
197
|
+
sourceId === prevSiblingId &&
|
|
198
|
+
((orientation === 'horizontal' && closestEdge === 'left') ||
|
|
199
|
+
(orientation === 'vertical' && closestEdge === 'top'));
|
|
200
|
+
if (isTrailingEdgeOfPrevSibling) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Don't show indicator when dragged item is over the leading edge of its next sibling
|
|
205
|
+
const isLeadingEdgeOfNextSibling =
|
|
206
|
+
nextSiblingId !== undefined &&
|
|
207
|
+
sourceId === nextSiblingId &&
|
|
208
|
+
((orientation === 'horizontal' && closestEdge === 'right') ||
|
|
209
|
+
(orientation === 'vertical' && closestEdge === 'bottom'));
|
|
210
|
+
if (isLeadingEdgeOfNextSibling) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
};
|
|
216
|
+
|
|
152
217
|
return (
|
|
153
|
-
<StackItemContext.Provider value={{ selfDragHandleRef, size, setSize }}>
|
|
218
|
+
<StackItemContext.Provider value={{ selfDragHandleRef, size, setSize, state: dragState, setState: setDragState }}>
|
|
154
219
|
<Root
|
|
155
220
|
{...props}
|
|
156
221
|
tabIndex={0}
|
|
@@ -178,13 +243,24 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
178
243
|
ref={composedItemRef}
|
|
179
244
|
>
|
|
180
245
|
{children}
|
|
181
|
-
{
|
|
246
|
+
{shouldShowDropIndicator() && closestEdge && (
|
|
247
|
+
<ListItem.DropIndicator lineInset={8} terminalInset={-8} edge={closestEdge} />
|
|
248
|
+
)}
|
|
182
249
|
</Root>
|
|
183
250
|
</StackItemContext.Provider>
|
|
184
251
|
);
|
|
185
252
|
},
|
|
186
253
|
);
|
|
187
254
|
|
|
255
|
+
type StackItemDragPreviewProps = {
|
|
256
|
+
children: ({ item }: { item: any }) => ReactNode;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const StackItemDragPreview = ({ children }: StackItemDragPreviewProps) => {
|
|
260
|
+
const { state } = useStackItem();
|
|
261
|
+
return state?.type === 'preview' ? createPortal(children({ item: state.item }), state.container) : null;
|
|
262
|
+
};
|
|
263
|
+
|
|
188
264
|
export const StackItem = {
|
|
189
265
|
Root: StackItemRoot,
|
|
190
266
|
Content: StackItemContent,
|
|
@@ -194,9 +270,11 @@ export const StackItem = {
|
|
|
194
270
|
DragHandle: StackItemDragHandle,
|
|
195
271
|
Sigil: StackItemSigil,
|
|
196
272
|
SigilButton: StackItemSigilButton,
|
|
273
|
+
DragPreview: StackItemDragPreview,
|
|
197
274
|
};
|
|
198
275
|
|
|
199
276
|
export type {
|
|
277
|
+
StackItemRootProps,
|
|
200
278
|
StackItemContentProps,
|
|
201
279
|
StackItemHeadingProps,
|
|
202
280
|
StackItemHeadingLabelProps,
|
|
@@ -205,4 +283,5 @@ export type {
|
|
|
205
283
|
StackItemSigilProps,
|
|
206
284
|
StackItemSigilButtonProps,
|
|
207
285
|
StackItemSigilAction,
|
|
286
|
+
StackItemDragPreviewProps,
|
|
208
287
|
};
|
|
@@ -7,7 +7,7 @@ import React, { type ComponentPropsWithoutRef, forwardRef } from 'react';
|
|
|
7
7
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
8
|
import { mx } from '@dxos/react-ui-theme';
|
|
9
9
|
|
|
10
|
-
import { useStack } from '
|
|
10
|
+
import { useStack } from '../StackContext';
|
|
11
11
|
|
|
12
12
|
export type StackItemContentProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> & {
|
|
13
13
|
/**
|
|
@@ -53,6 +53,7 @@ export const StackItemContent = forwardRef<HTMLDivElement, StackItemContentProps
|
|
|
53
53
|
...(statusbar ? ['var(--statusbar-size)'] : []),
|
|
54
54
|
].join(' '),
|
|
55
55
|
}}
|
|
56
|
+
data-popover-collision-boundary={true}
|
|
56
57
|
ref={forwardedRef}
|
|
57
58
|
>
|
|
58
59
|
{children}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { Slot } from '@radix-ui/react-slot';
|
|
6
6
|
import React, { type ComponentPropsWithoutRef } from 'react';
|
|
7
7
|
|
|
8
|
-
import { useStackItem } from '
|
|
8
|
+
import { useStackItem } from '../StackContext';
|
|
9
9
|
|
|
10
|
-
export type StackItemDragHandleProps = ComponentPropsWithoutRef<'button'> & { asChild
|
|
10
|
+
export type StackItemDragHandleProps = ComponentPropsWithoutRef<'button'> & { asChild?: boolean };
|
|
11
11
|
|
|
12
12
|
export const StackItemDragHandle = ({ asChild, children }: StackItemDragHandleProps) => {
|
|
13
13
|
const { selfDragHandleRef } = useStackItem();
|
|
@@ -3,34 +3,38 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
6
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
7
|
import React, { type ComponentPropsWithoutRef, type ComponentPropsWithRef, forwardRef } from 'react';
|
|
7
8
|
|
|
8
9
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
10
|
import { useAttention, type AttendableId, type Related } from '@dxos/react-ui-attention';
|
|
10
11
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
12
|
|
|
12
|
-
import { useStack } from '
|
|
13
|
+
import { useStack } from '../StackContext';
|
|
13
14
|
|
|
14
|
-
export type StackItemHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'
|
|
15
|
+
export type StackItemHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> & { asChild?: boolean };
|
|
15
16
|
|
|
16
|
-
export const StackItemHeading = ({ children, classNames, ...props }: StackItemHeadingProps) => {
|
|
17
|
+
export const StackItemHeading = ({ children, classNames, asChild, ...props }: StackItemHeadingProps) => {
|
|
17
18
|
const { orientation } = useStack();
|
|
18
19
|
const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
|
|
19
20
|
|
|
21
|
+
const Root = asChild ? Slot : 'div';
|
|
22
|
+
|
|
20
23
|
return (
|
|
21
|
-
<
|
|
24
|
+
<Root
|
|
22
25
|
role='heading'
|
|
23
26
|
{...props}
|
|
24
27
|
tabIndex={0}
|
|
25
28
|
{...focusableGroupAttrs}
|
|
26
29
|
className={mx(
|
|
27
|
-
'flex items-center dx-focus-ring-inset-over-all relative !border-is-0',
|
|
30
|
+
'flex items-center dx-focus-ring-inset-over-all relative !border-is-0 bg-headerSurface border-transparent [[data-scroll-separator="true"]_&]:border-subduedSeparator',
|
|
28
31
|
orientation === 'horizontal' ? 'bs-[--rail-size]' : 'is-[--rail-size] flex-col',
|
|
32
|
+
orientation === 'horizontal' ? 'border-be' : 'border-ie',
|
|
29
33
|
classNames,
|
|
30
34
|
)}
|
|
31
35
|
>
|
|
32
36
|
{children}
|
|
33
|
-
</
|
|
37
|
+
</Root>
|
|
34
38
|
);
|
|
35
39
|
};
|
|
36
40
|
|
|
@@ -6,8 +6,8 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { ResizeHandle } from '@dxos/react-ui-dnd';
|
|
8
8
|
|
|
9
|
-
import { useStack, useStackItem } from './StackContext';
|
|
10
9
|
import { DEFAULT_EXTRINSIC_SIZE } from './StackItem';
|
|
10
|
+
import { useStack, useStackItem } from '../StackContext';
|
|
11
11
|
|
|
12
12
|
const MIN_WIDTH = 20;
|
|
13
13
|
const MIN_HEIGHT = 3;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { Fragment, type PropsWithChildren, forwardRef,
|
|
5
|
+
import React, { Fragment, type PropsWithChildren, forwardRef, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type ActionLike } from '@dxos/app-graph';
|
|
8
8
|
import { keySymbols } from '@dxos/keyboard';
|
|
@@ -12,7 +12,7 @@ import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
|
12
12
|
import { getHostPlatform } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { MenuSignifierHorizontal } from './MenuSignifier';
|
|
15
|
-
import { translationKey } from '
|
|
15
|
+
import { translationKey } from '../../translations';
|
|
16
16
|
|
|
17
17
|
export type KeyBinding = {
|
|
18
18
|
windows?: string;
|
|
@@ -62,7 +62,6 @@ export type StackItemSigilProps = PropsWithChildren<
|
|
|
62
62
|
export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>(
|
|
63
63
|
({ actions: actionGroups, onAction, triggerLabel, attendableId, icon, related, children }, forwardedRef) => {
|
|
64
64
|
const { t } = useTranslation(translationKey);
|
|
65
|
-
const suppressNextTooltip = useRef(false);
|
|
66
65
|
|
|
67
66
|
const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
|
|
68
67
|
|
|
@@ -87,17 +86,7 @@ export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
return (
|
|
90
|
-
<DropdownMenu.Root
|
|
91
|
-
{...{
|
|
92
|
-
open: optionsMenuOpen,
|
|
93
|
-
onOpenChange: (nextOpen: boolean) => {
|
|
94
|
-
if (!nextOpen) {
|
|
95
|
-
suppressNextTooltip.current = true;
|
|
96
|
-
}
|
|
97
|
-
return setOptionsMenuOpen(nextOpen);
|
|
98
|
-
},
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
89
|
+
<DropdownMenu.Root open={optionsMenuOpen} onOpenChange={setOptionsMenuOpen}>
|
|
101
90
|
<DropdownMenu.Trigger asChild ref={forwardedRef}>
|
|
102
91
|
{button}
|
|
103
92
|
</DropdownMenu.Trigger>
|
|
@@ -127,7 +116,6 @@ export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>
|
|
|
127
116
|
}
|
|
128
117
|
event.stopPropagation();
|
|
129
118
|
// TODO(thure): Why does Dialog’s modal-ness cause issues if we don’t explicitly close the menu here?
|
|
130
|
-
suppressNextTooltip.current = true;
|
|
131
119
|
setOptionsMenuOpen(false);
|
|
132
120
|
onAction?.(action);
|
|
133
121
|
}}
|