@snack-uikit/list 0.10.0 → 0.10.1-preview-69abc1d3.0

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 (127) hide show
  1. package/README.md +56 -24
  2. package/dist/components/Items/AccordionItem/AccordionItem.d.ts +4 -2
  3. package/dist/components/Items/AccordionItem/AccordionItem.js +18 -16
  4. package/dist/components/Items/BaseItem/BaseItem.d.ts +8 -4
  5. package/dist/components/Items/BaseItem/BaseItem.js +20 -14
  6. package/dist/components/Items/BaseItem/styles.module.css +9 -122
  7. package/dist/components/Items/GroupItem/GroupItem.d.ts +5 -1
  8. package/dist/components/Items/GroupItem/GroupItem.js +1 -3
  9. package/dist/components/Items/GroupSelectItem/GroupSelectItem.d.ts +4 -0
  10. package/dist/components/Items/GroupSelectItem/GroupSelectItem.js +18 -0
  11. package/dist/components/Items/GroupSelectItem/index.d.ts +1 -0
  12. package/dist/components/Items/GroupSelectItem/index.js +1 -0
  13. package/dist/components/Items/NextListItem/NextListItem.d.ts +6 -2
  14. package/dist/components/Items/NextListItem/NextListItem.js +53 -45
  15. package/dist/components/Items/NextListItem/constants.d.ts +2 -0
  16. package/dist/components/Items/NextListItem/constants.js +10 -0
  17. package/dist/components/Items/SearchItem/SearchItem.d.ts +3 -1
  18. package/dist/components/Items/SearchItem/SearchItem.js +5 -8
  19. package/dist/components/Items/hooks.d.ts +11 -10
  20. package/dist/components/Items/hooks.js +64 -46
  21. package/dist/components/Items/index.d.ts +1 -5
  22. package/dist/components/Items/index.js +1 -5
  23. package/dist/components/Items/types.d.ts +68 -34
  24. package/dist/components/Items/utils.d.ts +33 -16
  25. package/dist/components/Items/utils.js +132 -37
  26. package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
  27. package/dist/components/Lists/Droplist/DropList.js +73 -50
  28. package/dist/components/Lists/List/List.d.ts +5 -9
  29. package/dist/components/Lists/List/List.js +56 -54
  30. package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +6 -20
  31. package/dist/components/Lists/ListPrivate/ListPrivate.js +9 -8
  32. package/dist/components/Lists/ListPrivate/styles.module.css +1 -0
  33. package/dist/components/Lists/contexts/CollapseProvider.d.ts +13 -1
  34. package/dist/components/Lists/contexts/CollapseProvider.js +2 -0
  35. package/dist/components/Lists/contexts/FocusListProvider.d.ts +9 -0
  36. package/dist/components/Lists/contexts/FocusListProvider.js +5 -0
  37. package/dist/components/Lists/contexts/NewListProvider.d.ts +32 -0
  38. package/dist/components/Lists/contexts/{ListProvider.js → NewListProvider.js} +10 -5
  39. package/dist/components/Lists/contexts/SelectionProvider.d.ts +25 -15
  40. package/dist/components/Lists/contexts/SelectionProvider.js +17 -13
  41. package/dist/components/Lists/contexts/index.d.ts +2 -2
  42. package/dist/components/Lists/contexts/index.js +2 -2
  43. package/dist/components/Lists/hooks.d.ts +10 -12
  44. package/dist/components/Lists/hooks.js +73 -40
  45. package/dist/components/Lists/types.d.ts +12 -14
  46. package/dist/components/index.d.ts +2 -2
  47. package/dist/components/index.js +1 -1
  48. package/dist/helperComponents/ItemContent/ItemContent.d.ts +15 -0
  49. package/dist/helperComponents/ItemContent/ItemContent.js +23 -0
  50. package/dist/helperComponents/ItemContent/index.d.ts +1 -0
  51. package/dist/helperComponents/ItemContent/index.js +1 -0
  52. package/dist/helperComponents/ItemContent/styles.module.css +117 -0
  53. package/dist/helperComponents/ListEmptyState/ListEmptyState.d.ts +2 -2
  54. package/dist/helperComponents/ListEmptyState/ListEmptyState.js +13 -4
  55. package/dist/helperComponents/Separator/Separator.d.ts +8 -2
  56. package/dist/helperComponents/Separator/Separator.js +22 -2
  57. package/dist/helperComponents/Separator/constants.d.ts +2 -0
  58. package/dist/helperComponents/Separator/constants.js +5 -0
  59. package/dist/helperComponents/Separator/styles.module.css +20 -1
  60. package/dist/helperComponents/index.d.ts +1 -0
  61. package/dist/helperComponents/index.js +1 -0
  62. package/dist/hooks.d.ts +1 -1
  63. package/dist/hooks.js +15 -10
  64. package/dist/index.d.ts +3 -2
  65. package/dist/index.js +3 -2
  66. package/dist/legacy/components/Items/hooks.d.ts +12 -0
  67. package/dist/legacy/components/Items/hooks.js +33 -0
  68. package/dist/legacy/components/index.d.ts +1 -0
  69. package/dist/legacy/components/index.js +1 -0
  70. package/dist/legacy/hooks.d.ts +5 -0
  71. package/dist/legacy/hooks.js +15 -0
  72. package/dist/legacy/index.d.ts +3 -0
  73. package/dist/legacy/index.js +3 -0
  74. package/dist/{utils.d.ts → legacy/utils.d.ts} +2 -3
  75. package/dist/{utils.js → legacy/utils.js} +4 -12
  76. package/dist/types.d.ts +1 -1
  77. package/package.json +14 -14
  78. package/src/components/Items/AccordionItem/AccordionItem.tsx +24 -20
  79. package/src/components/Items/BaseItem/BaseItem.tsx +48 -43
  80. package/src/components/Items/BaseItem/styles.module.scss +8 -70
  81. package/src/components/Items/GroupItem/GroupItem.tsx +5 -5
  82. package/src/components/Items/GroupSelectItem/GroupSelectItem.tsx +44 -0
  83. package/src/components/Items/GroupSelectItem/index.ts +1 -0
  84. package/src/components/Items/NextListItem/NextListItem.tsx +86 -85
  85. package/src/components/Items/NextListItem/constants.ts +12 -0
  86. package/src/components/Items/SearchItem/SearchItem.tsx +6 -8
  87. package/src/components/Items/hooks.tsx +86 -66
  88. package/src/components/Items/index.ts +1 -5
  89. package/src/components/Items/types.ts +91 -42
  90. package/src/components/Items/utils.ts +193 -52
  91. package/src/components/Lists/Droplist/DropList.tsx +151 -98
  92. package/src/components/Lists/List/List.tsx +124 -104
  93. package/src/components/Lists/ListPrivate/ListPrivate.tsx +19 -20
  94. package/src/components/Lists/ListPrivate/styles.module.scss +1 -0
  95. package/src/components/Lists/contexts/CollapseProvider.tsx +22 -0
  96. package/src/components/Lists/contexts/FocusListProvider.tsx +16 -0
  97. package/src/components/Lists/contexts/NewListProvider.tsx +54 -0
  98. package/src/components/Lists/contexts/SelectionProvider.tsx +56 -39
  99. package/src/components/Lists/contexts/index.ts +3 -2
  100. package/src/components/Lists/hooks.ts +124 -75
  101. package/src/components/Lists/types.ts +20 -24
  102. package/src/components/index.ts +16 -2
  103. package/src/helperComponents/ItemContent/ItemContent.tsx +63 -0
  104. package/src/helperComponents/ItemContent/index.ts +1 -0
  105. package/src/helperComponents/ItemContent/styles.module.scss +78 -0
  106. package/src/helperComponents/ListEmptyState/ListEmptyState.tsx +26 -12
  107. package/src/helperComponents/Separator/Separator.tsx +49 -3
  108. package/src/helperComponents/Separator/constants.ts +7 -0
  109. package/src/helperComponents/Separator/styles.module.scss +41 -1
  110. package/src/helperComponents/index.ts +1 -0
  111. package/src/hooks.ts +21 -15
  112. package/src/index.ts +5 -2
  113. package/src/legacy/components/Items/hooks.tsx +53 -0
  114. package/src/legacy/components/index.ts +1 -0
  115. package/src/legacy/hooks.ts +27 -0
  116. package/src/legacy/index.ts +3 -0
  117. package/src/{utils.ts → legacy/utils.ts} +4 -13
  118. package/src/types.ts +1 -1
  119. package/dist/components/Items/NextListItem/hooks.d.ts +0 -19
  120. package/dist/components/Items/NextListItem/hooks.js +0 -80
  121. package/dist/components/Lists/contexts/ListProvider.d.ts +0 -17
  122. package/dist/components/Lists/contexts/ParentListProvider.d.ts +0 -18
  123. package/dist/components/Lists/contexts/ParentListProvider.js +0 -12
  124. package/src/components/Items/NextListItem/hooks.ts +0 -105
  125. package/src/components/Lists/contexts/CollapseProvider.ts +0 -9
  126. package/src/components/Lists/contexts/ListProvider.tsx +0 -28
  127. package/src/components/Lists/contexts/ParentListProvider.tsx +0 -35
