@snack-uikit/list 0.19.0 → 0.21.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 (69) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +18 -2
  3. package/dist/cjs/components/Items/BaseItem/BaseItem.js +4 -2
  4. package/dist/cjs/components/Items/NextListItem/NextListItem.js +4 -3
  5. package/dist/cjs/components/Items/SearchItem/SearchItem.js +4 -2
  6. package/dist/cjs/components/Items/hooks.js +11 -9
  7. package/dist/cjs/components/Items/utils.js +5 -3
  8. package/dist/cjs/components/Lists/Droplist/DropList.js +12 -7
  9. package/dist/cjs/components/Lists/List/List.d.ts +7 -1
  10. package/dist/cjs/components/Lists/List/List.js +19 -11
  11. package/dist/cjs/components/Lists/ListPrivate/ListPrivate.d.ts +3 -2
  12. package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +17 -3
  13. package/dist/cjs/components/Lists/contexts/NewListProvider.d.ts +2 -4
  14. package/dist/cjs/components/Lists/contexts/NewListProvider.js +7 -3
  15. package/dist/cjs/components/Lists/hooks.d.ts +6 -1
  16. package/dist/cjs/components/Lists/hooks.js +32 -18
  17. package/dist/cjs/components/Lists/types.d.ts +12 -3
  18. package/dist/cjs/constants.d.ts +8 -0
  19. package/dist/cjs/constants.js +14 -0
  20. package/dist/cjs/index.d.ts +2 -0
  21. package/dist/cjs/index.js +3 -1
  22. package/dist/cjs/utils.d.ts +16 -0
  23. package/dist/cjs/utils.js +25 -0
  24. package/dist/esm/components/Items/BaseItem/BaseItem.js +3 -3
  25. package/dist/esm/components/Items/NextListItem/NextListItem.js +2 -1
  26. package/dist/esm/components/Items/SearchItem/SearchItem.js +3 -2
  27. package/dist/esm/components/Items/hooks.js +6 -4
  28. package/dist/esm/components/Items/utils.js +5 -3
  29. package/dist/esm/components/Lists/Droplist/DropList.js +20 -4
  30. package/dist/esm/components/Lists/List/List.d.ts +7 -1
  31. package/dist/esm/components/Lists/List/List.js +23 -6
  32. package/dist/esm/components/Lists/ListPrivate/ListPrivate.d.ts +3 -2
  33. package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +12 -4
  34. package/dist/esm/components/Lists/contexts/NewListProvider.d.ts +2 -4
  35. package/dist/esm/components/Lists/contexts/NewListProvider.js +4 -2
  36. package/dist/esm/components/Lists/hooks.d.ts +6 -1
  37. package/dist/esm/components/Lists/hooks.js +29 -19
  38. package/dist/esm/components/Lists/types.d.ts +12 -3
  39. package/dist/esm/constants.d.ts +8 -0
  40. package/dist/esm/constants.js +8 -0
  41. package/dist/esm/index.d.ts +2 -0
  42. package/dist/esm/index.js +2 -0
  43. package/dist/esm/utils.d.ts +16 -0
  44. package/dist/esm/utils.js +16 -0
  45. package/package.json +12 -12
  46. package/src/components/Items/BaseItem/BaseItem.tsx +4 -4
  47. package/src/components/Items/BaseItem/styles.module.scss +21 -21
  48. package/src/components/Items/NextListItem/NextListItem.tsx +2 -1
  49. package/src/components/Items/PinGroupItem/styles.module.scss +3 -4
  50. package/src/components/Items/SearchItem/SearchItem.tsx +3 -2
  51. package/src/components/Items/SearchItem/styles.module.scss +3 -3
  52. package/src/components/Items/hooks.tsx +6 -4
  53. package/src/components/Items/styles.module.scss +9 -9
  54. package/src/components/Items/utils.ts +5 -3
  55. package/src/components/Lists/Droplist/DropList.tsx +20 -3
  56. package/src/components/Lists/List/List.tsx +25 -5
  57. package/src/components/Lists/ListPrivate/ListPrivate.tsx +13 -3
  58. package/src/components/Lists/ListPrivate/styles.module.scss +11 -11
  59. package/src/components/Lists/contexts/NewListProvider.tsx +6 -2
  60. package/src/components/Lists/hooks.ts +35 -20
  61. package/src/components/Lists/styles.module.scss +8 -8
  62. package/src/components/Lists/types.ts +11 -3
  63. package/src/constants.ts +8 -0
  64. package/src/helperComponents/CollapseBlockPrivate/styles.module.scss +3 -3
  65. package/src/helperComponents/ItemContent/styles.module.scss +21 -22
  66. package/src/helperComponents/ListEmptyState/styles.module.scss +2 -2
  67. package/src/helperComponents/Separator/styles.module.scss +20 -22
  68. package/src/index.ts +3 -0
  69. package/src/utils.ts +20 -0
