@dxos/react-ui-list 0.8.4-main.b97322e → 0.8.4-main.c4373fc

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 (50) hide show
  1. package/dist/lib/browser/index.mjs +91 -89
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +91 -89
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Accordion/Accordion.stories.d.ts +10 -4
  8. package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/Accordion/AccordionItem.d.ts +1 -1
  10. package/dist/types/src/components/Accordion/AccordionItem.d.ts.map +1 -1
  11. package/dist/types/src/components/List/List.d.ts +6 -6
  12. package/dist/types/src/components/List/List.d.ts.map +1 -1
  13. package/dist/types/src/components/List/List.stories.d.ts +14 -5
  14. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/List/ListItem.d.ts +4 -7
  16. package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
  17. package/dist/types/src/components/List/ListRoot.d.ts.map +1 -1
  18. package/dist/types/src/components/List/testing.d.ts +1 -1
  19. package/dist/types/src/components/List/testing.d.ts.map +1 -1
  20. package/dist/types/src/components/Tree/Tree.d.ts +3 -3
  21. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  22. package/dist/types/src/components/Tree/Tree.stories.d.ts +36 -6
  23. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/Tree/TreeContext.d.ts +3 -2
  25. package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
  26. package/dist/types/src/components/Tree/TreeItem.d.ts +14 -9
  27. package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
  28. package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
  29. package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
  30. package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
  31. package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -1
  32. package/dist/types/src/components/Tree/testing.d.ts +2 -2
  33. package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
  34. package/dist/types/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +25 -24
  36. package/src/components/Accordion/Accordion.stories.tsx +8 -7
  37. package/src/components/Accordion/Accordion.tsx +1 -1
  38. package/src/components/Accordion/AccordionItem.tsx +5 -2
  39. package/src/components/List/List.stories.tsx +16 -14
  40. package/src/components/List/List.tsx +2 -5
  41. package/src/components/List/ListItem.tsx +39 -27
  42. package/src/components/List/ListRoot.tsx +1 -1
  43. package/src/components/List/testing.ts +2 -2
  44. package/src/components/Tree/Tree.stories.tsx +51 -48
  45. package/src/components/Tree/Tree.tsx +7 -2
  46. package/src/components/Tree/TreeContext.tsx +3 -2
  47. package/src/components/Tree/TreeItem.tsx +52 -42
  48. package/src/components/Tree/TreeItemHeading.tsx +7 -6
  49. package/src/components/Tree/TreeItemToggle.tsx +29 -18
  50. package/src/components/Tree/testing.ts +2 -2
