@dxos/react-ui-stack 0.7.4 → 0.7.5-labs.071a3e2

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 (43) hide show
  1. package/dist/lib/browser/index.mjs +228 -130
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +229 -129
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +228 -130
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/LayoutControls.d.ts.map +1 -1
  11. package/dist/types/src/components/Stack.d.ts +3 -0
  12. package/dist/types/src/components/Stack.d.ts.map +1 -1
  13. package/dist/types/src/components/Stack.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/StackContext.d.ts +9 -4
  15. package/dist/types/src/components/StackContext.d.ts.map +1 -1
  16. package/dist/types/src/components/StackItem.d.ts +14 -9
  17. package/dist/types/src/components/StackItem.d.ts.map +1 -1
  18. package/dist/types/src/components/StackItemContent.d.ts +35 -2
  19. package/dist/types/src/components/StackItemContent.d.ts.map +1 -1
  20. package/dist/types/src/components/StackItemDragHandle.d.ts +6 -0
  21. package/dist/types/src/components/StackItemDragHandle.d.ts.map +1 -0
  22. package/dist/types/src/components/StackItemHeading.d.ts.map +1 -1
  23. package/dist/types/src/components/StackItemResizeHandle.d.ts +1 -0
  24. package/dist/types/src/components/StackItemResizeHandle.d.ts.map +1 -1
  25. package/dist/types/src/components/StackItemSigil.d.ts.map +1 -1
  26. package/dist/types/src/components/index.d.ts +1 -0
  27. package/dist/types/src/components/index.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -0
  29. package/package.json +28 -26
  30. package/src/components/LayoutControls.tsx +1 -3
  31. package/src/components/Stack.stories.tsx +5 -4
  32. package/src/components/Stack.tsx +73 -10
  33. package/src/components/StackContext.tsx +13 -4
  34. package/src/components/StackItem.tsx +49 -16
  35. package/src/components/StackItemContent.tsx +48 -33
  36. package/src/components/StackItemDragHandle.tsx +22 -0
  37. package/src/components/StackItemHeading.tsx +3 -5
  38. package/src/components/StackItemResizeHandle.tsx +27 -15
  39. package/src/components/StackItemSigil.tsx +90 -103
  40. package/src/components/index.ts +1 -0
  41. package/dist/types/src/testing/EditorContent.d.ts +0 -8
  42. package/dist/types/src/testing/EditorContent.d.ts.map +0 -1
  43. package/src/testing/EditorContent.tsx +0 -60
@@ -1 +1 @@
1
- {"version":3,"file":"StackItemHeading.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemHeading.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAc,MAAM,OAAO,CAAC;AAErG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAKzF,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC;AAErF,eAAO,MAAM,gBAAgB,uCAAwC,qBAAqB,sBAoBzF,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,eAAe,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/G,eAAO,MAAM,qBAAqB,oHAejC,CAAC"}
1
+ {"version":3,"file":"StackItemHeading.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemHeading.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAc,MAAM,OAAO,CAAC;AAErG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAKzF,MAAM,MAAM,qBAAqB,GAAG,eAAe,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC;AAErF,eAAO,MAAM,gBAAgB,uCAAwC,qBAAqB,sBAkBzF,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,eAAe,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/G,eAAO,MAAM,qBAAqB,oHAejC,CAAC"}
@@ -1,3 +1,4 @@
1
1
  import React from 'react';
2
+ export type StackItemResizeHandleProps = {};
2
3
  export declare const StackItemResizeHandle: () => React.JSX.Element;
3
4
  //# sourceMappingURL=StackItemResizeHandle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StackItemResizeHandle.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemResizeHandle.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAoBvD,eAAO,MAAM,qBAAqB,yBAoEjC,CAAC"}