@@ -1,18 +1,31 @@
1
1
  import cn from 'classnames';
2
- import { FocusEvent, KeyboardEvent, MouseEvent, RefObject } from 'react';
2
+ import { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, RefObject } from 'react';
3
3
 
4
4
  import { Checkbox, Switch } from '@snack-uikit/toggles';
5
- import { TruncateString } from '@snack-uikit/truncate-string';
6
5
  import { extractSupportProps } from '@snack-uikit/utils';
7
6
 
8
- import { useCollapseContext, useListContext, useParentListContext, useSelectionContext } from '../../Lists/contexts';
7
+ import { ItemContent } from '../../../helperComponents';
8
+ import {
9
+ useCollapseLevelContext,
10
+ useFocusListContext,
11
+ useNewListContext,
12
+ useSelectionContext,
13
+ } from '../../Lists/contexts';
9
14
  import commonStyles from '../styles.module.scss';
10
- import { BaseItemPrivateProps, BaseItemProps } from '../types';
15
+ import { FlattenBaseItem } from '../types';
16
+ import { isContentItem } from '../utils';
11
17
  import { CHECKBOX_SIZE_MAP } from './constants';
12
18
  import styles from './styles.module.scss';
13
19
 
14
- type AllBaseItemProps = BaseItemProps &
15
- BaseItemPrivateProps & { indeterminate?: boolean; onSelect?(): void; isParentNode?: boolean };
20
+ type AllBaseItemProps = FlattenBaseItem & {
21
+ expandIcon?: ReactNode;
22
+ open?: boolean;
23
+ indeterminate?: boolean;
24
+ checked?: boolean;
25
+ onSelect?(): void;
26
+ isParentNode?: boolean;
27
+ onOpenNestedList?(e?: KeyboardEvent<HTMLElement>): void;
28
+ };
16
29
 
