@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
@@ -10,10 +10,20 @@ import {
10
10
  attachInstruction,
11
11
  extractInstruction,
12
12
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
13
+ import { useAtomValue } from '@effect-atom/atom-react';
13
14
  import * as Schema from 'effect/Schema';
14
- import React, { type FC, type KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
+ import React, {
16
+ type FC,
17
+ type KeyboardEvent,
18
+ type MouseEvent,
19
+ memo,
20
+ useCallback,
21
+ useEffect,
22
+ useMemo,
23
+ useRef,
24
+ useState,
25
+ } from 'react';
15
26
 
16
- import { type HasId } from '@dxos/echo/internal';
17
27
  import { invariant } from '@dxos/invariant';
18
28
  import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
19
29
  import {
@@ -22,7 +32,8 @@ import {
22
32
  hoverableControls,
23
33
  hoverableFocusedKeyboardControls,
24
34
  hoverableFocusedWithinControls,
25
- } from '@dxos/react-ui-theme';
35
+ mx,
36
+ } from '@dxos/ui-theme';
26
37
 
27
38
  import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
28
39
  import { useTree } from './TreeContext';
@@ -32,7 +43,7 @@ import { TreeItemToggle } from './TreeItemToggle';
32
43
  const hoverableDescriptionIcons =
33
44
  '[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
34
45
 
35
- type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
46
+ type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
36
47
 
37
48
  export const TreeDataSchema = Schema.Struct({
38
49
  id: Schema.String,
@@ -43,7 +54,7 @@ export const TreeDataSchema = Schema.Struct({
43
54
  export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
44
55
  export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
45
56
 
46
- export type ColumnRenderer<T extends HasId = any> = FC<{
57
+ export type ColumnRenderer<T extends { id: string } = any> = FC<{
47
58
  item: T;
48
59
  path: string[];
49
60
  open: boolean;
@@ -51,52 +62,73 @@ export type ColumnRenderer<T extends HasId = any> = FC<{
51
62
  setMenuOpen: (open: boolean) => void;
52
63
  }>;
53
64
 
54
- export type TreeItemProps<T extends HasId = any> = {
65
+ export type TreeItemProps<T extends { id: string } = any> = {
55
66
  item: T;
56
67
  path: string[];
57
68
  levelOffset?: number;
58
69
  last: boolean;
59
70
  draggable?: boolean;
60
71
  renderColumns?: ColumnRenderer<T>;
72
+ blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
61
73
  canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
62
74
  canSelect?: (params: { item: T; path: string[] }) => boolean;
63
75
  onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
64
76
  onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
77
+ onItemHover?: (params: { item: T }) => void;
65
78
  };
66
79
 
67
- const RawTreeItem = <T extends HasId = any>({
80
+ const RawTreeItem = <T extends { id: string } = any>({
68
81
  item,
69
- path: _path,
82
+ path: pathProp,
70
83
  levelOffset = 2,
71
84
  last,
72
- draggable: _draggable,
85
+ draggable: draggableProp,
73
86
  renderColumns: Columns,
87
+ blockInstruction,
74
88
  canDrop,
75
89
  canSelect,
76
90
  onOpenChange,
77
91
  onSelect,
92
+ onItemHover,
78
93
  }: TreeItemProps<T>) => {
79
94
  const rowRef = useRef<HTMLDivElement | null>(null);
80
95
  const buttonRef = useRef<HTMLButtonElement | null>(null);
81
96
  const openRef = useRef(false);
82
97
  const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
83
- const [_state, setState] = useState<TreeItemState>('idle');
98
+ const [_state, setState] = useState<TreeItemDragState>('idle');
84
99
  const [instruction, setInstruction] = useState<Instruction | null>(null);
85
100
  const [menuOpen, setMenuOpen] = useState(false);
86
101
 
87
- const { useItems, getProps, isOpen, isCurrent } = useTree();
88
- const items = useItems(item);
89
- const { id, parentOf, label, className, headingClassName, icon, iconClassName, disabled, testId } = getProps(
90
- item,
91
- _path,
92
- );
93
- const path = useMemo(() => [..._path, id], [_path, id]);
94
- const open = isOpen(path, item);
95
- const current = isCurrent(path, item);
102
+ const {
103
+ itemProps: itemPropsAtom,
104
+ childIds: childIdsAtom,
105
+ itemOpen: itemOpenAtom,
106
+ itemCurrent: itemCurrentAtom,
107
+ } = useTree();
108
+ const path = useMemo(() => [...pathProp, item.id], [pathProp, item.id]);
109
+
110
+ const {
111
+ id,
112
+ parentOf,
113
+ draggable: itemDraggable,
114
+ droppable: itemDroppable,
115
+ label,
116
+ className,
117
+ headingClassName,
118
+ icon,
119
+ iconHue,
120
+ disabled,
121
+ testId,
122
+ } = useAtomValue(itemPropsAtom(path));
123
+ const childIds = useAtomValue(childIdsAtom(item.id));
124
+ const open = useAtomValue(itemOpenAtom(path));
125
+ const current = useAtomValue(itemCurrentAtom(path));
126
+
96
127
  const level = path.length - levelOffset;
97
128
  const isBranch = !!parentOf;
98
129
  const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
99
130
  const canSelectItem = canSelect?.({ item, path }) ?? true;
131
+ const data = { id, path, item } satisfies TreeData;
100
132
 
101
133
  const cancelExpand = useCallback(() => {
102
134
  if (cancelExpandRef.current) {
@@ -105,19 +137,19 @@ const RawTreeItem = <T extends HasId = any>({
105
137
  }
106
138
  }, []);
107
139
 
140
+ const isItemDraggable = draggableProp && itemDraggable !== false;
141
+ const isItemDroppable = itemDroppable !== false;
142
+
108
143
  useEffect(() => {
109
- if (!_draggable) {
144
+ if (!draggableProp) {
110
145
  return;
111
146
  }
112
147
 
113
148
  invariant(buttonRef.current);
114
149
 
115
- const data = { id, path, item } satisfies TreeData;
116
-
117
- // https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
118
- return combine(
150
+ const makeDraggable = () =>
119
151
  draggable({
120
- element: buttonRef.current,
152
+ element: buttonRef.current!,
121
153
  getInitialData: () => data,
122
154
  onDragStart: () => {
123
155
  setState('dragging');
@@ -132,58 +164,72 @@ const RawTreeItem = <T extends HasId = any>({
132
164
  onOpenChange?.({ item, path, open: true });
133
165
  }
134
166
  },
135
- }),
136
- // https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/hitbox/constellation/index/about.mdx
137
- dropTargetForElements({
138
- element: buttonRef.current,
139
- getData: ({ input, element }) => {
140
- return attachInstruction(data, {
141
- input,
142
- element,
143
- indentPerLevel: DEFAULT_INDENTATION,
144
- currentLevel: level,
145
- mode,
146
- block: isBranch ? [] : ['make-child'],
147
- });
148
- },
149
- canDrop: ({ source }) => {
150
- const _canDrop = canDrop ?? (() => true);
151
- return source.element !== buttonRef.current && _canDrop({ source: source.data as TreeData, target: data });
152
- },
153
- getIsSticky: () => true,
154
- onDrag: ({ self, source }) => {
155
- const instruction = extractInstruction(self.data);
156
-
157
- if (source.data.id !== id) {
158
- if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
159
- cancelExpandRef.current = setTimeout(() => {
160
- onOpenChange?.({ item, path, open: true });
161
- }, 500);
162
- }
163
-
164
- if (instruction?.type !== 'make-child') {
165
- cancelExpand();
166
- }
167
-
168
- setInstruction(instruction);
169
- } else if (instruction?.type === 'reparent') {
170
- // TODO(wittjosiah): This is not occurring in the current implementation.
171
- setInstruction(instruction);
172
- } else {
173
- setInstruction(null);
167
+ });
168
+
169
+ if (!isItemDroppable) {
170
+ return isItemDraggable ? makeDraggable() : undefined;
171
+ }
172
+
173
+ const dropTarget = dropTargetForElements({
174
+ element: buttonRef.current,
175
+ getData: ({ input, element }) => {
176
+ return attachInstruction(data, {
177
+ input,
178
+ element,
179
+ indentPerLevel: DEFAULT_INDENTATION,
180
+ currentLevel: level,
181
+ mode,
182
+ block: isBranch ? [] : ['make-child'],
183
+ });
184
+ },
185
+ canDrop: ({ source }) => {
186
+ const _canDrop = canDrop ?? (() => true);
187
+ return source.element !== buttonRef.current && _canDrop({ source: source.data as TreeData, target: data });
188
+ },
189
+ getIsSticky: () => true,
190
+ onDrag: ({ self, source }) => {
191
+ const desired = extractInstruction(self.data);
192
+ const block =
193
+ desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
194
+ const instruction: Instruction | null =
195
+ block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
196
+
197
+ if (source.data.id !== id) {
198
+ if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
199
+ cancelExpandRef.current = setTimeout(() => {
200
+ onOpenChange?.({ item, path, open: true });
201
+ }, 500);
174
202
  }
175
- },
176
- onDragLeave: () => {
177
- cancelExpand();
178
- setInstruction(null);
179
- },
180
- onDrop: () => {
181
- cancelExpand();
203
+
204
+ if (instruction?.type !== 'make-child') {
205
+ cancelExpand();
206
+ }
207
+
208
+ setInstruction(instruction);
209
+ } else if (instruction?.type === 'reparent') {
210
+ // TODO(wittjosiah): This is not occurring in the current implementation.
211
+ setInstruction(instruction);
212
+ } else {
182
213
  setInstruction(null);
183
- },
184
- }),
185
- );
186
- }, [_draggable, item, id, mode, path, open, canDrop]);
214
+ }
215
+ },
216
+ onDragLeave: () => {
217
+ cancelExpand();
218
+ setInstruction(null);
219
+ },
220
+ onDrop: () => {
221
+ cancelExpand();
222
+ setInstruction(null);
223
+ },
224
+ });
225
+
226
+ if (!isItemDraggable) {
227
+ return dropTarget;
228
+ }
229
+
230
+ // https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
231
+ return combine(makeDraggable(), dropTarget);
232
+ }, [draggableProp, isItemDraggable, isItemDroppable, item, id, mode, path, open, blockInstruction, canDrop]);
187
233
 
188
234
  // Cancel expand on unmount.
189
235
  useEffect(() => () => cancelExpand(), [cancelExpand]);
@@ -221,6 +267,29 @@ const RawTreeItem = <T extends HasId = any>({
221
267
  [isBranch, open, handleOpenToggle, handleSelect],
222
268
  );
223
269
 
270
+ const handleItemHover = useCallback(() => {
271
+ onItemHover?.({ item });
272
+ }, [onItemHover, item]);
273
+
274
+ const handleContextMenu = useCallback(
275
+ (event: MouseEvent) => {
276
+ event.preventDefault();
277
+ setMenuOpen(true);
278
+ },
279
+ [setMenuOpen],
280
+ );
281
+
282
+ const childProps = {
283
+ draggable: draggableProp,
284
+ renderColumns: Columns,
285
+ blockInstruction,
286
+ canDrop,
287
+ canSelect,
288
+ onItemHover,
289
+ onOpenChange,
290
+ onSelect,
291
+ };
292
+
224
293
  return (
225
294
  <>
226
295
  <Treegrid.Row
@@ -229,27 +298,25 @@ const RawTreeItem = <T extends HasId = any>({
229
298
  id={id}
230
299
  aria-labelledby={`${id}__label`}
231
300
  parentOf={parentOf?.join(Treegrid.PARENT_OF_SEPARATOR)}
232
- classNames={[
233
- 'grid grid-cols-subgrid col-[tree-row] mbs-0.5 aria-[current]:bg-activeSurface',
301
+ data-object-id={id}
302
+ data-testid={testId}
303
+ // NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
304
+ // without alerting the user (except for in the correct link element). See also:
305
+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
306
+ aria-current={current ? ('' as 'page') : undefined}
307
+ classNames={mx(
308
+ 'grid grid-cols-subgrid col-[tree-row] mt-0.5 is-current:bg-active-surface',
234
309
  hoverableControls,
235
310
  hoverableFocusedKeyboardControls,
236
311
  hoverableFocusedWithinControls,
237
312
  hoverableDescriptionIcons,
238
- ghostHover,
239
313
  ghostFocusWithin,
314
+ ghostHover,
240
315
  className,
241
- ]}
242
- data-itemid={id}
243
- data-testid={testId}
244
- // NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
245
- // without alerting the user (except for in the correct link element). See also:
246
- // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
247
- aria-current={current ? ('' as 'page') : undefined}
316
+ )}
248
317
  onKeyDown={handleKeyDown}
249
- onContextMenu={(event) => {
250
- event.preventDefault();
251
- setMenuOpen(true);
252
- }}
318
+ onMouseEnter={handleItemHover}
319
+ onContextMenu={handleContextMenu}
253
320
  >
254
321
  <div
255
322
  role='none'
@@ -264,7 +331,7 @@ const RawTreeItem = <T extends HasId = any>({
264
331
  label={label}
265
332
  className={headingClassName}
266
333
  icon={icon}
267
- iconClassName={iconClassName}
334
+ iconHue={iconHue}
268
335
  onSelect={handleSelect}
269
336
  ref={buttonRef}
270
337
  />
@@ -274,22 +341,25 @@ const RawTreeItem = <T extends HasId = any>({
274
341
  </div>
275
342
  </Treegrid.Row>
276
343
  {open &&
277
- items.map((item, index) => (
278
- <TreeItem
279
- key={item.id}
280
- item={item}
281
- path={path}
282
- last={index === items.length - 1}
283
- draggable={_draggable}
284
- renderColumns={Columns}
285
- canDrop={canDrop}
286
- canSelect={canSelect}
287
- onOpenChange={onOpenChange}
288
- onSelect={onSelect}
289
- />
344
+ childIds.map((childId, index) => (
345
+ <TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
290
346
  ))}
291
347
  </>
292
348
  );
293
349
  };
294
350
 
295
351
  export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
352
+
353
+ /** Resolves a child ID to an item via the `item` atom and renders a TreeItem. */
354
+ export type TreeItemByIdProps = Omit<TreeItemProps, 'item'> & { id: string };
355
+
356
+ const RawTreeItemById = <T extends { id: string } = any>({ id, ...props }: TreeItemByIdProps) => {
357
+ const { item: itemAtom } = useTree();
358
+ const item = useAtomValue(itemAtom(id)) as T | undefined;
359
+ if (!item) {
360
+ return null;
361
+ }
362
+ return <TreeItem item={item} {...props} />;
363
+ };
364
+
365
+ export const TreeItemById = memo(RawTreeItemById) as FC<TreeItemByIdProps>;
@@ -6,6 +6,7 @@ import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallba
6
6
 
7
7
  import { Button, Icon, type Label, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
8
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
9
+ import { getStyles } from '@dxos/ui-theme';
9
10
 
10
11
  // TODO(wittjosiah): Consider whether there should be a separate disabled prop which was visually distinct
11
12
  // rather than just making the item unselectable.
@@ -13,7 +14,7 @@ export type TreeItemHeadingProps = {
13
14
  label: Label;
14
15
  className?: string;
15
16
  icon?: string;
16
- iconClassName?: string;
17
+ iconHue?: string;
17
18
  disabled?: boolean;
18
19
  current?: boolean;
19
20
  onSelect?: (option: boolean) => void;
@@ -21,8 +22,9 @@ export type TreeItemHeadingProps = {
21
22
 
22
23
  export const TreeItemHeading = memo(
23
24
  forwardRef<HTMLButtonElement, TreeItemHeadingProps>(
24
- ({ label, className, icon, iconClassName, disabled, current, onSelect }, forwardedRef) => {
25
+ ({ label, className, icon, iconHue, disabled, current, onSelect }, forwardedRef) => {
25
26
  const { t } = useTranslation();
27
+ const styles = iconHue ? getStyles(iconHue) : undefined;
26
28
 
27
29
  const handleSelect = useCallback(
28
30
  (event: MouseEvent) => {
@@ -56,7 +58,7 @@ export const TreeItemHeading = memo(
56
58
  variant='ghost'
57
59
  density='fine'
58
60
  classNames={[
59
- 'grow gap-2 pis-0.5 hover:bg-transparent dark:hover:bg-transparent',
61
+ 'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
60
62
  'disabled:cursor-default disabled:opacity-100',
61
63
  className,
62
64
  ]}
@@ -65,8 +67,10 @@ export const TreeItemHeading = memo(
65
67
  onKeyDown={handleButtonKeydown}
66
68
  {...(current && { 'aria-current': 'location' })}
67
69
  >
68
- {icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['mlb-1', iconClassName]} />}
69
- <span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
70
+ {icon && (
71
+ <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['my-1', styles?.surfaceText]} />
72
+ )}
73
+ <span className='flex-1 w-0 truncate text-start font-normal' data-tooltip>
70
74
  {toLocalizedString(label, t)}
71
75
  </span>
72
76
  </Button>
@@ -23,7 +23,7 @@ export const TreeItemToggle = memo(
23
23
  variant='ghost'
24
24
  density='fine'
25
25
  classNames={[
26
- 'bs-full is-6 pli-0',
26
+ 'h-full w-6 px-0',
27
27
  '[&_svg]:transition-[transform] [&_svg]:duration-200',
28
28
  open && '[&_svg]:rotate-90',
29
29
  hidden ? 'hidden' : !isBranch && 'invisible',
@@ -5,3 +5,5 @@
5
5
  export * from './Tree';
6
6
  export * from './TreeContext';
7
7
  export * from './TreeItem';
8
+ export * from './TreeItemToggle';
9
+ export * from './helpers';
@@ -5,20 +5,21 @@
5
5
  import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
6
6
  import * as Schema from 'effect/Schema';
7
7
 
8
- import { type HasId, ObjectId } from '@dxos/echo/internal';
8
+ import { Obj } from '@dxos/echo';
9
9
  import { log } from '@dxos/log';
10
10
  import { faker } from '@dxos/random';
11
11
 
12
12
  import { type TreeData } from './TreeItem';
13
13
 
14
- export type TestItem = HasId & {
14
+ export type TestItem = {
15
+ id: string;
15
16
  name: string;
16
17
  icon?: string;
17
18
  items: TestItem[];
18
19
  };
19
20
 
20
21
  export const TestItemSchema = Schema.Struct({
21
- id: ObjectId,
22
+ id: Obj.ID,
22
23
  name: Schema.String,
23
24
  icon: Schema.optional(Schema.String),
24
25
  items: Schema.mutable(Schema.Array(Schema.suspend((): Schema.Schema<TestItem> => TestItemSchema))),