@dxos/react-ui-list 0.8.4-main.406dc2a → 0.8.4-main.40e3dcdf1b

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 (48) hide show
  1. package/dist/lib/browser/index.mjs +667 -711
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +667 -711
  5. package/dist/lib/node-esm/index.mjs.map +3 -3
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Accordion/Accordion.stories.d.ts +0 -3
  8. package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/List/List.d.ts +6 -6
  10. package/dist/types/src/components/List/List.d.ts.map +1 -1
  11. package/dist/types/src/components/List/List.stories.d.ts +2 -2
  12. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/List/ListItem.d.ts +8 -6
  14. package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
  15. package/dist/types/src/components/List/ListRoot.d.ts +2 -2
  16. package/dist/types/src/components/List/ListRoot.d.ts.map +1 -1
  17. package/dist/types/src/components/Tree/Tree.d.ts +10 -6
  18. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  19. package/dist/types/src/components/Tree/Tree.stories.d.ts +9 -28
  20. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Tree/TreeContext.d.ts +22 -9
  22. package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
  23. package/dist/types/src/components/Tree/TreeItem.d.ts +20 -3
  24. package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
  25. package/dist/types/src/components/Tree/TreeItemHeading.d.ts +1 -1
  26. package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
  27. package/dist/types/src/components/Tree/index.d.ts +2 -0
  28. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  29. package/dist/types/src/components/Tree/testing.d.ts +2 -2
  30. package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
  31. package/dist/types/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +31 -28
  33. package/src/components/Accordion/Accordion.stories.tsx +2 -5
  34. package/src/components/Accordion/AccordionItem.tsx +3 -3
  35. package/src/components/Accordion/AccordionRoot.tsx +1 -1
  36. package/src/components/List/List.stories.tsx +31 -19
  37. package/src/components/List/List.tsx +2 -2
  38. package/src/components/List/ListItem.tsx +53 -35
  39. package/src/components/List/ListRoot.tsx +2 -2
  40. package/src/components/List/testing.ts +2 -2
  41. package/src/components/Tree/Tree.stories.tsx +150 -60
  42. package/src/components/Tree/Tree.tsx +39 -41
  43. package/src/components/Tree/TreeContext.tsx +19 -8
  44. package/src/components/Tree/TreeItem.tsx +173 -103
  45. package/src/components/Tree/TreeItemHeading.tsx +9 -5
  46. package/src/components/Tree/TreeItemToggle.tsx +1 -1
  47. package/src/components/Tree/index.ts +2 -0
  48. package/src/components/Tree/testing.ts +4 -3
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-list",
3
- "version": "0.8.4-main.406dc2a",
3
+ "version": "0.8.4-main.40e3dcdf1b",
4
4
  "description": "A list component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
13
  "type": "module",
@@ -24,39 +28,38 @@
24
28
  "src"
25
29
  ],
26
30
  "dependencies": {
27
- "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
28
- "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
29
- "@preact-signals/safe-react": "^0.9.0",
30
- "@preact/signals-core": "^1.12.1",
31
+ "@atlaskit/pragmatic-drag-and-drop": "1.7.7",
32
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
33
+ "@effect-atom/atom-react": "^0.5.0",
31
34
  "@radix-ui/react-accordion": "1.2.3",
32
35
  "@radix-ui/react-context": "1.1.1",
33
- "@dxos/debug": "0.8.4-main.406dc2a",
34
- "@dxos/invariant": "0.8.4-main.406dc2a",
35
- "@dxos/echo": "0.8.4-main.406dc2a",
36
- "@dxos/log": "0.8.4-main.406dc2a",
37
- "@dxos/live-object": "0.8.4-main.406dc2a",
38
- "@dxos/react-ui": "0.8.4-main.406dc2a",
39
- "@dxos/react-ui-text-tooltip": "0.8.4-main.406dc2a",
40
- "@dxos/react-ui-theme": "0.8.4-main.406dc2a",
41
- "@dxos/react-ui-types": "0.8.4-main.406dc2a",
42
- "@dxos/util": "0.8.4-main.406dc2a"
36
+ "@radix-ui/react-slot": "1.1.2",
37
+ "@dxos/debug": "0.8.4-main.40e3dcdf1b",
38
+ "@dxos/echo": "0.8.4-main.40e3dcdf1b",
39
+ "@dxos/invariant": "0.8.4-main.40e3dcdf1b",
40
+ "@dxos/log": "0.8.4-main.40e3dcdf1b",
41
+ "@dxos/ui-theme": "0.8.4-main.40e3dcdf1b",
42
+ "@dxos/react-ui": "0.8.4-main.40e3dcdf1b",
43
+ "@dxos/react-ui-text-tooltip": "0.8.4-main.40e3dcdf1b",
44
+ "@dxos/ui-types": "0.8.4-main.40e3dcdf1b",
45
+ "@dxos/util": "0.8.4-main.40e3dcdf1b"
43
46
  },
