@dxos/react-ui-list 0.8.4-main.3f58842 → 0.8.4-main.548089c

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 +94 -90
  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 +94 -90
  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 +6 -8
  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 +19 -17
  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 +50 -43
  48. package/src/components/Tree/TreeItemHeading.tsx +9 -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,14 @@ 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, iconHue, disabled, testId } = getProps(item, _path);
86
90
  const path = useMemo(() => [..._path, id], [_path, id]);
87
91
  const open = isOpen(path, item);
88
92
  const current = isCurrent(path, item);
89
93
  const level = path.length - levelOffset;
90
94
  const isBranch = !!parentOf;
91
95
  const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
96
+ const canSelectItem = canSelect?.({ item, path }) ?? true;
92
97
 
93
98
  const cancelExpand = useCallback(() => {
94
99
  if (cancelExpandRef.current) {
@@ -180,38 +185,37 @@ const RawTreeItem = <T extends HasId = any>({
180
185
  // Cancel expand on unmount.
181
186
  useEffect(() => () => cancelExpand(), [cancelExpand]);
182
187
 
183
- const handleOpenChange = useCallback(
188
+ const handleOpenToggle = useCallback(
184
189
  () => onOpenChange?.({ item, path, open: !open }),
185
190
  [onOpenChange, item, path, open],
186
191
  );
187
192
 
188
193
  const handleSelect = useCallback(
189
194
  (option = false) => {
190
- if (isBranch) {
191
- handleOpenChange();
192
- } 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 });
193
202
  rowRef.current?.focus();
194
203
  onSelect?.({ item, path, current: !current, option });
195
204
  }
196
205
  },
197
- [item, path, current, isBranch, handleOpenChange, onSelect],
206
+ [item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
198
207
  );
199
208
 
200
209
  const handleKeyDown = useCallback(
201
210
  (event: KeyboardEvent) => {
202
211
  switch (event.key) {
203
212
  case 'ArrowRight':
204
- isBranch && !open && handleOpenChange();
205
- break;
206
213
  case 'ArrowLeft':
207
- isBranch && open && handleOpenChange();
208
- break;
209
- case ' ':
210
- handleSelect(event.altKey);
214
+ isBranch && handleOpenToggle();
211
215
  break;
212
216
  }
213
217
  },
214
- [isBranch, open, handleOpenChange, handleSelect],
218
+ [isBranch, open, handleOpenToggle, handleSelect],
215
219
  );
216
220
 
217
221
  return (
@@ -229,9 +233,10 @@ const RawTreeItem = <T extends HasId = any>({
229
233
  hoverableFocusedWithinControls,
230
234
  hoverableDescriptionIcons,
231
235
  ghostHover,
236
+ ghostFocusWithin,
232
237
  className,
233
238
  ]}
234
- data-itemid={id}
239
+ data-object-id={id}
235
240
  data-testid={testId}
236
241
  // NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
237
242
  // without alerting the user (except for in the correct link element). See also:
@@ -243,26 +248,27 @@ const RawTreeItem = <T extends HasId = any>({
243
248
  setMenuOpen(true);
244
249
  }}
245
250
  >
246
- <Treegrid.Cell
247
- indent
248
- 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]'
249
254
  style={paddingIndentation(level)}
250
255
  >
251
- <div role='none' className='flex items-center'>
252
- <TreeItemToggle isBranch={isBranch} open={open} onToggle={handleOpenChange} />
256
+ <Treegrid.Cell classNames='flex items-center'>
257
+ <TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
253
258
  <TreeItemHeading
254
- ref={buttonRef}
255
- label={label}
256
- icon={icon}
257
- className={headingClassName}
258
259
  disabled={disabled}
259
260
  current={current}
261
+ label={label}
262
+ className={headingClassName}
263
+ icon={icon}
264
+ iconHue={iconHue}
260
265
  onSelect={handleSelect}
266
+ ref={buttonRef}
261
267
  />
262
- </div>
268
+ </Treegrid.Cell>
263
269
  {Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
264
270
  {instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
265
- </Treegrid.Cell>
271
+ </div>
266
272
  </Treegrid.Row>
267
273
  {open &&
268
274
  items.map((item, index) => (
@@ -274,6 +280,7 @@ const RawTreeItem = <T extends HasId = any>({
274
280
  draggable={_draggable}
275
281
  renderColumns={Columns}
276
282
  canDrop={canDrop}
283
+ canSelect={canSelect}
277
284
  onOpenChange={onOpenChange}
278
285
  onSelect={onSelect}
279
286
  />
@@ -4,24 +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 { getStyles } from '@dxos/react-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.
12
- export type NavTreeItemHeadingProps = {
13
+ export type TreeItemHeadingProps = {
13
14
  label: Label;
14
- icon?: string;
15
15
  className?: string;
16
+ icon?: string;
17
+ iconHue?: string;
16
18
  disabled?: boolean;
17
19
  current?: boolean;
18
20
  onSelect?: (option: boolean) => void;
19
21
  };
20
22
 
21
23
  export const TreeItemHeading = memo(
22
- forwardRef<HTMLButtonElement, NavTreeItemHeadingProps>(
23
- ({ label, icon, className, disabled, current, onSelect }, forwardedRef) => {
24
+ forwardRef<HTMLButtonElement, TreeItemHeadingProps>(
25
+ ({ label, className, icon, iconHue, disabled, current, onSelect }, forwardedRef) => {
24
26
  const { t } = useTranslation();
27
+ const styles = iconHue ? getStyles(iconHue) : undefined;
25
28
 
26
29
  const handleSelect = useCallback(
27
30
  (event: MouseEvent) => {
@@ -64,7 +67,7 @@ export const TreeItemHeading = memo(
64
67
  onKeyDown={handleButtonKeydown}
65
68
  {...(current && { 'aria-current': 'location' })}
66
69
  >
67
- {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]} />}
68
71
  <span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
69
72
  {toLocalizedString(label, t)}
70
73
  </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