@dxos/react-ui-list 0.8.4-main.84f28bd → 0.8.4-main.ae835ea

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