@snack-uikit/list 0.1.3-preview-3f90b7f1.0 → 0.2.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 +26 -0
- package/README.md +12 -12
- package/dist/components/Items/BaseItem/BaseItem.d.ts +3 -3
- package/dist/components/Items/BaseItem/BaseItem.js +9 -6
- package/dist/components/Items/BaseItem/styles.module.css +3 -0
- package/dist/components/Items/NextListItem/NextListItem.js +1 -1
- package/dist/components/Items/styles.module.css +10 -10
- package/dist/components/Items/types.d.ts +32 -7
- package/dist/components/Lists/Droplist/DropList.d.ts +1 -1
- package/dist/components/Lists/Droplist/DropList.js +11 -5
- package/dist/components/Lists/List/List.d.ts +10 -2
- package/dist/components/Lists/List/List.js +25 -11
- package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +10 -2
- package/dist/components/Lists/ListPrivate/ListPrivate.js +8 -8
- package/dist/components/Lists/ListPrivate/styles.module.css +4 -0
- package/dist/components/Lists/contexts/SelectionProvider.d.ts +19 -12
- package/dist/components/Lists/contexts/SelectionProvider.js +37 -16
- package/dist/components/Lists/styles.module.css +4 -0
- package/dist/components/Lists/types.d.ts +22 -6
- package/dist/helperComponents/HiddenTabButton/HiddenTabButton.d.ts +1 -0
- package/dist/helperComponents/HiddenTabButton/HiddenTabButton.js +2 -2
- package/dist/utils.js +6 -2
- package/package.json +8 -7
- package/src/components/Items/BaseItem/BaseItem.tsx +20 -12
- package/src/components/Items/BaseItem/styles.module.scss +4 -0
- package/src/components/Items/NextListItem/NextListItem.tsx +1 -0
- package/src/components/Items/styles.module.scss +17 -15
- package/src/components/Items/types.ts +32 -10
- package/src/components/Lists/Droplist/DropList.tsx +14 -5
- package/src/components/Lists/List/List.tsx +50 -18
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +11 -10
- package/src/components/Lists/ListPrivate/styles.module.scss +4 -0
- package/src/components/Lists/contexts/ListProvider.tsx +0 -2
- package/src/components/Lists/contexts/SelectionProvider.tsx +43 -29
- package/src/components/Lists/styles.module.scss +5 -0
- package/src/components/Lists/types.ts +22 -9
- package/src/helperComponents/HiddenTabButton/HiddenTabButton.tsx +12 -2
- package/src/utils.ts +11 -2
|
@@ -18,7 +18,6 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
18
18
|
items,
|
|
19
19
|
pinTop,
|
|
20
20
|
pinBottom,
|
|
21
|
-
collapse = 'multiple',
|
|
22
21
|
onKeyDown,
|
|
23
22
|
onBlur,
|
|
24
23
|
onFocus,
|
|
@@ -35,6 +34,8 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
35
34
|
noResults = 'No results',
|
|
36
35
|
size = 's',
|
|
37
36
|
marker,
|
|
37
|
+
limitedScrollHeight,
|
|
38
|
+
className,
|
|
38
39
|
...props
|
|
39
40
|
},
|
|
40
41
|
ref,
|
|
@@ -63,7 +64,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
63
64
|
|
|
64
65
|
const content = useMemo(
|
|
65
66
|
() => (
|
|
66
|
-
|
|
67
|
+
<div className={styles.content}>
|
|
67
68
|
{itemsJSX}
|
|
68
69
|
{loadingJSX}
|
|
69
70
|
{hasNoItems && !search?.value && !loading && (
|
|
@@ -76,14 +77,14 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
76
77
|
{noResults}
|
|
77
78
|
</div>
|
|
78
79
|
)}
|
|
79
|
-
|
|
80
|
+
</div>
|
|
80
81
|
),
|
|
81
82
|
[hasNoItems, itemsJSX, loading, loadingJSX, noData, noResults, search?.value],
|
|
82
83
|
);
|
|
83
84
|
|
|
84
85
|
const listJSX = (
|
|
85
86
|
<ul
|
|
86
|
-
className={commonStyles.listContainer}
|
|
87
|
+
className={cn(commonStyles.listContainer, className)}
|
|
87
88
|
ref={ref as ForwardedRef<HTMLUListElement>}
|
|
88
89
|
onKeyDown={onKeyDown}
|
|
89
90
|
tabIndex={tabIndex}
|
|
@@ -93,7 +94,7 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
93
94
|
role='menu'
|
|
94
95
|
{...extractSupportProps(props)}
|
|
95
96
|
>
|
|
96
|
-
<ToggleGroup selectionMode={
|
|
97
|
+
<ToggleGroup selectionMode={'multiple'}>
|
|
97
98
|
{(Number(pinTop?.length) > 0 || search) && (
|
|
98
99
|
<PinTopGroupItem>
|
|
99
100
|
{search && <SearchItem search={search} />}
|
|
@@ -105,12 +106,12 @@ export const ListPrivate = forwardRef<HTMLElement, ListPrivateProps>(
|
|
|
105
106
|
{scroll ? (
|
|
106
107
|
<Scroll
|
|
107
108
|
className={cn({
|
|
108
|
-
[commonStyles.scrollContainerS]: scroll && size === 's',
|
|
109
|
-
[commonStyles.scrollContainerM]: scroll && size === 'm',
|
|
110
|
-
[commonStyles.scrollContainerL]: scroll && size === 'l',
|
|
109
|
+
[commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
|
|
110
|
+
[commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
|
|
111
|
+
[commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
|
|
111
112
|
})}
|
|
112
|
-
barHideStrategy='
|
|
113
|
-
size='s'
|
|
113
|
+
barHideStrategy='leave'
|
|
114
|
+
size={size === 's' ? 's' : 'm'}
|
|
114
115
|
ref={scrollContainerRef}
|
|
115
116
|
>
|
|
116
117
|
{content}
|
|
@@ -3,8 +3,6 @@ import { createContext, ReactNode, useContext } from 'react';
|
|
|
3
3
|
export type ListContextType = {
|
|
4
4
|
/** Размер списка */
|
|
5
5
|
size?: 's' | 'm' | 'l';
|
|
6
|
-
// TODO
|
|
7
|
-
// collapse?: 'single' | 'multiple';
|
|
8
6
|
/** Отображать ли маркер у выбранного жлемента списка */
|
|
9
7
|
marker?: boolean;
|
|
10
8
|
};
|
|
@@ -3,7 +3,7 @@ import { useUncontrolledProp } from 'uncontrollable';
|
|
|
3
3
|
|
|
4
4
|
type SelectionSingleValueType = string | number | undefined;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type SelectionSingleState = {
|
|
7
7
|
/** Начальное состояние */
|
|
8
8
|
defaultValue?: SelectionSingleValueType;
|
|
9
9
|
/** Controlled состояние */
|
|
@@ -11,17 +11,20 @@ export type SelectionSingleProps = {
|
|
|
11
11
|
/** Controlled обработчик измения состояния */
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
13
|
onChange?(value: any): void;
|
|
14
|
+
/** Режим выбора */
|
|
15
|
+
mode: 'single';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SelectionSingleProps = {
|
|
14
19
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
20
|
setValue?(value: any): void;
|
|
16
|
-
/** Режим выбора */
|
|
17
|
-
selection: 'single';
|
|
18
21
|
/** Режим выбора single */
|
|
19
22
|
isSelectionSingle: true;
|
|
20
23
|
/** Режим выбора multi */
|
|
21
24
|
isSelectionMultiple: false;
|
|
22
|
-
};
|
|
25
|
+
} & SelectionSingleState;
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
type SelectionMultipleState = {
|
|
25
28
|
/** Начальное состояние */
|
|
26
29
|
defaultValue?: SelectionSingleValueType[];
|
|
27
30
|
/** Controlled состояние */
|
|
@@ -29,18 +32,21 @@ export type SelectionMultipleProps = {
|
|
|
29
32
|
/** Controlled обработчик измения состояния */
|
|
30
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
34
|
onChange?(value: any): void;
|
|
35
|
+
/** Режим выбора */
|
|
36
|
+
mode: 'multiple';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SelectionMultipleProps = {
|
|
32
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
41
|
setValue?(value: any): void;
|
|
34
|
-
/** Режим выбора */
|
|
35
|
-
selection: 'multiple';
|
|
36
42
|
/** Режим выбора single */
|
|
37
43
|
isSelectionSingle: false;
|
|
38
44
|
/** Режим выбора multi */
|
|
39
45
|
isSelectionMultiple: true;
|
|
40
|
-
};
|
|
46
|
+
} & SelectionMultipleState;
|
|
41
47
|
|
|
42
48
|
type SelectionNoneProps = {
|
|
43
|
-
|
|
49
|
+
mode?: undefined;
|
|
44
50
|
value?: undefined;
|
|
45
51
|
onChange?: undefined;
|
|
46
52
|
setValue?: undefined;
|
|
@@ -49,27 +55,29 @@ type SelectionNoneProps = {
|
|
|
49
55
|
isSelectionMultiple?: undefined;
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
|
|
53
59
|
|
|
54
60
|
type SelectionContextType =
|
|
55
61
|
| Omit<SelectionNoneProps, 'defaultValue'>
|
|
56
62
|
| Omit<SelectionSingleProps, 'defaultValue'>
|
|
57
63
|
| Omit<SelectionMultipleProps, 'defaultValue'>;
|
|
58
64
|
|
|
65
|
+
export type SelectionState = { selection?: SelectionSingleState | SelectionMultipleState };
|
|
66
|
+
|
|
59
67
|
export const SelectionContext = createContext<SelectionContextType>({
|
|
60
68
|
value: undefined,
|
|
61
69
|
onChange: undefined,
|
|
62
|
-
|
|
70
|
+
mode: undefined,
|
|
63
71
|
});
|
|
64
72
|
|
|
65
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
74
|
export function isSelectionMultipleProps(props: any): props is SelectionMultipleProps {
|
|
67
|
-
return '
|
|
75
|
+
return 'mode' in props && props['mode'] === 'multiple';
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
79
|
export function isSelectionSingleProps(props: any): props is SelectionSingleProps {
|
|
72
|
-
return '
|
|
80
|
+
return 'mode' in props && props['mode'] === 'single';
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
type Child = {
|
|
@@ -100,7 +108,14 @@ function SelectionSingleProvider({
|
|
|
100
108
|
|
|
101
109
|
return (
|
|
102
110
|
<SelectionContext.Provider
|
|
103
|
-
value={{
|
|
111
|
+
value={{
|
|
112
|
+
value,
|
|
113
|
+
onChange,
|
|
114
|
+
mode: 'single',
|
|
115
|
+
isSelectionSingle: true,
|
|
116
|
+
isSelectionMultiple: false,
|
|
117
|
+
setValue,
|
|
118
|
+
}}
|
|
104
119
|
>
|
|
105
120
|
{children}
|
|
106
121
|
</SelectionContext.Provider>
|
|
@@ -135,37 +150,36 @@ function SelectionMultipleProvider({
|
|
|
135
150
|
|
|
136
151
|
return (
|
|
137
152
|
<SelectionContext.Provider
|
|
138
|
-
value={{
|
|
153
|
+
value={{
|
|
154
|
+
value,
|
|
155
|
+
onChange,
|
|
156
|
+
mode: 'multiple',
|
|
157
|
+
isSelectionSingle: false,
|
|
158
|
+
isSelectionMultiple: true,
|
|
159
|
+
setValue,
|
|
160
|
+
}}
|
|
139
161
|
>
|
|
140
162
|
{children}
|
|
141
163
|
</SelectionContext.Provider>
|
|
142
164
|
);
|
|
143
165
|
}
|
|
144
166
|
|
|
145
|
-
export function SelectionProvider(props: SelectionProviderProps & Child) {
|
|
167
|
+
export function SelectionProvider({ children, ...props }: SelectionProviderProps & Child) {
|
|
146
168
|
if (isSelectionSingleProps(props)) {
|
|
147
|
-
return <SelectionSingleProvider {...props}
|
|
169
|
+
return <SelectionSingleProvider {...props}>{children}</SelectionSingleProvider>;
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
if (isSelectionMultipleProps(props)) {
|
|
151
|
-
return <SelectionMultipleProvider {...props}
|
|
173
|
+
return <SelectionMultipleProvider {...props}>{children}</SelectionMultipleProvider>;
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
return <SelectionContext.Provider value={{}}>{
|
|
176
|
+
return <SelectionContext.Provider value={{}}>{children}</SelectionContext.Provider>;
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
export const useSelectionContext = () => useContext(SelectionContext);
|
|
158
180
|
|
|
159
|
-
export function extractSelectionProps<T extends
|
|
160
|
-
selection,
|
|
161
|
-
value,
|
|
162
|
-
defaultValue,
|
|
163
|
-
onChange,
|
|
164
|
-
}: T) {
|
|
181
|
+
export function extractSelectionProps<T extends SelectionState>({ selection }: T) {
|
|
165
182
|
return {
|
|
166
|
-
selection,
|
|
167
|
-
value,
|
|
168
|
-
defaultValue,
|
|
169
|
-
onChange,
|
|
183
|
+
...(selection ?? {}),
|
|
170
184
|
} as SelectionProviderProps;
|
|
171
185
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
2
2
|
|
|
3
3
|
.listContainer {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
|
|
7
|
+
height: 100%;
|
|
4
8
|
margin: 0;
|
|
5
9
|
padding: 0;
|
|
6
10
|
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
display: block;
|
|
33
37
|
|
|
34
38
|
box-sizing: border-box;
|
|
39
|
+
height: 100%;
|
|
35
40
|
padding: $dimension-025m;
|
|
36
41
|
|
|
37
42
|
&:has(.listContainer:focus-visible) {
|
|
@@ -5,7 +5,13 @@ import { WithSupportProps } from '@snack-uikit/utils';
|
|
|
5
5
|
|
|
6
6
|
import { ScrollProps, SearchState } from '../../types';
|
|
7
7
|
import { ItemProps } from '../Items';
|
|
8
|
-
import { ListContextType,
|
|
8
|
+
import { ListContextType, SelectionState } from './contexts';
|
|
9
|
+
|
|
10
|
+
type CollapseState = {
|
|
11
|
+
value?: (string | number)[];
|
|
12
|
+
onChange?(value: (string | number)[]): void;
|
|
13
|
+
defaultValue?: (string | number)[];
|
|
14
|
+
};
|
|
9
15
|
|
|
10
16
|
export type ListProps = WithSupportProps<
|
|
11
17
|
{
|
|
@@ -15,12 +21,13 @@ export type ListProps = WithSupportProps<
|
|
|
15
21
|
pinTop?: ItemProps[];
|
|
16
22
|
/** Элементы списка, закрепленные снизу */
|
|
17
23
|
pinBottom?: ItemProps[];
|
|
18
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Кастомизируемый элемент в конце списка
|
|
26
|
+
* @type ReactElement
|
|
27
|
+
*/
|
|
19
28
|
footer?: ReactNode;
|
|
20
|
-
/** Список ссылок на
|
|
29
|
+
/** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
|
|
21
30
|
footerActiveElementsRefs?: RefObject<HTMLElement>[];
|
|
22
|
-
// TODO: add later
|
|
23
|
-
// collapse?: 'single' | 'multiple';
|
|
24
31
|
/** Настройки поисковой строки */
|
|
25
32
|
search?: SearchState;
|
|
26
33
|
/** Флаг, отвещающий за состояние загрузки списка */
|
|
@@ -29,7 +36,14 @@ export type ListProps = WithSupportProps<
|
|
|
29
36
|
noData?: string;
|
|
30
37
|
/** Текст для состояния "Отсутсвие результата" при поиске */
|
|
31
38
|
noResults?: string;
|
|
32
|
-
|
|
39
|
+
/** Tab Index */
|
|
40
|
+
tabIndex?: number;
|
|
41
|
+
/** Настройки раскрытия элементов */
|
|
42
|
+
collapse?: CollapseState;
|
|
43
|
+
/** CSS-класс */
|
|
44
|
+
className?: string;
|
|
45
|
+
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
46
|
+
} & SelectionState &
|
|
33
47
|
ListContextType &
|
|
34
48
|
ScrollProps
|
|
35
49
|
>;
|
|
@@ -40,7 +54,7 @@ export type DroplistProps = {
|
|
|
40
54
|
/** Триггер для дроплиста */
|
|
41
55
|
children?: ReactNode;
|
|
42
56
|
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> &
|
|
43
|
-
ListProps
|
|
57
|
+
Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
|
|
44
58
|
|
|
45
59
|
export type ListPrivateProps = ListProps & {
|
|
46
60
|
nested?: boolean;
|
|
@@ -49,6 +63,5 @@ export type ListPrivateProps = ListProps & {
|
|
|
49
63
|
onFocus?(e: FocusEvent<HTMLElement>): void;
|
|
50
64
|
onBlur?(e: FocusEvent<HTMLElement>): void;
|
|
51
65
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
52
|
-
|
|
53
|
-
collapse?: 'single' | 'multiple';
|
|
66
|
+
limitedScrollHeight?: boolean;
|
|
54
67
|
};
|
|
@@ -4,9 +4,10 @@ import styles from './styles.module.scss';
|
|
|
4
4
|
|
|
5
5
|
type HiddenTabButtonProps = {
|
|
6
6
|
listRef: RefObject<HTMLElement>;
|
|
7
|
+
tabIndex?: number;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProps>(({ listRef }, ref) => {
|
|
10
|
+
export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProps>(({ listRef, tabIndex }, ref) => {
|
|
10
11
|
const handleFocus = useCallback(
|
|
11
12
|
(e: FocusEvent<HTMLButtonElement>) => {
|
|
12
13
|
if (e.relatedTarget !== listRef.current) {
|
|
@@ -23,5 +24,14 @@ export const HiddenTabButton = forwardRef<HTMLButtonElement, HiddenTabButtonProp
|
|
|
23
24
|
e.stopPropagation();
|
|
24
25
|
}, []);
|
|
25
26
|
|
|
26
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
aria-hidden
|
|
30
|
+
ref={ref}
|
|
31
|
+
onKeyDown={handleKeyDown}
|
|
32
|
+
onFocus={handleFocus}
|
|
33
|
+
className={styles.hiddenBtn}
|
|
34
|
+
tabIndex={tabIndex}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
27
37
|
});
|
package/src/utils.ts
CHANGED
|
@@ -20,7 +20,10 @@ export function withCollapsedItems({ items, openCollapsedItems }: WithCollapsedI
|
|
|
20
20
|
let expandedIds: Array<string | number> = [];
|
|
21
21
|
|
|
22
22
|
items.forEach(item => {
|
|
23
|
-
if (
|
|
23
|
+
if (
|
|
24
|
+
((isBaseItemProps(item) && !item.inactive) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
|
|
25
|
+
!item.disabled
|
|
26
|
+
) {
|
|
24
27
|
newItems = newItems.concat([item]);
|
|
25
28
|
ids = ids.concat([item.id ?? '']);
|
|
26
29
|
|
|
@@ -88,7 +91,13 @@ export function extractItemIds(items: ItemProps[]): Array<string | number> {
|
|
|
88
91
|
|
|
89
92
|
export function extractChildIds({ items }: { items: ItemProps[] }): Array<string | number> {
|
|
90
93
|
return items
|
|
91
|
-
.filter(
|
|
94
|
+
.filter(
|
|
95
|
+
item =>
|
|
96
|
+
isAccordionItemProps(item) ||
|
|
97
|
+
isNextListItemProps(item) ||
|
|
98
|
+
isGroupItemProps(item) ||
|
|
99
|
+
(isBaseItemProps(item) && !item.disabled && !item.inactive),
|
|
100
|
+
)
|
|
92
101
|
.reduce(
|
|
93
102
|
(prev: Array<string | number>, item: ItemProps) => {
|
|
94
103
|
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|