@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.
- package/CHANGELOG.md +23 -0
- package/README.md +18 -2
- package/dist/cjs/components/Items/BaseItem/BaseItem.js +4 -2
- package/dist/cjs/components/Items/NextListItem/NextListItem.js +4 -3
- package/dist/cjs/components/Items/SearchItem/SearchItem.js +4 -2
- package/dist/cjs/components/Items/hooks.js +11 -9
- package/dist/cjs/components/Items/utils.js +5 -3
- package/dist/cjs/components/Lists/Droplist/DropList.js +12 -7
- package/dist/cjs/components/Lists/List/List.d.ts +7 -1
- package/dist/cjs/components/Lists/List/List.js +19 -11
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.d.ts +3 -2
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +17 -3
- package/dist/cjs/components/Lists/contexts/NewListProvider.d.ts +2 -4
- package/dist/cjs/components/Lists/contexts/NewListProvider.js +7 -3
- package/dist/cjs/components/Lists/hooks.d.ts +6 -1
- package/dist/cjs/components/Lists/hooks.js +32 -18
- package/dist/cjs/components/Lists/types.d.ts +12 -3
- package/dist/cjs/constants.d.ts +8 -0
- package/dist/cjs/constants.js +14 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/utils.d.ts +16 -0
- package/dist/cjs/utils.js +25 -0
- package/dist/esm/components/Items/BaseItem/BaseItem.js +3 -3
- package/dist/esm/components/Items/NextListItem/NextListItem.js +2 -1
- package/dist/esm/components/Items/SearchItem/SearchItem.js +3 -2
- package/dist/esm/components/Items/hooks.js +6 -4
- package/dist/esm/components/Items/utils.js +5 -3
- package/dist/esm/components/Lists/Droplist/DropList.js +20 -4
- package/dist/esm/components/Lists/List/List.d.ts +7 -1
- package/dist/esm/components/Lists/List/List.js +23 -6
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.d.ts +3 -2
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +12 -4
- package/dist/esm/components/Lists/contexts/NewListProvider.d.ts +2 -4
- package/dist/esm/components/Lists/contexts/NewListProvider.js +4 -2
- package/dist/esm/components/Lists/hooks.d.ts +6 -1
- package/dist/esm/components/Lists/hooks.js +29 -19
- package/dist/esm/components/Lists/types.d.ts +12 -3
- package/dist/esm/constants.d.ts +8 -0
- package/dist/esm/constants.js +8 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/utils.d.ts +16 -0
- package/dist/esm/utils.js +16 -0
- package/package.json +12 -12
- package/src/components/Items/BaseItem/BaseItem.tsx +4 -4
- package/src/components/Items/BaseItem/styles.module.scss +21 -21
- package/src/components/Items/NextListItem/NextListItem.tsx +2 -1
- package/src/components/Items/PinGroupItem/styles.module.scss +3 -4
- package/src/components/Items/SearchItem/SearchItem.tsx +3 -2
- package/src/components/Items/SearchItem/styles.module.scss +3 -3
- package/src/components/Items/hooks.tsx +6 -4
- package/src/components/Items/styles.module.scss +9 -9
- package/src/components/Items/utils.ts +5 -3
- package/src/components/Lists/Droplist/DropList.tsx +20 -3
- package/src/components/Lists/List/List.tsx +25 -5
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +13 -3
- package/src/components/Lists/ListPrivate/styles.module.scss +11 -11
- package/src/components/Lists/contexts/NewListProvider.tsx +6 -2
- package/src/components/Lists/hooks.ts +35 -20
- package/src/components/Lists/styles.module.scss +8 -8
- package/src/components/Lists/types.ts +11 -3
- package/src/constants.ts +8 -0
- package/src/helperComponents/CollapseBlockPrivate/styles.module.scss +3 -3
- package/src/helperComponents/ItemContent/styles.module.scss +21 -22
- package/src/helperComponents/ListEmptyState/styles.module.scss +2 -2
- package/src/helperComponents/Separator/styles.module.scss +20 -22
- package/src/index.ts +3 -0
- 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;
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
40
|
-
"@snack-uikit/divider": "3.
|
|
41
|
-
"@snack-uikit/dropdown": "0.
|
|
42
|
-
"@snack-uikit/icons": "0.
|
|
43
|
-
"@snack-uikit/info-block": "0.
|
|
44
|
-
"@snack-uikit/loaders": "0.
|
|
45
|
-
"@snack-uikit/scroll": "0.
|
|
46
|
-
"@snack-uikit/search-private": "0.
|
|
47
|
-
"@snack-uikit/toggles": "0.
|
|
48
|
-
"@snack-uikit/truncate-string": "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": "
|
|
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
|
-
@
|
|
2
|
-
@
|
|
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:
|
|
14
|
-
height:
|
|
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:
|
|
25
|
-
height:
|
|
24
|
+
width: styles-tokens-element.$icon-s;
|
|
25
|
+
height: styles-tokens-element.$icon-s;
|
|
26
26
|
|
|
27
|
-
color:
|
|
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:
|
|
38
|
+
color: styles-tokens-dropList.$sys-neutral-text-light;
|
|
39
39
|
fill: currentColor;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.markerContainer {
|
|
43
|
-
@include composite-var(
|
|
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(
|
|
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) *
|
|
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(
|
|
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:
|
|
85
|
+
color: styles-tokens-dropList.$sys-neutral-text-disabled;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.beforeContent {
|
|
89
|
-
opacity:
|
|
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:
|
|
102
|
-
background-color:
|
|
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:
|
|
108
|
-
background-color:
|
|
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:
|
|
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:
|
|
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:
|
|
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?.(
|
|
75
|
+
forceUpdateActiveItemId?.(ITEM_PREFIXES.dropFocus);
|
|
75
76
|
setOpen(false);
|
|
76
77
|
return true;
|
|
77
78
|
}, [forceUpdateActiveItemId]);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
@
|
|
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(
|
|
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(
|
|
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
|
-
@
|
|
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(
|
|
8
|
+
@include styles-tokens-element.outline-inside-var(styles-tokens-element.$container-focused-s);
|
|
9
9
|
|
|
10
10
|
max-width: 100%;
|
|
11
|
-
outline-color:
|
|
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:
|
|
68
|
-
parentId:
|
|
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:
|
|
76
|
+
id: getFooterItemId(idx),
|
|
75
77
|
itemRef,
|
|
76
|
-
parentId:
|
|
78
|
+
parentId: ITEM_PREFIXES.default,
|
|
77
79
|
items: [],
|
|
78
80
|
allChildIds: [],
|
|
79
81
|
})) ?? [],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
@
|
|
2
|
-
@
|
|
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:
|
|
49
|
-
background-color:
|
|
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:
|
|
57
|
-
background-color:
|
|
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:
|
|
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(
|
|
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(
|
|
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 =
|
|
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 ?
|
|
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 ??
|
|
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({
|
|
71
|
-
|
|
72
|
-
|
|
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({
|
|
53
|
-
|
|
54
|
-
|
|
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>
|