@@ -29,6 +29,10 @@ export type ListProps = WithSupportProps<{
29
29
  footer?: ReactNode;
30
30
  /** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
31
31
  footerActiveElementsRefs?: RefObject<HTMLElement>[];
32
+ /** Ссылка на управление навигацией листа с клавиатуры */
33
+ keyboardNavigationRef?: RefObject<{
34
+ focusItem(id: ItemId): void;
35
+ }>;
32
36
  /** Настройки поисковой строки */
33
37
  search?: SearchState;
34
38
  /** Tab Index */
@@ -37,9 +41,14 @@ export type ListProps = WithSupportProps<{
37
41
  collapse?: CollapseState;
38
42
  /** CSS-класс */
39
43
  className?: string;
40
- onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
41
44
  /** Флаг, отвещающий за состояние загрузки списка */
42
45
  loading?: boolean;
46
+ /** Обработчик события по нажатию клавиш */
47
+ onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
48
+ /** Флаг, отвещающий за включение самого родительского контейнера листа в цепочку фокусирующихся элементов */
49
+ hasListInFocusChain?: boolean;
50
+ /** Флаг, отвещающий за прокручивание до выбранного элемента */
51
+ scrollToSelectedItem?: boolean;
43
52
  } & SelectionState & PublicListContextType & ScrollProps & EmptyState>;
44
53
  export type DroplistProps = {
45
54
  /** Ссылка на элемент-триггер для дроплиста */
@@ -62,8 +71,8 @@ export type DroplistProps = {
62
71
  children: ReactNode | ((params: {
63
72
  onKeyDown?: (e: KeyboardEvent<HTMLElement>) => void;
64
73
  }) => ReactNode);
65
- } & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange' | 'triggerClassName'> & Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
66
- export type ListPrivateProps = Omit<ListProps, 'pinTop' | 'pinBottom' | 'items'> & {
74
+ } & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange' | 'triggerClassName'> & Omit<ListProps, 'tabIndex' | 'onKeyDown' | 'hasListInFocusChain' | 'keyboardNavigationRef'>;
75
+ export type ListPrivateProps = Omit<ListProps, 'pinTop' | 'pinBottom' | 'items' | 'hasListInFocusChain'> & {
67
76
  nested?: boolean;
68
77
  active?: boolean;
69
78
  tabIndex?: number;
@@ -0,0 +1,8 @@
1
+ export declare const ITEM_PREFIXES: {
2
+ default: string;
3
+ pinTop: string;
4
+ pinBottom: string;
5
+ footer: string;
6
+ search: string;
7
+ dropFocus: string;
8
+ };
@@ -0,0 +1,8 @@
1
+ export const ITEM_PREFIXES = {
2
+ default: '~main',
3
+ pinTop: '~pinTop',
4
+ pinBottom: '~pinBottom',
5
+ footer: '~footer',
6
+ search: '~search',
7
+ dropFocus: '~dropFocus',
8
+ };
@@ -2,3 +2,5 @@ export * from './components';
2
2
  export type { SearchState } from './types';
3
3
  export { kindFlattenItems } from './components/Items';
4
4
  export * from './helperComponents/ItemContent';
5
+ export * from './utils';
6
+ export * from './constants';
package/dist/esm/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './components';
2
2
  export { kindFlattenItems } from './components/Items';
3
3
  export * from './helperComponents/ItemContent';
4
+ export * from './utils';
5
+ export * from './constants';
@@ -0,0 +1,16 @@
1
+ import { ItemId } from './components';
2
+ /**
3
+ * Возвращает id для элемента футера
4
+ * @function helper
5
+ */
6
+ export declare const getFooterItemId: (id: ItemId) => string;
7
+ /**
8
+ * Возвращает id для элемента, подставляя перфикс
9
+ * @function helper
10
+ */
11
+ export declare const getItemAutoId: (prefix: ItemId, id: ItemId) => string;
12
+ /**
13
+ * Возвращает id для дефолтного элемента
14
+ * @function helper
15
+ */
16
+ export declare const getDefaultItemId: (id: ItemId) => string;
@@ -0,0 +1,16 @@
1
+ import { ITEM_PREFIXES } from './constants';
2
+ /**
3
+ * Возвращает id для элемента футера
4
+ * @function helper
5
+ */
6
+ export const getFooterItemId = (id) => `${ITEM_PREFIXES.footer}__${id}`;
7
+ /**
8
+ * Возвращает id для элемента, подставляя перфикс
9
+ * @function helper
10
+ */
11
+ export const getItemAutoId = (prefix, id) => [prefix, id].join('-');
12
+ /**
13
+ * Возвращает id для дефолтного элемента
14
+ * @function helper
15
+ */
16
+ export const getDefaultItemId = (id) => getItemAutoId(ITEM_PREFIXES.default, id);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "List",
7
- "version": "0.19.0",
7
+ "version": "0.21.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -36,16 +36,16 @@
36
36
  "license": "Apache-2.0",
37
37
  "scripts": {},
38
38
  "dependencies": {
39
- "@snack-uikit/button": "0.18.0",
40
- "@snack-uikit/divider": "3.1.0",
41
- "@snack-uikit/dropdown": "0.3.0",
42
- "@snack-uikit/icons": "0.23.0",
43
- "@snack-uikit/info-block": "0.5.0",
44
- "@snack-uikit/loaders": "0.6.0",
45
- "@snack-uikit/scroll": "0.7.0",
46
- "@snack-uikit/search-private": "0.3.0",
47
- "@snack-uikit/toggles": "0.11.0",
48
- "@snack-uikit/truncate-string": "0.5.0",
39
+ "@snack-uikit/button": "0.19.1",
40
+ "@snack-uikit/divider": "3.2.0",
41
+ "@snack-uikit/dropdown": "0.4.0",
42
+ "@snack-uikit/icons": "0.24.0",
43
+ "@snack-uikit/info-block": "0.6.1",
44
+ "@snack-uikit/loaders": "0.7.1",
45
+ "@snack-uikit/scroll": "0.9.0",
46
+ "@snack-uikit/search-private": "0.4.1",
47
+ "@snack-uikit/toggles": "0.12.0",
48
+ "@snack-uikit/truncate-string": "0.6.0",
49
49
  "@snack-uikit/utils": "3.5.0",
50
50
  "classnames": "2.5.1",
51
51
  "merge-refs": "1.2.2"
@@ -53,5 +53,5 @@
53
53
  "peerDependencies": {
54
54
  "@snack-uikit/locale": "*"
55
55
  },
56
- "gitHead": "62cc21d8606a61a78f80e3a7455b20d6f38474fa"
56
+ "gitHead": "b7163c6f939105eb34cabec64c9e983ac7958c26"
57
57
  }
@@ -52,12 +52,12 @@ export function BaseItem({
52
52
  }: AllBaseItemProps) {
53
53
  const interactive = !inactive;
54
54
 
55
- const { size = 's', marker, contentRender } = useNewListContext();
55
+ const { size = 's', marker, contentRender, firstItemId, focusFlattenItems } = useNewListContext();
56
56
  const { level = 0 } = useCollapseLevelContext();
57
57
  const { closeDroplist, closeDroplistOnItemClick } = useOpenListContext();
58
58
  const { value, onChange, mode, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
59
59
 
60
- const isChecked = isSelectionSingle ? checkedProp ?? value === id : checkedProp ?? value?.includes(id ?? '');
60
+ const isChecked = isSelectionSingle ? (checkedProp ?? value === id) : (checkedProp ?? value?.includes(id ?? ''));
61
61
 
62
62
  const handleChange = () => {
63
63
  onChange?.(id);
@@ -150,7 +150,7 @@ export function BaseItem({
150
150
  data-size={size}
151
151
  onClick={handleItemClick}
152
152
  onMouseDown={handleItemMouseDown}
153
- tabIndex={-1}
153
+ tabIndex={id === focusFlattenItems[firstItemId].originalId ? 0 : -1}
154
154
  data-non-pointer={inactive && !onClick}
155
155
  data-variant={mode || undefined}
156
156
  data-open={open || undefined}
@@ -179,7 +179,7 @@ export function BaseItem({
179
179
 
180
180
  {beforeContent && <div className={styles.beforeContent}>{beforeContent}</div>}
181
181
  {content && isContentItem(content) ? (
182
- contentRender?.({ id, content, disabled }) ?? <ItemContent disabled={disabled} {...content} />
182
+ (contentRender?.({ id, content, disabled }) ?? <ItemContent disabled={disabled} {...content} />)
183
183
  ) : (
184
184
  <div className={styles.content}> {content} </div>
185
185
  )}
@@ -1,5 +1,5 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
2
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
3
 
4
4
  $sizes: 's', 'm', 'l';
5
5
 
@@ -10,8 +10,8 @@ $sizes: 's', 'm', 'l';
10
10
  align-items: center;
11
11
  justify-content: center;
12
12
 
13
- width: $dimension-3m;
14
- height: $dimension-3m;
13
+ width: styles-tokens-dropList.$dimension-3m;
14
+ height: styles-tokens-dropList.$dimension-3m;
15
15
  }
16
16
 
17
17
  .beforeContent {
@@ -21,10 +21,10 @@ $sizes: 's', 'm', 'l';
21
21
  align-items: center;
22
22
  justify-content: center;
23
23
 
24
- width: $icon-s;
25
- height: $icon-s;
24
+ width: styles-tokens-element.$icon-s;
25
+ height: styles-tokens-element.$icon-s;
26
26
 
27
- color: $sys-neutral-text-light;
27
+ color: styles-tokens-dropList.$sys-neutral-text-light;
28
28
 
29
29
  svg {
30
30
  max-width: 100%;
@@ -35,12 +35,12 @@ $sizes: 's', 'm', 'l';
35
35
  .expandableIcon {
36
36
  display: flex;
37
37
  align-items: center;
38
- color: $sys-neutral-text-light;
38
+ color: styles-tokens-dropList.$sys-neutral-text-light;
39
39
  fill: currentColor;
40
40
  }
41
41
 
42
42
  .markerContainer {
43
- @include composite-var($drop-list, 'item', 'marker-container');
43
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', 'marker-container');
44
44
 
45
45
  pointer-events: none;
46
46
 
@@ -52,7 +52,7 @@ $sizes: 's', 'm', 'l';
52
52
  height: 100%;
53
53
 
54
54
  &:before {
55
- @include composite-var($drop-list, 'item', 'marker');
55
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', 'marker');
56
56
 
57
57
  content: '';
58
58
  display: block;
@@ -65,11 +65,11 @@ $sizes: 's', 'm', 'l';
65
65
  @each $size in $sizes {
66
66
  &[data-size='#{$size}'] {
67
67
  padding-left: calc(
68
- var(--level, 0) * $dimension-050m + simple-var($drop-list, 'item', $size, 'container', 'padding-left')
68
+ var(--level, 0) * styles-tokens-dropList.$dimension-050m + styles-tokens-dropList.simple-var(styles-tokens-dropList.$drop-list, 'item', $size, 'container', 'padding-left')
69
69
  );
70
70
 
71
71
  .headline {
72
- @include composite-var($drop-list, 'item', $size, 'headline');
72
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', $size, 'headline');
73
73
  }
74
74
  }
75
75
  }
@@ -82,11 +82,11 @@ $sizes: 's', 'm', 'l';
82
82
  .innerWrapper {
83
83
  &[data-disabled] {
84
84
  .expandableIcon {
85
- color: $sys-neutral-text-disabled;
85
+ color: styles-tokens-dropList.$sys-neutral-text-disabled;
86
86
  }
87
87
 
88
88
  .beforeContent {
89
- opacity: $opacity-a064;
89
+ opacity: styles-tokens-dropList.$opacity-a064;
90
90
  }
91
91
 
92
92
  .droplistItem {
@@ -98,31 +98,31 @@ $sizes: 's', 'm', 'l';
98
98
  &[data-checked] {
99
99
  &[data-variant='single'] {
100
100
  &::before {
101
- opacity: $opacity-a008;
102
- background-color: $sys-primary-accent-default;
101
+ opacity: styles-tokens-dropList.$opacity-a008;
102
+ background-color: styles-tokens-dropList.$sys-primary-accent-default;
103
103
  }
104
104
 
105
105
  &:hover {
106
106
  &::before {
107
- opacity: $opacity-a016;
108
- background-color: $sys-primary-accent-default;
107
+ opacity: styles-tokens-dropList.$opacity-a016;
108
+ background-color: styles-tokens-dropList.$sys-primary-accent-default;
109
109
  }
110
110
  }
111
111
  }
112
112
 
113
113
  .markerContainer:before {
114
- background-color: $sys-primary-accent-default;
114
+ background-color: styles-tokens-dropList.$sys-primary-accent-default;
115
115
  }
116
116
 
117
117
  .droplistItem:focus-visible {
118
118
  &[data-variant='single'] {
119
- outline-color: $sys-primary-accent-default;
119
+ outline-color: styles-tokens-dropList.$sys-primary-accent-default;
120
120
  }
121
121
  }
122
122
 
123
123
  &[data-disabled] {
124
124
  .markerContainer:before {
125
- background-color: $sys-neutral-text-disabled;
125
+ background-color: styles-tokens-dropList.$sys-neutral-text-disabled;
126
126
  }
127
127
  }
128
128
  }
@@ -3,6 +3,7 @@ import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from
3
3
  import { Dropdown } from '@snack-uikit/dropdown';
4
4
  import { ChevronRightSVG } from '@snack-uikit/icons';
5
5
 
6
+ import { ITEM_PREFIXES } from '../../../constants';
6
7
  import { useCollapseContext, useFocusListContext, useNewListContext, useSelectionContext } from '../../Lists/contexts';
7
8
  import { ListPrivate } from '../../Lists/ListPrivate';
8
9
  import { BaseItem } from '../BaseItem';
@@ -71,7 +72,7 @@ export function NextListItem({
71
72
  });
72
73
 
73
74
  const handleOutsideClick = useCallback(() => {
74
- forceUpdateActiveItemId?.('~drop-focus');
75
+ forceUpdateActiveItemId?.(ITEM_PREFIXES.dropFocus);
75
76
  setOpen(false);
76
77
  return true;
77
78
  }, [forceUpdateActiveItemId]);
@@ -1,12 +1,11 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
2
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
3
2
 
4
3
  $sizes: 's', 'm', 'l';
5
4
 
6
5
  .pinTopItem {
7
6
  @each $size in $sizes {
8
7
  &[data-size='#{$size}'] {
9
- @include composite-var($drop-list, 'item', $size, 'content-pin-top');
8
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', $size, 'content-pin-top');
10
9
  }
11
10
  }
12
11
 
@@ -23,7 +22,7 @@ $sizes: 's', 'm', 'l';
23
22
  .pinBottomItem {
24
23
  @each $size in $sizes {
25
24
  &[data-size='#{$size}'] {
26
- @include composite-var($drop-list, 'item', $size, 'content-pin-bottom');
25
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', $size, 'content-pin-bottom');
27
26
  }
28
27
  }
29
28
 
@@ -3,6 +3,7 @@ import { FocusEvent, KeyboardEvent, RefObject } from 'react';
3
3
 
4
4
  import { SearchPrivate } from '@snack-uikit/search-private';
5
5
 
6
+ import { ITEM_PREFIXES } from '../../../constants';
6
7
  import { SearchState } from '../../../types';
7
8
  import { useNewListContext } from '../../Lists/contexts';
8
9
  import commonStyles from '../styles.module.scss';
@@ -14,7 +15,7 @@ export type SearchItemProps = {
14
15
  };
15
16
 
16
17
  export function SearchItem({ search, itemRef }: SearchItemProps) {
17
- const { size = 's' } = useNewListContext();
18
+ const { size = 's', firstItemId } = useNewListContext();
18
19
 
19
20
  const handleKeyDown = (e: KeyboardEvent) => {
20
21
  if (['ArrowDown', 'ArrowUp'].includes(e.key)) {
@@ -34,7 +35,7 @@ export function SearchItem({ search, itemRef }: SearchItemProps) {
34
35
  <div className={cn(commonStyles.listItem, styles.searchItem)} data-size={size} data-test-id='list__search-item'>
35
36
  <SearchPrivate
36
37
  size={size}
37
- tabIndex={-1}
38
+ tabIndex={ITEM_PREFIXES.search === firstItemId ? 0 : -1}
38
39
  onKeyDown={handleKeyDown}
39
40
  onFocus={handleFocus}
40
41
  {...search}
@@ -1,13 +1,13 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
2
2
 
3
3
  .searchItem {
4
4
  width: 100%;
5
5
 
6
6
  &:focus-within,
7
7
  &:focus-visible {
8
- @include outline-inside-var($container-focused-s);
8
+ @include styles-tokens-element.outline-inside-var(styles-tokens-element.$container-focused-s);
9
9
 
10
10
  max-width: 100%;
11
- outline-color: $sys-primary-decor-activated;
11
+ outline-color: styles-tokens-element.$sys-primary-decor-activated;
12
12
  }
13
13
  }
@@ -1,5 +1,7 @@
1
1
  import { createRef, RefObject, useMemo } from 'react';
2
2
 
3
+ import { ITEM_PREFIXES } from '../../constants';
4
+ import { getFooterItemId } from '../../utils';
3
5
  import { useNewListContext, useSelectionContext } from '../Lists/contexts';
4
6
  import { AccordionItem } from './AccordionItem';
5
7
  import { BaseItem } from './BaseItem';
@@ -64,16 +66,16 @@ export function useCreateBaseItems({ footerActiveElementsRefs }: UseCreateBaseIt
64
66
  () => ({
65
67
  searchItem: {
66
68
  itemRef: createRef<HTMLInputElement>(),
67
- id: '~search',
68
- parentId: '~main',
69
+ id: ITEM_PREFIXES.search,
70
+ parentId: ITEM_PREFIXES.default,
69
71
  items: [],
70
72
  allChildIds: [],
71
73
  },
72
74
  footerItems:
73
75
  footerActiveElementsRefs?.map((itemRef, idx) => ({
74
- id: `~footer__${idx}`,
76
+ id: getFooterItemId(idx),
75
77
  itemRef,
76
- parentId: '~main',
78
+ parentId: ITEM_PREFIXES.default,
77
79
  items: [],
78
80
  allChildIds: [],
79
81
  })) ?? [],
@@ -1,5 +1,5 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
2
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-dropList';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
3
3
 
4
4
  $sizes: 's', 'm', 'l';
5
5
 
@@ -45,16 +45,16 @@ $sizes: 's', 'm', 'l';
45
45
  &[data-focused],
46
46
  &:focus-visible {
47
47
  &::before {
48
- opacity: $opacity-a008;
49
- background-color: $sys-neutral-accent-default;
48
+ opacity: styles-tokens-dropList.$opacity-a008;
49
+ background-color: styles-tokens-dropList.$sys-neutral-accent-default;
50
50
  }
51
51
  }
52
52
 
53
53
  @media (hover: hover) {
54
54
  &:hover {
55
55
  &::before {
56
- opacity: $opacity-a008;
57
- background-color: $sys-neutral-accent-default;
56
+ opacity: styles-tokens-dropList.$opacity-a008;
57
+ background-color: styles-tokens-dropList.$sys-neutral-accent-default;
58
58
  }
59
59
  }
60
60
  }
@@ -76,7 +76,7 @@ $sizes: 's', 'm', 'l';
76
76
  margin: 0;
77
77
  padding: 0;
78
78
 
79
- color: $sys-neutral-text-main;
79
+ color: styles-tokens-dropList.$sys-neutral-text-main;
80
80
  text-align: start;
81
81
 
82
82
  appearance: unset;
@@ -86,14 +86,14 @@ $sizes: 's', 'm', 'l';
86
86
 
87
87
  @each $size in $sizes {
88
88
  &[data-size='#{$size}'] {
89
- @include composite-var($drop-list, 'item', $size, 'container');
89
+ @include styles-tokens-dropList.composite-var(styles-tokens-dropList.$drop-list, 'item', $size, 'container');
90
90
  }
91
91
  }
92
92
 
93
93
  &:not([data-inactive], [data-disabled]) {
94
94
  &[data-focused],
95
95
  &:focus-visible {
96
- @include outline-inside-var($container-focused-s);
96
+ @include styles-tokens-dropList.outline-inside-var(styles-tokens-element.$container-focused-s);
97
97
  }
98
98
  }
99
99
  }
@@ -1,6 +1,8 @@
1
1
  import { createRef } from 'react';
2
2
 
3
+ import { ITEM_PREFIXES } from '../../constants';
3
4
  import { ItemContentProps } from '../../helperComponents';
5
+ import { getItemAutoId } from '../../utils';
4
6
  import {
5
7
  AccordionItem,
6
8
  AnyType,
@@ -55,13 +57,13 @@ export function kindFlattenItems({ items, prefix, parentId }: KindFlattenItemsPr
55
57
  const flattenItems: Record<string, FlattenItem> = {};
56
58
  const focusFlattenItems: Record<string, FocusFlattenItem> = {};
57
59
 
58
- function flatten({ item, idx, prefix, parentId = '~main' }: FlattenProps): {
60
+ function flatten({ item, idx, prefix, parentId = ITEM_PREFIXES.default }: FlattenProps): {
59
61
  id: ItemId;
60
62
  children: ItemId[];
61
63
  focusChildren: ItemId[];
62
64
  autoId: ItemId;
63
65
  } {
64
- const autoId = prefix !== undefined ? [prefix, idx].join('-') : String(idx);
66
+ const autoId = prefix !== undefined ? getItemAutoId(prefix, idx) : String(idx);
65
67
  const itemId = (!isGroupItem(item) ? item.id : undefined) ?? autoId;
66
68
 
67
69
  if (isBaseItem(item)) {
@@ -92,7 +94,7 @@ export function kindFlattenItems({ items, prefix, parentId }: KindFlattenItemsPr
92
94
  const autoChildIds: ItemId[] = [];
93
95
 
94
96
  const { items, ...rest } = item;
95
- const childActiveParent = isGroupItem(item) ? parentId ?? '~main' : autoId;
97
+ const childActiveParent = isGroupItem(item) ? parentId ?? ITEM_PREFIXES.default : autoId;
96
98
 
97
99
  const filteredItems = items.filter(item => !item.hidden);
98
100
 
@@ -5,6 +5,7 @@ import { cloneElement, isValidElement, KeyboardEvent, ReactNode, useCallback, us
5
5
  import { Dropdown, DropdownProps } from '@snack-uikit/dropdown';
6
6
  import { useValueControl } from '@snack-uikit/utils';
7
7
 
8
+ import { ITEM_PREFIXES } from '../../../constants';
8
9
  import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
10
  import {
10
11
  CollapseContext,
@@ -67,9 +68,21 @@ export function Droplist({
67
68
  * Объект с пропсами всех вложенных айтемов; ключ id
68
69
  */
69
70
  const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
70
- const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
71
- const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
72
- const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
71
+ const pinTop = kindFlattenItems({
72
+ items: pinTopProp,
73
+ prefix: ITEM_PREFIXES.pinTop,
74
+ parentId: ITEM_PREFIXES.default,
75
+ });
76
+ const items = kindFlattenItems({
77
+ items: itemsProp,
78
+ prefix: ITEM_PREFIXES.default,
79
+ parentId: ITEM_PREFIXES.default,
80
+ });
81
+ const pinBottom = kindFlattenItems({
82
+ items: pinBottomProp,
83
+ prefix: ITEM_PREFIXES.pinBottom,
84
+ parentId: ITEM_PREFIXES.default,
85
+ });
73
86
 
74
87
  const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
75
88
  const focusFlattenItems = {
@@ -120,11 +133,14 @@ export function Droplist({
120
133
 
121
134
  const triggerElemRef = useRef<HTMLElement>(null);
122
135
  const listRef = useRef<HTMLElement>(null);
136
+ const firstItemId = ids[0];
123
137
 
124
138
  const { handleListKeyDownFactory, resetActiveItemId, activeItemId, forceUpdateActiveItemId } =
125
139
  useNewKeyboardNavigation({
126
140
  mainRef: triggerElemRefProp ?? triggerElemRef,
127
141
  focusFlattenItems,
142
+ hasListInFocusChain: true,
143
+ firstItemId,
128
144
  });
129
145
 
130
146
  const handleListKeyDown = useCallback(
@@ -190,6 +206,7 @@ export function Droplist({
190
206
  contentRender={contentRender}
191
207
  size={size}
192
208
  marker={marker}
209
+ firstItemId={firstItemId}
193
210
  >
194
211
  <SelectionProvider {...selection}>
195
212
  <CollapseContext.Provider
@@ -4,6 +4,7 @@ import { ForwardedRef, forwardRef, KeyboardEvent, useCallback, useMemo, useRef }
4
4
 
5
5
  import { isBrowser, useValueControl } from '@snack-uikit/utils';
6
6
 
7
+ import { ITEM_PREFIXES } from '../../../constants';
7
8
  import { HiddenTabButton } from '../../../helperComponents';
8
9
  import { extractActiveItems, ItemId, kindFlattenItems, useCreateBaseItems } from '../../Items';
9
10
  import { CollapseContext, FocusListContext, NewListContextProvider, SelectionProvider } from '../contexts';
@@ -28,6 +29,8 @@ export const List = forwardRef(
28
29
  contentRender,
29
30
  size = 's',
30
31
  marker = true,
32
+ keyboardNavigationRef,
33
+ hasListInFocusChain = true,
31
34
  ...props
32
35
  }: ListProps,
33
36
  ref: ForwardedRef<HTMLElement>,
@@ -49,9 +52,21 @@ export const List = forwardRef(
49
52
  * Объект с пропсами всех вложенных айтемов; ключ id
50
53
  */
51
54
  const { flattenItems, focusFlattenItems, ...memorizedItems } = useMemo(() => {
52
- const pinTop = kindFlattenItems({ items: pinTopProp, prefix: '~pinTop', parentId: '~main' });
53
- const items = kindFlattenItems({ items: itemsProp, prefix: '~main', parentId: '~main' });
54
- const pinBottom = kindFlattenItems({ items: pinBottomProp, prefix: '~pinBottom', parentId: '~main' });
55
+ const pinTop = kindFlattenItems({
56
+ items: pinTopProp,
57
+ prefix: ITEM_PREFIXES.pinTop,
58
+ parentId: ITEM_PREFIXES.default,
59
+ });
60
+ const items = kindFlattenItems({
61
+ items: itemsProp,
62
+ prefix: ITEM_PREFIXES.default,
63
+ parentId: ITEM_PREFIXES.default,
64
+ });
65
+ const pinBottom = kindFlattenItems({
66
+ items: pinBottomProp,
67
+ prefix: ITEM_PREFIXES.pinBottom,
68
+ parentId: ITEM_PREFIXES.default,
69
+ });
55
70
 
56
71
  const flattenItems = { ...pinTop.flattenItems, ...pinBottom.flattenItems, ...items.flattenItems };
57
72
  const focusFlattenItems = {
@@ -102,12 +117,16 @@ export const List = forwardRef(
102
117
 
103
118
  const listRef = useRef<HTMLElement>(null);
104
119
  const btnRef = useRef<HTMLButtonElement>(null);
120
+ const firstItemId = ids[0];
105
121
 
106
122
  const { handleListKeyDownFactory, activeItemId, resetActiveItemId, forceUpdateActiveItemId } =
107
123
  useNewKeyboardNavigation({
108
124
  mainRef: listRef,
109
125
  btnRef,
110
126
  focusFlattenItems,
127
+ keyboardNavigationRef,
128
+ hasListInFocusChain,
129
+ firstItemId,
111
130
  });
112
131
 
113
132
  const handleListKeyDown = useCallback(
@@ -133,6 +152,7 @@ export const List = forwardRef(
133
152
  contentRender={contentRender}
134
153
  size={size}
135
154
  marker={marker}
155
+ firstItemId={firstItemId}
136
156
  >
137
157
  <SelectionProvider {...selection}>
138
158
  <CollapseContext.Provider
@@ -158,12 +178,12 @@ export const List = forwardRef(
158
178
  ref={mergeRefs(ref, listRef)}
159
179
  onFocus={handleOnFocus}
160
180
  onKeyDown={mergedHandlerKeyDown}
161
- tabIndex={tabIndex}
181
+ tabIndex={hasListInFocusChain ? tabIndex : undefined}
162
182
  search={search}
163
183
  nested={false}
164
184
  />
165
185
 
166
- <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
186
+ {hasListInFocusChain && <HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />}
167
187
  </div>
168
188
  </FocusListContext.Provider>
169
189
  </CollapseContext.Provider>