@@ -5,34 +5,35 @@
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 {
8
- attachInstruction,
9
- extractInstruction,
10
8
  type Instruction,
11
9
  type ItemMode,
10
+ attachInstruction,
11
+ extractInstruction,
12
12
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
13
- import { Schema } from 'effect';
14
- import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type FC, type KeyboardEvent } from 'react';
13
+ import * as Schema from 'effect/Schema';
14
+ import React, { type FC, type KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
 
16
- import { type HasId } from '@dxos/echo-schema';
16
+ import { type HasId } from '@dxos/echo/internal';
17
17
  import { invariant } from '@dxos/invariant';
18
- import { Treegrid, TreeItem as NaturalTreeItem } from '@dxos/react-ui';
18
+ import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
19
19
  import {
20
+ ghostFocusWithin,
20
21
  ghostHover,
21
22
  hoverableControls,
22
23
  hoverableFocusedKeyboardControls,
23
24
  hoverableFocusedWithinControls,
24
25
  } from '@dxos/react-ui-theme';
25
26
 
27
+ import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
26
28
  import { useTree } from './TreeContext';
27
29
  import { TreeItemHeading } from './TreeItemHeading';
28
30
  import { TreeItemToggle } from './TreeItemToggle';
29
- import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
30
-
31
- type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
32
31
 
33
32
  const hoverableDescriptionIcons =
34
33
  '[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
35
34
 
35
+ type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
36
+
36
37
  export const TreeDataSchema = Schema.Struct({
37
38
  id: Schema.String,
38
39
  path: Schema.Array(Schema.String),
@@ -40,23 +41,25 @@ export const TreeDataSchema = Schema.Struct({
40
41
  });
41
42
 
42
43
  export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
43
-
44
44
  export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
45
45
 
46
+ export type ColumnRenderer<T extends HasId = any> = FC<{
47
+ item: T;
48
+ path: string[];
49
+ open: boolean;
50
+ menuOpen: boolean;
51
+ setMenuOpen: (open: boolean) => void;
52
+ }>;
53
+
46
54
  export type TreeItemProps<T extends HasId = any> = {
47
55
  item: T;
48
56
  path: string[];
49
57
  levelOffset?: number;
50
58
  last: boolean;
51
59
  draggable?: boolean;
52
- renderColumns?: FC<{
53
- item: T;
54
- path: string[];
55
- open: boolean;
56
- menuOpen: boolean;
57
- setMenuOpen: (open: boolean) => void;
58
- }>;
60
+ renderColumns?: ColumnRenderer<T>;
59
61
  canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
62
+ canSelect?: (params: { item: T; path: string[] }) => boolean;
60
63
  onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
61
64
  onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
62
65
  };
@@ -64,13 +67,14 @@ export type TreeItemProps<T extends HasId = any> = {
64
67
  const RawTreeItem = <T extends HasId = any>({
65
68
  item,
66
69
  path: _path,
70
+ levelOffset = 2,
67
71
  last,
68
72
  draggable: _draggable,
69
73
  renderColumns: Columns,
70
74
  canDrop,
75
+ canSelect,
71
76
  onOpenChange,
72
77
  onSelect,
73
- levelOffset = 2,
74
78
  }: TreeItemProps<T>) => {
75
79
  const rowRef = useRef<HTMLDivElement | null>(null);
76
80
  const buttonRef = useRef<HTMLButtonElement | null>(null);
@@ -82,13 +86,17 @@ const RawTreeItem = <T extends HasId = any>({
82
86
 
83
87
  const { useItems, getProps, isOpen, isCurrent } = useTree();
84
88
  const items = useItems(item);
85
- const { id, label, parentOf, icon, disabled, className, headingClassName, testId } = getProps(item, _path);
89
+ const { id, parentOf, label, className, headingClassName, icon, iconClassName, disabled, testId } = getProps(
90
+ item,
91
+ _path,
92
+ );
86
93
  const path = useMemo(() => [..._path, id], [_path, id]);
87
94
  const open = isOpen(path, item);
88
95
  const current = isCurrent(path, item);
89
96
  const level = path.length - levelOffset;
90
97
  const isBranch = !!parentOf;
91
98
  const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
99
+ const canSelectItem = canSelect?.({ item, path }) ?? true;
92
100
 
93
101
  const cancelExpand = useCallback(() => {
94
102
  if (cancelExpandRef.current) {
@@ -180,38 +188,37 @@ const RawTreeItem = <T extends HasId = any>({
180
188
  // Cancel expand on unmount.
181
189
  useEffect(() => () => cancelExpand(), [cancelExpand]);
182
190
 
183
- const handleOpenChange = useCallback(
191
+ const handleOpenToggle = useCallback(
184
192
  () => onOpenChange?.({ item, path, open: !open }),
185
193
  [onOpenChange, item, path, open],
186
194
  );
187
195
 
188
196
  const handleSelect = useCallback(
189
197
  (option = false) => {
190
- if (isBranch) {
191
- handleOpenChange();
192
- } else {
198
+ // If the item is a branch, toggle it if:
199
+ // - also holding down the option key
200
+ // - or the item is currently selected
201
+ if (isBranch && (option || current)) {
202
+ handleOpenToggle();
203
+ } else if (canSelectItem) {
204
+ canSelect?.({ item, path });
193
205
  rowRef.current?.focus();
194
206
  onSelect?.({ item, path, current: !current, option });
195
207
  }
196
208
  },
197
- [item, path, current, isBranch, handleOpenChange, onSelect],
209
+ [item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
198
210
  );
199
211
 
200
212
  const handleKeyDown = useCallback(
201
213
  (event: KeyboardEvent) => {
202
214
  switch (event.key) {
203
215
  case 'ArrowRight':
204
- isBranch && !open && handleOpenChange();
205
- break;
206
216
  case 'ArrowLeft':
207
- isBranch && open && handleOpenChange();
208
- break;
209
- case ' ':
210
- handleSelect(event.altKey);
217
+ isBranch && handleOpenToggle();
211
218
  break;
212
219
  }
213
220
  },
214
- [isBranch, open, handleOpenChange, handleSelect],
221
+ [isBranch, open, handleOpenToggle, handleSelect],
215
222
  );
216
223
 
217
224
  return (
@@ -229,6 +236,7 @@ const RawTreeItem = <T extends HasId = any>({
229
236
  hoverableFocusedWithinControls,
230
237
  hoverableDescriptionIcons,
231
238
  ghostHover,
239
+ ghostFocusWithin,
232
240
  className,
233
241
  ]}
234
242
  data-itemid={id}
@@ -243,26 +251,27 @@ const RawTreeItem = <T extends HasId = any>({
243
251
  setMenuOpen(true);
244
252
  }}
245
253
  >
246
- <Treegrid.Cell
247
- indent
248
- classNames='relative grid grid-cols-subgrid col-[tree-row]'
254
+ <div
255
+ role='none'
256
+ className='indent relative grid grid-cols-subgrid col-[tree-row]'
249
257
  style={paddingIndentation(level)}
250
258
  >
251
- <div role='none' className='flex items-center'>
252
- <TreeItemToggle isBranch={isBranch} open={open} onToggle={handleOpenChange} />
259
+ <Treegrid.Cell classNames='flex items-center'>
260
+ <TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
253
261
  <TreeItemHeading
254
- ref={buttonRef}
255
- label={label}
256
- icon={icon}
257
- className={headingClassName}
258
262
  disabled={disabled}
259
263
  current={current}
264
+ label={label}
265
+ className={headingClassName}
266
+ icon={icon}
267
+ iconClassName={iconClassName}
260
268
  onSelect={handleSelect}
269
+ ref={buttonRef}
261
270
  />
262
- </div>
271
+ </Treegrid.Cell>
263
272
  {Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
264
273
  {instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
265
- </Treegrid.Cell>
274
+ </div>
266
275
  </Treegrid.Row>
267
276
  {open &&
268
277
  items.map((item, index) => (
@@ -274,6 +283,7 @@ const RawTreeItem = <T extends HasId = any>({
274
283
  draggable={_draggable}
275
284
  renderColumns={Columns}
276
285
  canDrop={canDrop}
286
+ canSelect={canSelect}
277
287
  onOpenChange={onOpenChange}
278
288
  onSelect={onSelect}
279
289
  />
@@ -4,23 +4,24 @@
4
4
 
5
5
  import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallback } from 'react';
6
6
 
7
- import { Button, Icon, toLocalizedString, useTranslation, type Label } from '@dxos/react-ui';
7
+ import { Button, Icon, type Label, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
8
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
9
9
 
10
10
  // TODO(wittjosiah): Consider whether there should be a separate disabled prop which was visually distinct
11
11
  // rather than just making the item unselectable.
12
- export type NavTreeItemHeadingProps = {
12
+ export type TreeItemHeadingProps = {
13
13
  label: Label;
14
- icon?: string;
15
14
  className?: string;
15
+ icon?: string;
16
+ iconClassName?: string;
16
17
  disabled?: boolean;
17
18
  current?: boolean;
18
19
  onSelect?: (option: boolean) => void;
19
20
  };
20
21
 
21
22
  export const TreeItemHeading = memo(
22
- forwardRef<HTMLButtonElement, NavTreeItemHeadingProps>(
23
- ({ label, icon, className, disabled, current, onSelect }, forwardedRef) => {
23
+ forwardRef<HTMLButtonElement, TreeItemHeadingProps>(
24
+ ({ label, className, icon, iconClassName, disabled, current, onSelect }, forwardedRef) => {
24
25
  const { t } = useTranslation();
25
26
 
26
27
  const handleSelect = useCallback(
@@ -64,7 +65,7 @@ export const TreeItemHeading = memo(
64
65
  onKeyDown={handleButtonKeydown}
65
66
  {...(current && { 'aria-current': 'location' })}
66
67
  >
67
- {icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames='mlb-1' />}
68
+ {icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['mlb-1', iconClassName]} />}
68
69
  <span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
69
70
  {toLocalizedString(label, t)}
70
71
  </span>
@@ -4,29 +4,40 @@
4
4
 
5
5
  import React, { forwardRef, memo } from 'react';
6
6
 
7
- import { Button, Icon } from '@dxos/react-ui';
7
+ import { IconButton, type IconButtonProps } from '@dxos/react-ui';
8
8
 
9
- export type TreeItemToggleProps = {
9
+ export type TreeItemToggleProps = Omit<IconButtonProps, 'icon' | 'size' | 'label'> & {
10
10
  open?: boolean;
11
11
  isBranch?: boolean;
12
- onToggle?: () => void;
13
12
  hidden?: boolean;
14
13
  };
15
14
 
16
15
  export const TreeItemToggle = memo(
17
- forwardRef<HTMLButtonElement, TreeItemToggleProps>(({ open, isBranch, hidden, onToggle }, forwardedRef) => {
18
- return (
19
- <Button
20
- ref={forwardedRef}
21
- data-testid='treeItem.toggle'
22
- aria-expanded={open}
23
- variant='ghost'
24
- density='fine'
25
- classNames={['is-6 pli-0 dx-focus-ring-inset', hidden ? 'hidden' : !isBranch && 'invisible']}
26
- onClick={onToggle}
27
- >
28
- <Icon icon='ph--caret-right--bold' size={3} classNames={['transition duration-200', open && 'rotate-90']} />
29
- </Button>
30
- );
31
- }),
16
+ forwardRef<HTMLButtonElement, TreeItemToggleProps>(
17
+ ({ open, isBranch, hidden, classNames, ...props }, forwardedRef) => {
18
+ return (
19
+ <IconButton
20
+ ref={forwardedRef}
21
+ data-testid='treeItem.toggle'
22
+ aria-expanded={open}
23
+ variant='ghost'
24
+ density='fine'
25
+ classNames={[
26
+ 'bs-full is-6 pli-0',
27
+ '[&_svg]:transition-[transform] [&_svg]:duration-200',
28
+ open && '[&_svg]:rotate-90',
29
+ hidden ? 'hidden' : !isBranch && 'invisible',
30
+ classNames,
31
+ ]}
32
+ size={3}
33
+ icon='ph--caret-right--bold'
34
+ iconOnly
35
+ noTooltip
36
+ label={open ? 'Click to close' : 'Click to open'}
37
+ tabIndex={-1}
38
+ {...props}
39
+ />
40
+ );
41
+ },
42
+ ),
32
43
  );
@@ -3,9 +3,9 @@
3
3
  //
4
4
 
5
5
  import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
6
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
7
7
 
8
- import { type HasId, ObjectId } from '@dxos/echo-schema';
8
+ import { type HasId, ObjectId } from '@dxos/echo/internal';
9
9
  import { log } from '@dxos/log';
10
10
  import { faker } from '@dxos/random';
11
11