44
47
  "devDependencies": {
45
- "@types/react": "~19.2.2",
46
- "@types/react-dom": "~19.2.1",
47
- "effect": "3.18.3",
48
- "react": "~19.2.0",
49
- "react-dom": "~19.2.0",
50
- "vite": "7.1.9",
51
- "@dxos/random": "0.8.4-main.406dc2a",
52
- "@dxos/storybook-utils": "0.8.4-main.406dc2a"
48
+ "@types/react": "~19.2.7",
49
+ "@types/react-dom": "~19.2.3",
50
+ "effect": "3.19.16",
51
+ "react": "~19.2.3",
52
+ "react-dom": "~19.2.3",
53
+ "vite": "^7.1.11",
54
+ "@dxos/random": "0.8.4-main.40e3dcdf1b",
55
+ "@dxos/storybook-utils": "0.8.4-main.40e3dcdf1b"
53
56
  },
54
57
  "peerDependencies": {
55
- "effect": "^3.13.3",
56
- "react": "^19.0.0",
57
- "react-dom": "^19.0.0",
58
- "@dxos/react-ui": "0.8.4-main.406dc2a",
59
- "@dxos/react-ui-theme": "0.8.4-main.406dc2a"
58
+ "effect": "3.19.16",
59
+ "react": "~19.2.3",
60
+ "react-dom": "~19.2.3",
61
+ "@dxos/react-ui": "0.8.4-main.40e3dcdf1b",
62
+ "@dxos/ui-theme": "0.8.4-main.40e3dcdf1b"
60
63
  },