17
30
  export function BaseItem({
18
31
  beforeContent,
@@ -29,44 +42,54 @@ export function BaseItem({
29
42
  onKeyDown,
30
43
  onFocus,
31
44
  indeterminate,
45
+ checked: checkedProp,
32
46
  onSelect,
47
+ onOpenNestedList,
33
48
  isParentNode,
34
49
  className,
35
50
  inactive,
36
51
  itemWrapRender,
37
52
  ...rest
38
53
  }: AllBaseItemProps) {
39
- const { option, caption, description, truncate: contentTruncate } = content || {};
40
54
  const interactive = !inactive;
41
55
 
42
- const { parentResetActiveFocusIndex } = useParentListContext();
43
- const { size, marker, parent } = useListContext();
44
- const { level = 0 } = useCollapseContext();
56
+ const { size = 's', marker, contentRender } = useNewListContext();
57
+ const { level = 0 } = useCollapseLevelContext();
58
+ const { forceUpdateActiveItemId } = useFocusListContext();
45
59
  const { value, onChange, mode, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
46
60
 
47
- const isChecked = isSelectionSingle ? value === id : value?.includes(id);
61
+ const isChecked = isSelectionSingle ? value === id : checkedProp ?? value?.includes(id ?? '');
48
62
 
49
63
  const handleChange = () => {
50
- onChange?.(id ?? '');
64
+ onChange?.(id);
51
65
  };
52
66
 
53
67
  const handleItemMouseDown = (e: MouseEvent<HTMLElement>) => {
54
68
  if (disabled) return;
55
69
 
56
70
  if (interactive) {
57
- parentResetActiveFocusIndex?.();
58
-
59
71
  if (!isParentNode) {
60
72
  handleChange();
61
73
  }
62
74
  }
63
75
 
64
76
  onMouseDown?.(e);
77
+
78
+ forceUpdateActiveItemId?.('~drop-focus');
65
79
  };
66
80
 
67
81
  const handleItemKeyDown = (e: KeyboardEvent<HTMLElement>) => {
68
82
  onKeyDown?.(e);
69
83
 
84
+ if (e.code === 'ArrowRight') {
85
+ onOpenNestedList?.(e);
86
+
87
+ e.preventDefault();
88
+ e.stopPropagation();
89
+
90
+ return;
91
+ }
92
+
70
93
  if (e.code === 'Space' || e.key === 'Enter') {
71
94
  if (isSelectionMultiple && isParentNode && onSelect) {
72
95
  onSelect();
@@ -100,7 +123,7 @@ export function BaseItem({
100
123
 
101
124
  const props = extractSupportProps(rest);
102
125
 
103
- const item = (
126
+ const itemJSX = (
104
127
  <li
105
128
  data-type='outside'
106
129
  role={'menuitem'}
@@ -126,13 +149,14 @@ export function BaseItem({
126
149
  {!switchProp && isSelectionSingle && marker && !isParentNode && interactive && (
127
150
  <div className={styles.markerContainer} data-test-id='list__base-item-marker' />
128
151
  )}
152
+
129
153
  {!switchProp && isSelectionMultiple && interactive && (
130
154
  <div className={styles.checkbox}>
131
155
  <Checkbox
132
156
  size={CHECKBOX_SIZE_MAP[size ?? 's']}
133
157
  disabled={disabled}
134
158
  tabIndex={-1}
135
- onChange={handleCheckboxChange}
159
+ onChange={isParentNode ? handleCheckboxChange : undefined}
136
160
  checked={isChecked}
137
161
  data-test-id='list__base-item-checkbox'
138
162
  onClick={handleCheckboxClick}
@@ -142,43 +166,24 @@ export function BaseItem({
142
166
  )}
143
167
 
144
168
  {beforeContent && <div className={styles.beforeContent}>{beforeContent}</div>}
145
-
146
- <div className={styles.content}>
147
- <div className={styles.headline}>
148
- <span className={styles.option}>
149
- <TruncateString
150
- variant={contentTruncate?.variant}
151
- text={option}
152
- maxLines={contentTruncate?.option ?? 1}
153
- data-test-id='list__base-item-option'
154
- />
155
- </span>
156
- {caption && <span className={styles.caption}>{caption}</span>}
157
- </div>
158
-
159
- {description && (
160
- <div className={styles.description}>
161
- <TruncateString
162
- text={description}
163
- maxLines={contentTruncate?.description ?? 2}
164
- data-test-id='list__base-item-description'
165
- />
166
- </div>
167
- )}
168
- </div>
169
-
169
+ {content && isContentItem(content) ? (
170
+ contentRender?.({ id, content }) ?? <ItemContent disabled={disabled} {...content} />
171
+ ) : (
172
+ <div className={styles.content}> {content} </div>
173
+ )}
170
174
  {afterContent}
171
175
 
172
176
  {switchProp && interactive && (
173
177
  <Switch disabled={disabled} checked={isChecked} data-test-id='list__base-item-switch' />
174
178
  )}
179
+
175
180
  {!switchProp && expandIcon && <span className={styles.expandableIcon}>{expandIcon}</span>}
176
181
  </li>
177
182
  );
178
183
 
179
184
  if (!itemWrapRender) {
180
- return item;
185
+ return itemJSX;
181
186
  }
182
187
 
183
- return itemWrapRender(item);
188
+ return itemWrapRender(itemJSX);
184
189
  }
@@ -2,48 +2,6 @@
2
2
  @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
3
 
4
4
  $sizes: 's', 'm', 'l';
5
- $typography: (
6
- 's': (
7
- 'label': $sans-body-s,
8
- 'caption': $sans-body-s,
9
- 'description': $sans-body-s,
10
- 'separator': $light-label-m,
11
- ),
12
- 'm': (
13
- 'label': $sans-body-m,
14
- 'caption': $sans-body-s,
15
- 'description': $sans-body-s,
16
- 'separator': $light-label-l,
17
- ),
18
- 'l': (
19
- 'label': $sans-body-l,
20
- 'caption': $sans-body-m,
21
- 'description': $sans-body-m,
22
- 'separator': $light-label-l,
23
- ),
24
- );
25
-
26
- .headline {
27
- display: flex;
28
- align-items: center;
29
- }
30
-
31
- .option {
32
- overflow: hidden;
33
- flex: 1;
34
- color: $sys-neutral-text-main;
35
- }
36
-
37
- .caption {
38
- overflow: hidden;
39
- color: $sys-neutral-text-light;
40
- text-overflow: ellipsis;
41
- white-space: nowrap;
42
- }
43
-
44
- .description {
45
- color: $sys-neutral-text-support;
46
- }
47
5
 
48
6
  .checkbox {
49
7
  display: inline-flex;
@@ -69,8 +27,8 @@ $typography: (
69
27
  color: $sys-neutral-text-light;
70
28
 
71
29
  svg {
72
- width: 100% !important; /* stylelint-disable-line declaration-no-important */
73
- height: 100% !important; /* stylelint-disable-line declaration-no-important */
30
+ max-width: 100%;
31
+ max-height: 100%;
74
32
  }
75
33
  }
76
34
 
@@ -102,13 +60,6 @@ $typography: (
102
60
  }
103
61
  }
104
62
 
105
- .content {
106
- overflow: hidden;
107
- flex-grow: 1;
108
- flex-shrink: 1;
109
- box-sizing: border-box;
110
- }
111
-
112
63
  .droplistItem {
113
64
  width: 100%;
114
65
 
@@ -117,22 +68,6 @@ $typography: (
117
68
  padding-left: calc(
118
69
  var(--level, 0) * $dimension-050m + simple-var($drop-list, 'item', $size, 'container', 'padding-left')
119
70
  );
120
-
121
- .headline {
122
- @include composite-var($drop-list, 'item', $size, 'headline');
123
- }
124
-
125
- .option {
126
- @include composite-var($typography, $size, 'label');
127
- }
128
-
129
- .caption {
130
- @include composite-var($typography, $size, 'caption');
131
- }
132
-
133
- .description {
134
- @include composite-var($typography, $size, 'description');
135
- }
136
71
  }
137
72
  }
138
73
 
@@ -142,9 +77,6 @@ $typography: (
142
77
 
143
78
  &[aria-disabled],
144
79
  &[data-disabled] {
145
- .option,
146
- .description,
147
- .caption,
148
80
  .expandableIcon {
149
81
  color: $sys-neutral-text-disabled;
150
82
  }
@@ -192,3 +124,9 @@ $typography: (
192
124
  }
193
125
  }
194
126
  }
127
+
128
+ .content {
129
+ flex-grow: 1;
130
+ flex-shrink: 1;
131
+ box-sizing: border-box;
132
+ }
@@ -1,16 +1,16 @@
1
1
  import { Separator } from '../../../helperComponents';
2
- import { useListContext } from '../../Lists/contexts';
3
2
  import { useRenderItems } from '../hooks';
4
- import { GroupItemProps } from '../types';
3
+ import { FlattenGroupListItem, ItemId } from '../types';
5
4
 
6
- export function GroupItem({ label, truncate, divider, items, mode }: GroupItemProps) {
7
- const { size } = useListContext();
5
+ type GroupItemProps = Omit<FlattenGroupListItem, 'type'> & { items: ItemId[] };
8
6
 
7
+ export function GroupItem({ label, truncate, divider, items, mode }: GroupItemProps) {
9
8
  const itemsJSX = useRenderItems(items);
10
9
 
11
10
  return (
12
11
  <>
13
- <Separator label={label} truncate={truncate} divider={divider} mode={mode} size={size} />
12
+ <Separator label={label} truncate={truncate} divider={divider} mode={mode} />
13
+
14
14
  {itemsJSX}
15
15
  </>
16
16
  );
@@ -0,0 +1,44 @@
1
+ import { Separator } from '../../../helperComponents';
2
+ import { useGroupItemSelection, useRenderItems } from '../hooks';
3
+ import { CommonFlattenProps, FlattenGroupSelectListItem } from '../types';
4
+
5
+ type GroupSelectItemProps = Omit<FlattenGroupSelectListItem, 'type'> & CommonFlattenProps;
6
+
7
+ export function GroupSelectItem({
8
+ label,
9
+ truncate,
10
+ divider,
11
+ items,
12
+ mode,
13
+ id,
14
+ itemRef,
15
+ allChildIds,
16
+ }: GroupSelectItemProps) {
17
+ const { indeterminate, checked, handleOnSelect } = useGroupItemSelection({
18
+ items,
19
+ id,
20
+ disabled: false,
21
+ allChildIds,
22
+ });
23
+
24
+ const itemsJSX = useRenderItems(items);
25
+
26
+ return (
27
+ <>
28
+ <Separator
29
+ label={label}
30
+ truncate={truncate}
31
+ divider={divider}
32
+ mode={mode}
33
+ selectButton={{
34
+ indeterminate,
35
+ checked,
36
+ itemRef,
37
+ onClick: handleOnSelect,
38
+ }}
39
+ />
40
+
41
+ {itemsJSX}
42
+ </>
43
+ );
44
+ }
@@ -0,0 +1 @@
1
+ export * from './GroupSelectItem';
@@ -1,122 +1,116 @@
1
- import { useCallback, useMemo, useRef, useState } from 'react';
1
+ import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
- import { Dropdown, DropdownProps } from '@snack-uikit/dropdown';
3
+ import { Dropdown } from '@snack-uikit/dropdown';
4
4
  import { ChevronRightSVG } from '@snack-uikit/icons';
5
5
 
6
- import { withCollapsedItems } from '../../../utils';
7
- import { ParentListContext, useParentListContext } from '../../Lists/contexts';
6
+ import { useCollapseContext, useFocusListContext, useNewListContext } from '../../Lists/contexts';
8
7
  import { ListPrivate } from '../../Lists/ListPrivate';
9
8
  import { BaseItem } from '../BaseItem';
10
9
  import { useGroupItemSelection } from '../hooks';
11
- import { NextListItemProps } from '../types';
12
- import { useKeyboardNavigation } from './hooks';
13
-
14
- const FALLBACK_PLACEMENTS: DropdownProps['fallbackPlacements'] = [
15
- 'right',
16
- 'right-start',
17
- 'right-end',
18
- 'left',
19
- 'left-start',
20
- 'left-end',
21
- ];
10
+ import { CommonFlattenProps, FlattenNextListItem, ItemId } from '../types';
11
+ import { extractActiveItems, isNextListItem } from '../utils';
12
+ import { FALLBACK_PLACEMENTS } from './constants';
13
+
14
+ type NextListItemProps = Omit<FlattenNextListItem, 'type'> & CommonFlattenProps & { focusId?: ItemId };
22
15
 
23
16
  export function NextListItem({
24
- items: itemsProp,
17
+ items,
25
18
  placement = 'right-start',
26
19
  id,
27
- search,
28
20
  scroll,
29
21
  scrollRef,
30
22
  disabled,
31
23
  onSublistOpenChanged,
24
+ allChildIds,
32
25
  loading = false,
26
+ focusId = id,
33
27
  ...option
34
28
  }: NextListItemProps) {
35
- const listRef = useRef<HTMLUListElement>(null);
29
+ const { flattenItems, focusFlattenItems } = useNewListContext();
30
+ const { openCollapseItems = [] } = useCollapseContext();
36
31
 
37
- const [openCollapsedItems, setOpenCollapsedItems] = useState<Array<number | string>>([]);
32
+ const item = flattenItems[id];
38
33
 
39
- const { items, itemRefs, ids, expandedIds } = useMemo(
40
- () =>
41
- withCollapsedItems({
42
- items: itemsProp,
43
- openCollapsedItems,
44
- }),
45
- [itemsProp, openCollapsedItems],
46
- );
34
+ const { ids, expandedIds } = useMemo(() => {
35
+ const { ids, expandedIds } = extractActiveItems({
36
+ focusCloseChildIds: items,
37
+ focusFlattenItems,
38
+ openCollapseItems,
39
+ });
40
+
41
+ return { ids, expandedIds: expandedIds.concat([id]) };
42
+ }, [focusFlattenItems, id, items, openCollapseItems]);
43
+
44
+ const { handleListKeyDownFactory, activeItemId, forceUpdateActiveItemId } = useFocusListContext();
45
+
46
+ const [open, setOpen] = useState<boolean>();
47
+
48
+ const handleListKeyDown = useCallback(
49
+ (e: KeyboardEvent<HTMLElement>) => {
50
+ handleListKeyDownFactory(ids, expandedIds)(e);
51
+
52
+ if (e.code === 'ArrowLeft') {
53
+ forceUpdateActiveItemId?.(focusId);
54
+ setOpen(false);
55
+ e.stopPropagation();
47
56
 
48
- const { parentOpenNestedIndex, parentIds, triggerRef, parentResetNestedIndex, parentResetActiveFocusIndex } =
49
- useParentListContext();
50
-
51
- const {
52
- open,
53
- setOpen,
54
- handleListKeyDown,
55
- activeFocusIndex,
56
- openNestedIndex,
57
- resetNestedIndex,
58
- resetActiveFocusIndex,
59
- } = useKeyboardNavigation({ ids, expandedIds, itemRefs, id });
60
-
61
- const { isIndeterminate, checked, handleOnSelect } = useGroupItemSelection({ items: itemsProp, id, disabled });
62
-
63
- const handleOpenChange = useCallback(
64
- (open: boolean) => {
65
- if (!open) {
66
- resetActiveFocusIndex();
67
- resetNestedIndex();
68
- setOpenCollapsedItems([]);
57
+ return;
69
58
  }
70
- onSublistOpenChanged?.(open, id);
71
- setOpen(open);
72
59
  },
73
- [id, onSublistOpenChanged, resetActiveFocusIndex, resetNestedIndex, setOpen],
60
+ [handleListKeyDownFactory, ids, expandedIds, forceUpdateActiveItemId, focusId],
74
61
  );
75
62
 
76
- const handleOutsideClick = useCallback(() => {
77
- parentResetNestedIndex?.();
78
- parentResetActiveFocusIndex?.();
63
+ const { indeterminate, checked, handleOnSelect } = useGroupItemSelection({
64
+ items: isNextListItem<FlattenNextListItem>(item) ? item.items : [],
65
+ id,
66
+ disabled,
67
+ allChildIds,
68
+ });
79
69
 
70
+ const handleOutsideClick = useCallback(() => {
71
+ forceUpdateActiveItemId?.('~drop-focus');
72
+ setOpen(false);
80
73
  return true;
81
- }, [parentResetActiveFocusIndex, parentResetNestedIndex]);
74
+ }, [forceUpdateActiveItemId]);
75
+
76
+ const isOpen = useMemo(
77
+ () => Boolean(!disabled && activeItemId && focusFlattenItems[focusId].allChildIds.includes(activeItemId)),
78
+ [activeItemId, disabled, focusFlattenItems, focusId],
79
+ );
80
+
81
+ useEffect(() => {
82
+ setOpen(open => open && isOpen);
83
+ }, [id, isOpen]);
84
+
85
+ const listRef = useRef<HTMLElement>(null);
82
86
 
83
87
  return (
84
88
  <Dropdown
85
89
  outsideClick={handleOutsideClick}
86
90
  fallbackPlacements={FALLBACK_PLACEMENTS}
87
91
  content={
88
- <ParentListContext.Provider
89
- value={{
90
- parentIds: ids,
91
- parentActiveFocusIndex: activeFocusIndex,
92
- parentExpandedIds: expandedIds,
93
- parentItemRefs: itemRefs,
94
- parentOpenNestedIndex: openNestedIndex,
95
- triggerRef,
96
- parentRef: listRef,
97
- parentResetNestedIndex: resetNestedIndex,
98
- parentResetActiveFocusIndex: resetActiveFocusIndex,
99
- toggleOpenCollapsedItems: id =>
100
- setOpenCollapsedItems(items =>
101
- items.includes(id) ? items.filter(item => item !== id) : items.concat([id]),
102
- ),
92
+ <ListPrivate
93
+ onKeyDown={handleListKeyDown}
94
+ items={items}
95
+ nested
96
+ scroll={scroll}
97
+ tabIndex={0}
98
+ ref={listRef}
99
+ onFocus={e => {
100
+ e.stopPropagation();
101
+ forceUpdateActiveItemId?.(ids[0]);
103
102
  }}
104
- >
105
- <ListPrivate
106
- onKeyDown={handleListKeyDown}
107
- items={items}
108
- nested
109
- search={search}
110
- scroll={scroll}
111
- scrollRef={scrollRef}
112
- limitedScrollHeight
113
- loading={loading}
114
- />
115
- </ParentListContext.Provider>
103
+ scrollRef={scrollRef}
104
+ limitedScrollHeight
105
+ loading={loading}
106
+ />
116
107
  }
117
108
  trigger='hover'
118
- open={(open || parentIds[parentOpenNestedIndex] === id) && !disabled}
119
- onOpenChange={handleOpenChange}
109
+ open={isOpen || open}
110
+ onOpenChange={value => {
111
+ setOpen(value);
112
+ onSublistOpenChanged?.(value, id);
113
+ }}
120
114
  placement={placement}
121
115
  widthStrategy='auto'
122
116
  >
@@ -127,7 +121,14 @@ export function NextListItem({
127
121
  expandIcon={<ChevronRightSVG />}
128
122
  id={id}
129
123
  isParentNode
130
- indeterminate={isIndeterminate && !checked}
124
+ indeterminate={indeterminate}
125
+ checked={checked}
126
+ onOpenNestedList={() => {
127
+ setOpen(true);
128
+ setTimeout(() => {
129
+ listRef.current?.focus();
130
+ }, 0);
131
+ }}
131
132
  onSelect={handleOnSelect}
132
133
  />
133
134
  </Dropdown>
@@ -0,0 +1,12 @@
1
+ import { DropdownProps } from '@snack-uikit/dropdown';
2
+
3
+ export const FALLBACK_PLACEMENTS: DropdownProps['fallbackPlacements'] = [
4
+ 'right',
5
+ 'right-start',
6
+ 'right-end',
7
+ 'left',
8
+ 'left-start',
9
+ 'left-end',
10
+ 'bottom',
11
+ 'top',
12
+ ];
@@ -4,17 +4,17 @@ import { FocusEvent, KeyboardEvent, RefObject } from 'react';
4
4
  import { SearchPrivate } from '@snack-uikit/search-private';
5
5
 
6
6
  import { SearchState } from '../../../types';
7
- import { useListContext, useParentListContext } from '../../Lists/contexts';
7
+ import { useNewListContext } from '../../Lists/contexts';
8
8
  import commonStyles from '../styles.module.scss';
9
9
  import styles from './styles.module.scss';
10
10
 
11
11
  export type SearchItemProps = {
12
12
  search?: SearchState;
13
+ itemRef?: RefObject<HTMLElement>;
13
14
  };
14
15
 
15
- export function SearchItem({ search }: SearchItemProps) {
16
- const { parentItemRefs, parentSetActiveFocusIndex } = useParentListContext();
17
- const { size } = useListContext();
16
+ export function SearchItem({ search, itemRef }: SearchItemProps) {
17
+ const { size = 's' } = useNewListContext();
18
18
 
19
19
  const handleKeyDown = (e: KeyboardEvent) => {
20
20
  if (['ArrowDown', 'ArrowUp'].includes(e.key)) {
@@ -23,13 +23,11 @@ export function SearchItem({ search }: SearchItemProps) {
23
23
 
24
24
  if (['ArrowRight'].includes(e.key)) {
25
25
  e.stopPropagation();
26
+ e.preventDefault();
26
27
  }
27
28
  };
28
29
 
29
30
  const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
30
- if (e.target === parentItemRefs[0].current) {
31
- parentSetActiveFocusIndex?.(0);
32
- }
33
31
  e.stopPropagation();
34
32
  };
35
33
 
@@ -45,7 +43,7 @@ export function SearchItem({ search }: SearchItemProps) {
45
43
  onKeyDown={handleKeyDown}
46
44
  onFocus={handleFocus}
47
45
  {...search}
48
- ref={parentItemRefs[0] as RefObject<HTMLInputElement>}
46
+ ref={itemRef as RefObject<HTMLInputElement>}
49
47
  />
50
48
  </div>
51
49
  );