1
+ {"version":3,"file":"StackItemResizeHandle.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemResizeHandle.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAwBvD,MAAM,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBA0EjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"StackItemSigil.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemSigil.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAY,KAAK,iBAAiB,EAAgC,MAAM,OAAO,CAAC;AAE9F,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAgB,MAAM,0BAA0B,CAAC;AAOzF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;AAElF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9F,eAAO,MAAM,oBAAoB,kHAgBhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CACjD;IACE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD,GAAG,OAAO,CACZ,CAAC;AAEF,eAAO,MAAM,cAAc;mBARR,MAAM;kBACP,MAAM;cACV,oBAAoB,EAAE,EAAE;UAC5B,MAAM;eACD,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI;;;2CA6GpD,CAAC"}
1
+ {"version":3,"file":"StackItemSigil.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemSigil.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAY,KAAK,iBAAiB,EAAgC,MAAM,OAAO,CAAC;AAE9F,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAU,KAAK,WAAW,EAAyD,MAAM,gBAAgB,CAAC;AACjH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAgB,MAAM,0BAA0B,CAAC;AAOzF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;AAElF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9F,eAAO,MAAM,oBAAoB,kHAgBhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CACjD;IACE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD,GAAG,OAAO,CACZ,CAAC;AAEF,eAAO,MAAM,cAAc;mBARR,MAAM;kBACP,MAAM;cACV,oBAAoB,EAAE,EAAE;UAC5B,MAAM;eACD,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI;;;2CAwGpD,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from './Stack';
2
2
  export * from './StackItem';
3
3
  export * from './LayoutControls';
4
+ export * from './StackContext';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":"5.7.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-stack",
3
- "version": "0.7.4",
3
+ "version": "0.7.5-labs.071a3e2",
4
4
  "description": "A stack component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -36,21 +36,23 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
39
+ "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
39
40
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
40
41
  "@effect/schema": "^0.75.5",
41
- "@fluentui/react-tabster": "^9.19.0",
42
- "@radix-ui/primitive": "^1.0.0",
43
- "@radix-ui/react-compose-refs": "^1.0.0",
44
- "@radix-ui/react-context": "^1.0.0",
45
- "@radix-ui/react-menu": "^2.0.6",
46
- "@radix-ui/react-use-controllable-state": "^1.0.0",
42
+ "@fluentui/react-tabster": "9.23.3",
43
+ "@radix-ui/primitive": "1.1.1",
44
+ "@radix-ui/react-compose-refs": "1.1.1",
45
+ "@radix-ui/react-context": "1.1.1",
46
+ "@radix-ui/react-menu": "2.1.6",
47
+ "@radix-ui/react-slot": "1.1.2",
48
+ "@radix-ui/react-use-controllable-state": "1.1.0",
47
49
  "react-resize-detector": "^11.0.1",
48
- "@dxos/echo-schema": "0.7.4",
49
- "@dxos/react-ui-attention": "0.7.4",
50
- "@dxos/live-object": "0.7.4",
51
- "@dxos/react-ui-mosaic": "0.7.4",
52
- "@dxos/util": "0.7.4",
53
- "@dxos/keyboard": "0.7.4"
50
+ "@dxos/echo-schema": "0.7.5-labs.071a3e2",
51
+ "@dxos/keyboard": "0.7.5-labs.071a3e2",
52
+ "@dxos/react-ui-attention": "0.7.5-labs.071a3e2",
53
+ "@dxos/live-object": "0.7.5-labs.071a3e2",
54
+ "@dxos/react-ui-mosaic": "0.7.5-labs.071a3e2",
55
+ "@dxos/util": "0.7.5-labs.071a3e2"
54
56
  },
55
57
  "devDependencies": {
56
58
  "@phosphor-icons/react": "^2.1.5",
@@ -59,24 +61,24 @@
59
61
  "react": "~18.2.0",
60
62
  "react-dom": "~18.2.0",
61
63
  "vite": "5.4.7",
62
- "@dxos/app-graph": "0.7.4",
63
- "@dxos/client": "0.7.4",
64
- "@dxos/echo-schema": "0.7.4",
65
- "@dxos/react-ui": "0.7.4",
66
- "@dxos/random": "0.7.4",
67
- "@dxos/react-ui-theme": "0.7.4",
68
- "@dxos/react-ui-editor": "0.7.4",
69
- "@dxos/storybook-utils": "0.7.4",
70
- "@dxos/test-utils": "0.7.4"
64
+ "@dxos/app-graph": "0.7.5-labs.071a3e2",
65
+ "@dxos/client": "0.7.5-labs.071a3e2",
66
+ "@dxos/echo-schema": "0.7.5-labs.071a3e2",
67
+ "@dxos/random": "0.7.5-labs.071a3e2",
68
+ "@dxos/react-ui": "0.7.5-labs.071a3e2",
69
+ "@dxos/react-ui-editor": "0.7.5-labs.071a3e2",
70
+ "@dxos/storybook-utils": "0.7.5-labs.071a3e2",
71
+ "@dxos/test-utils": "0.7.5-labs.071a3e2",
72
+ "@dxos/react-ui-theme": "0.7.5-labs.071a3e2"
71
73
  },
72
74
  "peerDependencies": {
73
75
  "@phosphor-icons/react": "^2.1.5",
74
76
  "react": "~18.2.0",
75
77
  "react-dom": "~18.2.0",
76
- "@dxos/client": "0.7.4",
77
- "@dxos/random": "0.7.4",
78
- "@dxos/react-ui": "0.7.4",
79
- "@dxos/react-ui-theme": "0.7.4"
78
+ "@dxos/client": "0.7.5-labs.071a3e2",
79
+ "@dxos/random": "0.7.5-labs.071a3e2",
80
+ "@dxos/react-ui-theme": "0.7.5-labs.071a3e2",
81
+ "@dxos/react-ui": "0.7.5-labs.071a3e2"
80
82
  },
81
83
  "publishConfig": {
82
84
  "access": "public"
@@ -44,9 +44,7 @@ const LayoutControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'>
44
44
  </Button>
45
45
  </Tooltip.Trigger>
46
46
  <Tooltip.Portal>
47
- <Tooltip.Content side='bottom' classNames='z-[70]'>
48
- {label}
49
- </Tooltip.Content>
47
+ <Tooltip.Content side='bottom'>{label}</Tooltip.Content>
50
48
  </Tooltip.Portal>
51
49
  </Tooltip.Root>
52
50
  );
@@ -10,7 +10,8 @@ import { faker } from '@dxos/random';
10
10
  import { withTheme } from '@dxos/storybook-utils';
11
11
 
12
12
  import { Stack } from './Stack';
13
- import { StackItem, type StackItemData } from './StackItem';
13
+ import { type StackItemData } from './StackContext';
14
+ import { StackItem } from './StackItem';
14
15
 
15
16
  type StoryStackItem = {
16
17
  id: string;
@@ -93,15 +94,15 @@ const StorybookStack = () => {
93
94
 
94
95
  return (
95
96
  <main className='fixed inset-0'>
96
- <Stack orientation='horizontal' size='contain'>
97
+ <Stack orientation='horizontal' size='contain' onRearrange={reorderItem}>
97
98
  {columns.map((column) => (
98
- <StackItem.Root key={column.id} item={column} onRearrange={reorderItem}>
99
+ <StackItem.Root key={column.id} item={column}>
99
100
  <StackItem.Heading>
100
101
  <StackItem.ResizeHandle />
101
102
  </StackItem.Heading>
102
103
  <Stack orientation='vertical' size='contain'>
103
104
  {column.items?.map((card) => (
104
- <StackItem.Root key={card.id} item={card} onRearrange={reorderItem}>
105
+ <StackItem.Root key={card.id} item={card}>
105
106
  <StackItem.Heading>
106
107
  <StackItem.ResizeHandle />
107
108
  </StackItem.Heading>
@@ -1,14 +1,25 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
-
4
+ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
5
+ import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
+ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
7
+ import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
5
8
  import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
- import React, { Children, type CSSProperties, type ComponentPropsWithRef, forwardRef } from 'react';
9
+ import { composeRefs } from '@radix-ui/react-compose-refs';
10
+ import React, {
11
+ Children,
12
+ type CSSProperties,
13
+ type ComponentPropsWithRef,
14
+ forwardRef,
15
+ useLayoutEffect,
16
+ useState,
17
+ } from 'react';
7
18
 
8
- import { type ThemedClassName } from '@dxos/react-ui';
19
+ import { type ThemedClassName, ListItem } from '@dxos/react-ui';
9
20
  import { mx } from '@dxos/react-ui-theme';
10
21
 
11
- import { type StackContextValue, StackContext } from './StackContext';
22
+ import { type StackContextValue, StackContext, type StackItemData } from './StackContext';
12
23
 
13
24
  export type Orientation = 'horizontal' | 'vertical';
14
25
  export type Size = 'intrinsic' | 'contain';
@@ -20,6 +31,8 @@ export const railGridHorizontal = 'grid-rows-[[rail-start]_var(--rail-size)_[con
20
31
 
21
32
  export const railGridVertical = 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
22
33
 
34
+ export const autoScrollRootAttributes = { 'data-drag-autoscroll': 'idle' };
35
+
23
36
  export const Stack = forwardRef<HTMLDivElement, StackProps>(
24
37
  (
25
38
  {
@@ -28,13 +41,17 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
28
41
  style,
29
42
  orientation = 'vertical',
30
43
  rail = true,
31
- separators = true,
32
44
  size = 'intrinsic',
45
+ onRearrange,
33
46
  itemsCount = Children.count(children),
34
47
  ...props
35
48
  },
36
49
  forwardedRef,
37
50
  ) => {
51
+ const [stackElement, stackRef] = useState<HTMLDivElement | null>(null);
52
+ const composedItemRef = composeRefs<HTMLDivElement>(stackRef, forwardedRef);
53
+ const [dropping, setDropping] = useState(false);
54
+
38
55
  const arrowNavigationGroup = useArrowNavigationGroup({ axis: orientation });
39
56
 
40
57
  const styles: CSSProperties = {
@@ -42,8 +59,46 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
42
59
  ...style,
43
60
  };
44
61
 
62
+ const selfDroppable = !!(itemsCount < 1 && onRearrange && props.id);
63
+
64
+ useLayoutEffect(() => {
65
+ if (!stackElement || !selfDroppable) {
66
+ return;
67
+ }
68
+ const acceptSourceType = orientation === 'horizontal' ? 'column' : 'card';
69
+ return combine(
70
+ dropTargetForElements({
71
+ element: stackElement,
72
+ getData: ({ input, element }) => {
73
+ return attachClosestEdge(
74
+ { id: props.id, type: orientation === 'horizontal' ? 'card' : 'column' },
75
+ { input, element, allowedEdges: [orientation === 'horizontal' ? 'left' : 'top'] },
76
+ );
77
+ },
78
+ onDragEnter: ({ source }) => {
79
+ if (source.data.type === acceptSourceType) {
80
+ setDropping(true);
81
+ }
82
+ },
83
+ onDrag: ({ source }) => {
84
+ if (source.data.type === acceptSourceType) {
85
+ setDropping(true);
86
+ }
87
+ },
88
+ onDragLeave: () => setDropping(false),
89
+ onDrop: ({ self, source }) => {
90
+ setDropping(false);
91
+ if (source.data.type === acceptSourceType && selfDroppable) {
92
+ onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
93
+ }
94
+ },
95
+ }),
96
+ autoScrollForElements({ element: stackElement, getAllowedAxis: () => orientation }),
97
+ );
98
+ }, [stackElement, selfDroppable, orientation]);
99
+
45
100
  return (
46
- <StackContext.Provider value={{ orientation, rail, size, separators }}>
101
+ <StackContext.Provider value={{ orientation, rail, size, onRearrange }}>
47
102
  <div
48
103
  {...props}
49
104
  {...arrowNavigationGroup}
@@ -54,20 +109,28 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
54
109
  ? railGridHorizontal
55
110
  : railGridVertical
56
111
  : orientation === 'horizontal'
57
- ? 'grid-rows-1'
58
- : 'grid-cols-1',
112
+ ? 'grid-rows-1 pli-1'
113
+ : 'grid-cols-1 plb-1',
59
114
  size === 'contain' &&
60
115
  (orientation === 'horizontal'
61
116
  ? 'overflow-x-auto min-bs-0 bs-full max-bs-full'
62
117
  : 'overflow-y-auto min-is-0 is-full max-is-full'),
63
- separators && (orientation === 'horizontal' ? 'divide-separator divide-x' : 'divide-separator divide-y'),
64
118
  classNames,
65
119
  )}
120
+ data-rail={rail}
66
121
  aria-orientation={orientation}
67
122
  style={styles}
68
- ref={forwardedRef}
123
+ ref={composedItemRef}
69
124
  >
70
125
  {children}
126
+ {selfDroppable && dropping && (
127
+ <ListItem.DropIndicator
128
+ lineInset={8}
129
+ terminalInset={-8}
130
+ gap={-8}
131
+ edge={orientation === 'horizontal' ? 'left' : 'top'}
132
+ />
133
+ )}
71
134
  </div>
72
135
  </StackContext.Provider>
73
136
  );
@@ -2,28 +2,37 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
5
6
  import { createContext, useContext } from 'react';
6
7
 
7
8
  import { type Orientation, type Size } from './Stack';
8
- import { type StackItemSize } from './StackItem';
9
+
10
+ export type StackItemSize = number | 'min-content';
11
+
12
+ export type StackItemData = { id: string; type: 'column' | 'card' };
13
+
14
+ export type StackItemRearrangeHandler = (
15
+ source: StackItemData,
16
+ target: StackItemData,
17
+ closestEdge: Edge | null,
18
+ ) => void;
9
19
 
10
20
  export type StackContextValue = {
11
21
  orientation: Orientation;
12
- separators: boolean;
13
22
  rail: boolean;
14
23
  size: Size;
24
+ onRearrange?: StackItemRearrangeHandler;
15
25
  };
16
26
 
17
27
  export const StackContext = createContext<StackContextValue>({
18
28
  orientation: 'vertical',
19
29
  rail: true,
20
30
  size: 'intrinsic',
21
- separators: true,
22
31
  });
23
32
 
24
33
  export const useStack = () => useContext(StackContext);
25
34
 
26
- type StackItemContextValue = {
35
+ export type StackItemContextValue = {
27
36
  selfDragHandleRef: (element: HTMLDivElement | null) => void;
28
37
  size: StackItemSize;
29
38
  setSize: (nextSize: StackItemSize, commit?: boolean) => void;
@@ -4,29 +4,30 @@
4
4
 
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
+ import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
8
+ import { scrollJustEnoughIntoView } from '@atlaskit/pragmatic-drag-and-drop/element/scroll-just-enough-into-view';
7
9
  import {
8
10
  attachClosestEdge,
9
11
  extractClosestEdge,
10
12
  type Edge,
11
13
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
12
- // TODO(wittjosiah): Drop indicator that doesn't depend on emotion. See react-ui-list DropIndicator.
13
- // import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box';
14
14
  import { useFocusableGroup } from '@fluentui/react-tabster';
15
15
  import { composeRefs } from '@radix-ui/react-compose-refs';
16
16
  import React, { forwardRef, useLayoutEffect, useState, type ComponentPropsWithRef, useCallback } from 'react';
17
17
 
18
- import { type ThemedClassName } from '@dxos/react-ui';
18
+ import { type ThemedClassName, ListItem } from '@dxos/react-ui';
19
19
  import { mx } from '@dxos/react-ui-theme';
20
20
 
21
- import { useStack, StackItemContext } from './StackContext';
21
+ import { useStack, StackItemContext, type StackItemSize, type StackItemData } from './StackContext';
22
22
  import { StackItemContent, type StackItemContentProps } from './StackItemContent';
23
+ import { StackItemDragHandle, type StackItemDragHandleProps } from './StackItemDragHandle';
23
24
  import {
24
25
  StackItemHeading,
25
26
  StackItemHeadingLabel,
26
27
  type StackItemHeadingProps,
27
28
  type StackItemHeadingLabelProps,
28
29
  } from './StackItemHeading';
29
- import { StackItemResizeHandle } from './StackItemResizeHandle';
30
+ import { StackItemResizeHandle, type StackItemResizeHandleProps } from './StackItemResizeHandle';
30
31
  import {
31
32
  StackItemSigil,
32
33
  type StackItemSigilProps,
@@ -35,31 +36,41 @@ import {
35
36
  StackItemSigilButton,
36
37
  } from './StackItemSigil';
37
38
 
38
- export type StackItemSize = number | 'min-content';
39
39
  export const DEFAULT_HORIZONTAL_SIZE = 44 satisfies StackItemSize;
40
40
  export const DEFAULT_VERTICAL_SIZE = 'min-content' satisfies StackItemSize;
41
41
  export const DEFAULT_EXTRINSIC_SIZE = DEFAULT_HORIZONTAL_SIZE satisfies StackItemSize;
42
42
 
43
- export type StackItemData = { id: string; type: 'column' | 'card' };
44
-
45
43
  export type StackItemRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> & {
46
44
  item: Omit<StackItemData, 'type'>;
47
45
  order?: number;
48
- onRearrange?: (source: StackItemData, target: StackItemData, closestEdge: Edge | null) => void;
49
46
  size?: StackItemSize;
50
47
  onSizeChange?: (nextSize: StackItemSize) => void;
51
48
  role?: 'article' | 'section';
49
+ disableRearrange?: boolean;
50
+ focusIndicatorVariant?: 'over-all' | 'group';
52
51
  };
53
52
 
54
53
  const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
55
54
  (
56
- { item, children, classNames, onRearrange, size: propsSize, onSizeChange, role, order, style, ...props },
55
+ {
56
+ item,
57
+ children,
58
+ classNames,
59
+ size: propsSize,
60
+ onSizeChange,
61
+ role,
62
+ order,
63
+ style,
64
+ disableRearrange,
65
+ focusIndicatorVariant = 'over-all',
66
+ ...props
67
+ },
57
68
  forwardedRef,
58
69
  ) => {
59
70
  const [itemElement, itemRef] = useState<HTMLDivElement | null>(null);
60
71
  const [selfDragHandleElement, selfDragHandleRef] = useState<HTMLDivElement | null>(null);
61
- const [_closestEdge, setEdge] = useState<Edge | null>(null);
62
- const { orientation, rail, separators } = useStack();
72
+ const [closestEdge, setEdge] = useState<Edge | null>(null);
73
+ const { orientation, rail, onRearrange } = useStack();
63
74
  const [size = orientation === 'horizontal' ? DEFAULT_HORIZONTAL_SIZE : DEFAULT_VERTICAL_SIZE, setInternalSize] =
64
75
  useState(propsSize);
65
76
 
@@ -80,7 +91,7 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
80
91
  const type = orientation === 'horizontal' ? 'column' : 'card';
81
92
 
82
93
  useLayoutEffect(() => {
83
- if (!itemElement || !onRearrange) {
94
+ if (!itemElement || !onRearrange || disableRearrange) {
84
95
  return;
85
96
  }
86
97
  return combine(
@@ -88,6 +99,21 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
88
99
  element: itemElement,
89
100
  ...(selfDragHandleElement && { dragHandle: selfDragHandleElement }),
90
101
  getInitialData: () => ({ id: item.id, type }),
102
+ onGenerateDragPreview: ({ nativeSetDragImage, source, location }) => {
103
+ document.body.setAttribute('data-drag-preview', 'true');
104
+ scrollJustEnoughIntoView({ element: source.element });
105
+ const { x, y } = preserveOffsetOnSource({ element: source.element, input: location.current.input })({
106
+ container: (source.element.offsetParent ?? document.body) as HTMLElement,
107
+ });
108
+ nativeSetDragImage?.(source.element, x, y);
109
+ },
110
+ onDragStart: () => {
111
+ document.body.removeAttribute('data-drag-preview');
112
+ itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'active');
113
+ },
114
+ onDrop: () => {
115
+ itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'idle');
116
+ },
91
117
  }),
92
118
  dropTargetForElements({
93
119
  element: itemElement,
@@ -127,11 +153,15 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
127
153
  tabIndex={0}
128
154
  {...focusGroupAttrs}
129
155
  className={mx(
130
- 'group/stack-item grid relative ch-focus-ring-inset-over-all',
156
+ 'group/stack-item grid relative',
157
+ focusIndicatorVariant === 'over-all'
158
+ ? 'dx-focus-ring-inset-over-all'
159
+ : orientation === 'horizontal'
160
+ ? 'dx-focus-ring-group-x'
161
+ : 'dx-focus-ring-group-y',
131
162
  size === 'min-content' && (orientation === 'horizontal' ? 'is-min' : 'bs-min'),
132
163
  orientation === 'horizontal' ? 'grid-rows-subgrid' : 'grid-cols-subgrid',
133
164
  rail && (orientation === 'horizontal' ? 'row-span-2' : 'col-span-2'),
134
- separators && (orientation === 'horizontal' ? 'divide-separator divide-y' : 'divide-separator divide-x'),
135
165
  classNames,
136
166
  )}
137
167
  data-dx-stack-item
@@ -147,7 +177,7 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
147
177
  ref={composedItemRef}
148
178
  >
149
179
  {children}
150
- {/* {closestEdge && <DropIndicator edge={closestEdge} />} */}
180
+ {closestEdge && <ListItem.DropIndicator lineInset={8} terminalInset={-8} edge={closestEdge} />}
151
181
  </Root>
152
182
  </StackItemContext.Provider>
153
183
  );
@@ -160,6 +190,7 @@ export const StackItem = {
160
190
  Heading: StackItemHeading,
161
191
  HeadingLabel: StackItemHeadingLabel,
162
192
  ResizeHandle: StackItemResizeHandle,
193
+ DragHandle: StackItemDragHandle,
163
194
  Sigil: StackItemSigil,
164
195
  SigilButton: StackItemSigilButton,
165
196
  };
@@ -168,6 +199,8 @@ export type {
168
199
  StackItemContentProps,
169
200
  StackItemHeadingProps,
170
201
  StackItemHeadingLabelProps,
202
+ StackItemResizeHandleProps,
203
+ StackItemDragHandleProps,
171
204
  StackItemSigilProps,
172
205
  StackItemSigilButtonProps,
173
206
  StackItemSigilAction,
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ComponentPropsWithoutRef } from 'react';
5
+ import React, { type ComponentPropsWithoutRef, forwardRef } from 'react';
6
6
 
7
7
  import { type ThemedClassName } from '@dxos/react-ui';
8
8
  import { mx } from '@dxos/react-ui-theme';
@@ -10,38 +10,53 @@ import { mx } from '@dxos/react-ui-theme';
10
10
  import { useStack } from './StackContext';
11
11
 
12
12
  export type StackItemContentProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> & {
13
- toolbar?: boolean;
13
+ /**
14
+ * This flag is required in order to clarify a developer experience that seemed like it needed extra boilerplate
15
+ * (`row-span-2`) or was buggy. See the description of the StackItem.Content component itself for more information.
16
+ */
17
+ toolbar: boolean;
18
+
19
+ /**
20
+ * Whether to provide for the layout of a statusbar after the content.
21
+ */
14
22
  statusbar?: boolean;
15
- };
16
23
 
17
- export const StackItemContent = ({
18
- children,
19
- toolbar = true,
20
- statusbar,
21
- classNames,
22
- ...props
23
- }: StackItemContentProps) => {
24
- const { size, separators } = useStack();
25
-
26
- return (
27
- <div
28
- role='none'
29
- {...props}
30
- className={mx(
31
- 'group grid grid-cols-[100%]',
32
- size === 'contain' && 'min-bs-0 overflow-hidden',
33
- separators && 'divide-separator divide-y',
34
- classNames,
35
- )}
36
- style={{
37
- gridTemplateRows: [
38
- ...(toolbar ? ['var(--rail-action)'] : []),
39
- '1fr',
40
- ...(statusbar ? ['var(--statusbar-size)'] : []),
41
- ].join(' '),
42
- }}
43
- >
44
- {children}
45
- </div>
46
- );
24
+ /**
25
+ * Whether to set a certain aspect ratio on the content, including the toolbar and statusbar. This is provided for
26
+ * convenience and consistency; it can instead be specified by the `classNames` or `style` props as needed.
27
+ */
28
+ size?: 'intrinsic' | 'video' | 'square';
47
29
  };
30
+
31
+ /**
32
+ * This component should be used by plugins for rendering content within a stack item, a.k.a. a “plank” or “section”.
33
+ * The `toolbar` flag must be provided since this component provides for the layout of content with the toolbar.
34
+ */
35
+ export const StackItemContent = forwardRef<HTMLDivElement, StackItemContentProps>(
36
+ ({ children, toolbar, statusbar, classNames, size = 'intrinsic', ...props }, forwardedRef) => {
37
+ const { size: stackItemSize } = useStack();
38
+
39
+ return (
40
+ <div
41
+ role='none'
42
+ {...props}
43
+ className={mx(
44
+ 'group grid grid-cols-[100%]',
45
+ stackItemSize === 'contain' && 'min-bs-0 overflow-hidden',
46
+ size === 'video' ? 'aspect-video' : size === 'square' && 'aspect-square',
47
+ classNames,
48
+ )}
49
+ style={{
50
+ gridTemplateRows: [
51
+ ...(toolbar ? ['var(--rail-action)'] : []),
52
+ '1fr',
53
+ ...(statusbar ? ['var(--statusbar-size)'] : []),
54
+ ].join(' '),
55
+ }}
56
+ ref={forwardedRef}
57
+ >
58
+ {children}
59
+ </div>
60
+ );
61
+ },
62
+ );
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Slot } from '@radix-ui/react-slot';
6
+ import React, { type ComponentPropsWithoutRef } from 'react';
7
+
8
+ import { useStackItem } from './StackContext';
9
+
10
+ export type StackItemDragHandleProps = ComponentPropsWithoutRef<'button'> & { asChild: boolean };
11
+
12
+ export const StackItemDragHandle = ({ asChild, children }: StackItemDragHandleProps) => {
13
+ const { selfDragHandleRef } = useStackItem();
14
+
15
+ const Root = asChild ? Slot : 'div';
16
+
17
+ return (
18
+ <Root ref={selfDragHandleRef} role='button'>
19
+ {children}
20
+ </Root>
21
+ );
22
+ };
@@ -9,13 +9,12 @@ import { type ThemedClassName } from '@dxos/react-ui';
9
9
  import { useAttention, type AttendableId, type Related } from '@dxos/react-ui-attention';
10
10
  import { mx } from '@dxos/react-ui-theme';
11
11
 
12
- import { useStack, useStackItem } from './StackContext';
12
+ import { useStack } from './StackContext';
13
13
 
14
14
  export type StackItemHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
15
15
 
16
16
  export const StackItemHeading = ({ children, classNames, ...props }: StackItemHeadingProps) => {
17
17
  const { orientation } = useStack();
18
- const { selfDragHandleRef } = useStackItem();
19
18
  const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
20
19
  return (
21
20
  <div
@@ -24,11 +23,10 @@ export const StackItemHeading = ({ children, classNames, ...props }: StackItemHe
24
23
  tabIndex={0}
25
24
  {...focusableGroupAttrs}
26
25
  className={mx(
27
- 'flex items-center ch-focus-ring-inset-over-all relative !border-is-0',
26
+ 'flex items-center dx-focus-ring-inset-over-all relative !border-is-0',
28
27
  orientation === 'horizontal' ? 'bs-[--rail-size]' : 'is-[--rail-size] flex-col',
29
28
  classNames,
30
29
  )}
31
- ref={selfDragHandleRef}
32
30
  >
33
31
  {children}
34
32
  </div>
@@ -45,7 +43,7 @@ export const StackItemHeadingLabel = forwardRef<HTMLHeadingElement, StackItemHea
45
43
  {...props}
46
44
  data-attention={((related && isRelated) || hasAttention || isAncestor).toString()}
47
45
  className={mx(
48
- 'pli-1 min-is-0 is-0 grow truncate font-medium text-baseText data-[attention=true]:text-accentText',
46
+ 'pli-1 min-is-0 is-0 grow truncate font-medium text-baseText data-[attention=true]:text-accentText self-center',
49
47
  classNames,
50
48
  )}
51
49
  ref={forwardedRef}