61
64
  "publishConfig": {
62
65
  "access": "public"
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React from 'react';
7
7
 
8
8
  import { faker } from '@dxos/random';
9
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
 
11
11
  import { Accordion } from './Accordion';
12
12
 
@@ -42,10 +42,7 @@ const DefaultStory = () => {
42
42
  const meta = {
43
43
  title: 'ui/react-ui-list/Accordion',
44
44
  render: DefaultStory,
45
- decorators: [withTheme],
46
- parameters: {
47
- layout: 'column',
48
- },
45
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
49
46
  } satisfies Meta<typeof Accordion>;
50
47
 
51
48
  export default meta;
@@ -7,7 +7,7 @@ import { createContext } from '@radix-ui/react-context';
7
7
  import React, { type PropsWithChildren } from 'react';
8
8
 
9
9
  import { Icon, type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
11
11
 
12
12
  import { type ListItemRecord } from '../List';
13
13
 
@@ -43,7 +43,7 @@ export type AccordionItemHeaderProps = ThemedClassName<AccordionPrimitive.Accord
43
43
  export const AccordionItemHeader = ({ classNames, children, ...props }: AccordionItemHeaderProps) => {
44
44
  return (
45
45
  <AccordionPrimitive.Header {...props} className={mx(classNames)}>
46
- <AccordionPrimitive.Trigger className='group flex items-center p-2 dx-focus-ring-inset is-full text-start'>
46
+ <AccordionPrimitive.Trigger className='group flex items-center p-2 dx-focus-ring-inset w-full text-start'>
47
47
  {children}
48
48
  <Icon
49
49
  icon='ph--caret-right--regular'
@@ -59,7 +59,7 @@ export type AccordionItemBodyProps = ThemedClassName<PropsWithChildren>;
59
59
 
60
60
  export const AccordionItemBody = ({ children, classNames }: AccordionItemBodyProps) => {
61
61
  return (
62
- <AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown'>
62
+ <AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-slide-up data-[state=open]:animate-slide-down'>
63
63
  <div role='none' className={mx('p-2', classNames)}>
64
64
  {children}
65
65
  </div>
@@ -7,7 +7,7 @@ import { createContext } from '@radix-ui/react-context';
7
7
  import React, { type ReactNode } from 'react';
8
8
 
9
9
  import { type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
11
11
 
12
12
  import { type ListItemRecord } from '../List';
13
13
 
@@ -2,31 +2,43 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
5
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
7
  import * as Schema from 'effect/Schema';
7
- import React from 'react';
8
+ import React, { useContext, useMemo } from 'react';
8
9
 
9
- import { live } from '@dxos/live-object';
10
- import { withTheme } from '@dxos/react-ui/testing';
11
- import { ghostHover, mx } from '@dxos/react-ui-theme';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+ import { withRegistry } from '@dxos/storybook-utils';
12
+ import { ghostHover, mx } from '@dxos/ui-theme';
12
13
  import { arrayMove } from '@dxos/util';
13
14
 
14
15
  import { List, type ListRootProps } from './List';
15
- import { TestItemSchema, type TestItemType, createList } from './testing';
16
+ import { TestItemSchema, type TestItemType, type TestList, createList } from './testing';
16
17
 
17
18
  // TODO(burdon): var-icon-size.
18
- const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2rem] rounded';
19
+ const grid = 'grid grid-cols-[32px_1fr_32px] min-h-[2rem] rounded-sm';
20
+
21
+ const DefaultStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
22
+ const registry = useContext(RegistryContext);
23
+ const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
24
+ const list = useAtomValue(listAtom);
25
+ const items = list.items;
19
26
 
20
- const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
21
27
  const handleSelect = (item: TestItemType) => {
22
28
  console.log('select', item);
23
29
  };
24
30
  const handleDelete = (item: TestItemType) => {
25
- const idx = items.findIndex((i) => i.id === item.id);
26
- items.splice(idx, 1);
31
+ const prev = registry.get(listAtom);
32
+ registry.set(listAtom, {
33
+ ...prev,
34
+ items: prev.items.filter((i) => i.id !== item.id),
35
+ });
27
36
  };
28
37
  const handleMove = (from: number, to: number) => {
29
- arrayMove(items, from, to);
38
+ const prev = registry.get(listAtom);
39
+ const newItems = [...prev.items];
40
+ arrayMove(newItems, from, to);
41
+ registry.set(listAtom, { ...prev, items: newItems });
30
42
  };
31
43
 
32
44
  return (
@@ -41,7 +53,7 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
41
53
 
42
54
  <div role='list' className='w-full h-full overflow-auto'>
43
55
  {items?.map((item) => (
44
- <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
56
+ <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
45
57
  <List.ItemDragHandle />
46
58
  <List.ItemTitle onClick={() => handleSelect(item)}>{item.name}</List.ItemTitle>
47
59
  <List.ItemDeleteButton onClick={() => handleDelete(item)} />
@@ -57,7 +69,7 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
57
69
 
58
70
  <List.ItemDragPreview<TestItemType>>
59
71
  {({ item }) => (
60
- <List.ItemWrapper classNames={mx(grid, 'bg-modalSurface border border-separator')}>
72
+ <List.ItemWrapper classNames={mx(grid, 'bg-modal-surface border border-separator')}>
61
73
  <List.ItemDragHandle />
62
74
  <div className='flex items-center'>{item.name}</div>
63
75
  </List.ItemWrapper>
@@ -69,13 +81,17 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
69
81
  );
70
82
  };
71
83
 
72
- const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
84
+ const SimpleStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
85
+ const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
86
+ const list = useAtomValue(listAtom);
87
+ const items = list.items;
88
+
73
89
  return (
74
90
  <List.Root<TestItemType> dragPreview items={items} {...props}>
75
91
  {({ items }) => (
76
92
  <div role='list' className='w-full h-full overflow-auto'>
77
93
  {items?.map((item) => (
78
- <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
94
+ <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
79
95
  <List.ItemDragHandle />
80
96
  <List.ItemTitle>{item.name}</List.ItemTitle>
81
97
  <List.ItemDeleteButton />
@@ -87,12 +103,10 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
87
103
  );
88
104
  };
89
105
 
90
- const list = live(createList(100));
91
-
92
106
  const meta = {
93
107
  title: 'ui/react-ui-list/List',
94
108
  component: List.Root,
95
- decorators: [withTheme],
109
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withRegistry],
96
110
  parameters: {
97
111
  layout: 'fullscreen',
98
112
  },
@@ -103,7 +117,6 @@ export default meta;
103
117
  export const Default: StoryObj<typeof DefaultStory> = {
104
118
  render: DefaultStory,
105
119
  args: {
106
- items: list.items,
107
120
  isItem: Schema.is(TestItemSchema),
108
121
  },
109
122
  };
@@ -111,7 +124,6 @@ export const Default: StoryObj<typeof DefaultStory> = {
111
124
  export const Simple: StoryObj<typeof SimpleStory> = {
112
125
  render: SimpleStory,
113
126
  args: {
114
- items: list.items,
115
127
  isItem: Schema.is(TestItemSchema),
116
128
  },
117
129
  };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import {
6
6
  ListItem,
7
- ListItemButton,
7
+ ListItemIconButton,
8
8
  ListItemDeleteButton,
9
9
  ListItemDragHandle,
10
10
  ListItemDragPreview,
@@ -33,8 +33,8 @@ export const List = {
33
33
  ItemDragPreview: ListItemDragPreview,
34
34
  ItemWrapper: ListItemWrapper,
35
35
  ItemDragHandle: ListItemDragHandle,
36
+ ItemIconButton: ListItemIconButton,
36
37
  ItemDeleteButton: ListItemDeleteButton,
37
- ItemButton: ListItemButton,
38
38
  ItemTitle: ListItemTitle,
39
39
  };
40
40
 
@@ -11,6 +11,7 @@ import {
11
11
  extractClosestEdge,
12
12
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
13
13
  import { createContext } from '@radix-ui/react-context';
14
+ import { Slot } from '@radix-ui/react-slot';
14
15
  import React, {
15
16
  type ComponentProps,
16
17
  type HTMLAttributes,
@@ -31,7 +32,7 @@ import {
31
32
  type ThemedClassName,
32
33
  useTranslation,
33
34
  } from '@dxos/react-ui';
34
- import { mx } from '@dxos/react-ui-theme';
35
+ import { mx, osTranslations } from '@dxos/ui-theme';
35
36
 
36
37
  import { useListContext } from './ListRoot';
37
38
 
@@ -46,17 +47,17 @@ export type ItemDragState =
46
47
  container: HTMLElement;
47
48
  }
48
49
  | {
49
- type: 'is-dragging';
50
+ type: 'w-dragging';
50
51
  }
51
52
  | {
52
- type: 'is-dragging-over';
53
+ type: 'w-dragging-over';
53
54
  closestEdge: Edge | null;
54
55
  };
55
56
 
56
57
  export const idle: ItemDragState = { type: 'idle' };
57
58
 
58
59
  const stateStyles: { [Key in ItemDragState['type']]?: HTMLAttributes<HTMLDivElement>['className'] } = {
59
- 'is-dragging': 'opacity-50',
60
+ 'w-dragging': 'opacity-50',
60
61
  };
61
62
 
62
63
  type ListItemContext<T extends ListItemRecord> = {
@@ -80,6 +81,8 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
80
81
  PropsWithChildren<
81
82
  {
82
83
  item: T;
84
+ asChild?: boolean;
85
+ selected?: boolean;
83
86
  } & HTMLAttributes<HTMLDivElement>
84
87
  >
85
88
  >;
@@ -87,7 +90,15 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
87
90
  /**
88
91
  * Draggable list item.
89
92
  */
90
- export const ListItem = <T extends ListItemRecord>({ children, classNames, item, ...props }: ListItemProps<T>) => {
93
+ export const ListItem = <T extends ListItemRecord>({
94
+ children,
95
+ classNames,
96
+ item,
97
+ asChild,
98
+ selected,
99
+ ...props
100
+ }: ListItemProps<T>) => {
101
+ const Comp = asChild ? Slot : 'div';
91
102
  const { isItem, readonly, dragPreview, setState: setRootState } = useListContext(LIST_ITEM_NAME);
92
103
  const ref = useRef<HTMLDivElement | null>(null);
93
104
  const dragHandleRef = useRef<HTMLElement | null>(null);
@@ -124,8 +135,8 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
124
135
  }
125
136
  : undefined,
126
137
  onDragStart: () => {
127
- setState({ type: 'is-dragging' });
128
- setRootState({ type: 'is-dragging', item });
138
+ setState({ type: 'w-dragging' });
139
+ setRootState({ type: 'w-dragging', item });
129
140
  },
130
141
  onDrop: () => {
131
142
  setState(idle);
@@ -147,7 +158,7 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
147
158
  getIsSticky: () => true,
148
159
  onDragEnter: ({ self }) => {
149
160
  const closestEdge = extractClosestEdge(self.data);
150
- setState({ type: 'is-dragging-over', closestEdge });
161
+ setState({ type: 'w-dragging-over', closestEdge });
151
162
  },
152
163
  onDragLeave: () => {
153
164
  setState(idle);
@@ -155,10 +166,10 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
155
166
  onDrag: ({ self }) => {
156
167
  const closestEdge = extractClosestEdge(self.data);
157
168
  setState((current) => {
158
- if (current.type === 'is-dragging-over' && current.closestEdge === closestEdge) {
169
+ if (current.type === 'w-dragging-over' && current.closestEdge === closestEdge) {
159
170
  return current;
160
171
  }
161
- return { type: 'is-dragging-over', closestEdge };
172
+ return { type: 'w-dragging-over', closestEdge };
162
173
  });
163
174
  },
164
175
  onDrop: () => {
@@ -170,12 +181,18 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
170
181
 
171
182
  return (
172
183
  <ListItemProvider item={item} dragHandleRef={dragHandleRef}>
173
- <div ref={ref} role='listitem' className={mx('flex relative', classNames, stateStyles[state.type])} {...props}>
184
+ <Comp
185
+ ref={ref}
186
+ role='listitem'
187
+ aria-selected={selected}
188
+ className={mx('relative dx-selected', classNames, stateStyles[state.type])}
189
+ {...props}
190
+ >
174
191
  {children}
175
- {state.type === 'is-dragging-over' && state.closestEdge && (
176
- <NaturalListItem.DropIndicator edge={state.closestEdge} />
177
- )}
178
- </div>
192
+ </Comp>
193
+ {state.type === 'w-dragging-over' && state.closestEdge && (
194
+ <NaturalListItem.DropIndicator edge={state.closestEdge} />
195
+ )}
179
196
  </ListItemProvider>
180
197
  );
181
198
  };
@@ -184,47 +201,48 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
184
201
  // List item components
185
202
  //
186
203
 
187
- export const ListItemDeleteButton = ({
204
+ export const ListItemIconButton = ({
188
205
  autoHide = true,
206
+ iconOnly = true,
207
+ variant = 'ghost',
189
208
  classNames,
190
209
  disabled,
191
- icon = 'ph--x--regular',
192
- label,
193
210
  ...props
194
- }: Partial<Pick<IconButtonProps, 'icon'>> &
195
- Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
196
- const { state } = useListContext('DELETE_BUTTON');
211
+ }: IconButtonProps & { autoHide?: boolean }) => {
212
+ const { state } = useListContext('ITEM_BUTTON');
197
213
  const isDisabled = state.type !== 'idle' || disabled;
198
- const { t } = useTranslation('os');
199
214
  return (
200
215
  <IconButton
201
- iconOnly
202
- variant='ghost'
203
216
  {...props}
204
- icon={icon}
205
217
  disabled={isDisabled}
206
- label={label ?? t('delete label')}
218
+ iconOnly={iconOnly}
219
+ variant={variant}
207
220
  classNames={[classNames, autoHide && disabled && 'hidden']}
208
221
  />
209
222
  );
210
223
  };
211
224
 
212
- export const ListItemButton = ({
225
+ // TODO(burdon): Generalize to action button.
226
+ export const ListItemDeleteButton = ({
213
227
  autoHide = true,
214
- iconOnly = true,
215
- variant = 'ghost',
216
228
  classNames,
217
229
  disabled,
230
+ icon = 'ph--x--regular',
231
+ label,
218
232
  ...props
219
- }: IconButtonProps & { autoHide?: boolean }) => {
220
- const { state } = useListContext('ITEM_BUTTON');
233
+ }: Partial<Pick<IconButtonProps, 'icon'>> &
234
+ Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
235
+ const { state } = useListContext('DELETE_BUTTON');
221
236
  const isDisabled = state.type !== 'idle' || disabled;
237
+ const { t } = useTranslation(osTranslations);
222
238
  return (
223
239
  <IconButton
240
+ iconOnly
241
+ variant='ghost'
224
242
  {...props}
243
+ icon={icon}
225
244
  disabled={isDisabled}
226
- iconOnly={iconOnly}
227
- variant={variant}
245
+ label={label ?? t('delete label')}
228
246
  classNames={[classNames, autoHide && disabled && 'hidden']}
229
247
  />
230
248
  );
@@ -232,7 +250,7 @@ export const ListItemButton = ({
232
250
 
233
251
  export const ListItemDragHandle = ({ disabled }: Pick<IconButtonProps, 'disabled'>) => {
234
252
  const { dragHandleRef } = useListItemContext('DRAG_HANDLE');
235
- const { t } = useTranslation('os');
253
+ const { t } = useTranslation(osTranslations);
236
254
  return (
237
255
  <IconButton
238
256
  iconOnly
@@ -255,7 +273,7 @@ export const ListItemDragPreview = <T extends ListItemRecord>({
255
273
  };
256
274
 
257
275
  export const ListItemWrapper = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
258
- <div className={mx('flex is-full gap-2', classNames)}>{children}</div>
276
+ <div className={mx('flex w-full gap-2', classNames)}>{children}</div>
259
277
  );
260
278
 
261
279
  export const ListItemTitle = ({
@@ -26,14 +26,14 @@ export const [ListProvider, useListContext] = createContext<ListContext<any>>(LI
26
26
 
27
27
  export type ListRendererProps<T extends ListItemRecord> = {
28
28
  state: ListContext<T>['state'];
29
- items: T[];
29
+ items: readonly T[];
30
30
  };
31
31
 
32
32
  const defaultGetId = <T extends ListItemRecord>(item: T) => (item as any)?.id;
33
33
 
34
34
  export type ListRootProps<T extends ListItemRecord> = {
35
35
  children?: (props: ListRendererProps<T>) => ReactNode;
36
- items?: T[];
36
+ items?: readonly T[];
37
37
  onMove?: (fromIndex: number, toIndex: number) => void;
38
38
  } & Pick<ListContext<T>, 'isItem' | 'getId' | 'readonly' | 'dragPreview'>;
39
39
 
@@ -4,11 +4,11 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { ObjectId } from '@dxos/echo/internal';
7
+ import { Obj } from '@dxos/echo';
8
8
  import { faker } from '@dxos/random';
9
9
 
10
10
  export const TestItemSchema = Schema.Struct({
11
- id: ObjectId,
11
+ id: Obj.ID,
12
12
  name: Schema.String,
13
13
  });
14
14