@snack-uikit/list 0.1.2 → 0.1.3-preview-3a43a07a.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/README.md +6 -4
- package/dist/components/Items/BaseItem/BaseItem.d.ts +3 -3
- package/dist/components/Items/BaseItem/BaseItem.js +7 -5
- package/dist/components/Items/BaseItem/styles.module.css +3 -0
- package/dist/components/Items/styles.module.css +10 -10
- package/dist/components/Items/types.d.ts +9 -7
- package/dist/components/Lists/List/List.d.ts +3 -1
- package/dist/components/Lists/List/List.js +14 -8
- package/dist/components/Lists/ListPrivate/ListPrivate.d.ts +2 -0
- package/dist/components/Lists/types.d.ts +3 -1
- 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 +3 -2
- package/src/components/Items/BaseItem/BaseItem.tsx +17 -10
- package/src/components/Items/BaseItem/styles.module.scss +4 -0
- package/src/components/Items/styles.module.scss +17 -15
- package/src/components/Items/types.ts +9 -7
- package/src/components/Lists/List/List.tsx +25 -14
- package/src/components/Lists/types.ts +3 -1
- package/src/helperComponents/HiddenTabButton/HiddenTabButton.tsx +12 -2
- package/src/utils.ts +11 -2
package/README.md
CHANGED
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
| widthStrategy | enum PopoverWidthStrategy: `"auto"`, `"gte"`, `"eq"` | auto | Стратегия управления шириной контейнера поповера <br> - `auto` - соответствует ширине контента, <br> - `gte` - Great Than or Equal, равен ширине таргета или больше ее, если контент в поповере шире, <br> - `eq` - Equal, строго равен ширине таргета. |
|
|
29
29
|
| trigger | enum Trigger: `"click"`, `"hover"`, `"focusVisible"`, `"focus"`, `"hoverAndFocusVisible"`, `"hoverAndFocus"`, `"clickAndFocusVisible"` | - | Условие отображения поповера: <br> - `click` - открывать по клику <br> - `hover` - открывать по ховеру <br> - `focusVisible` - открывать по focus-visible <br> - `focus` - открывать по фокусу <br> - `hoverAndFocusVisible` - открывать по ховеру и focus-visible <br> - `hoverAndFocus` - открывать по ховеру и фокусу <br> - `clickAndFocusVisible` - открывать по клику и focus-visible |
|
|
30
30
|
| placement | enum Placement: `"left"`, `"left-start"`, `"left-end"`, `"right"`, `"right-start"`, `"right-end"`, `"top"`, `"top-start"`, `"top-end"`, `"bottom"`, `"bottom-start"`, `"bottom-end"` | top | Положение поповера относительно своего триггера (children). |
|
|
31
|
+
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
|
|
32
|
+
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
|
|
33
|
+
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
|
|
34
|
+
| selection | "single" \| "multiple" | - | Режим выбора |
|
|
31
35
|
| pinTop | `ItemProps[]` | - | Элементы списка, закрепленные сверху |
|
|
32
36
|
| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
|
|
33
37
|
| footer | `ReactNode` | - | Кастомизируемый элемент, помещаемый внизу списка |
|
|
@@ -36,10 +40,6 @@
|
|
|
36
40
|
| loading | `boolean` | - | Флаг, отвещающий за состояние загрузки списка |
|
|
37
41
|
| noData | `string` | - | Текст для состояния "Отсутсвие данных" |
|
|
38
42
|
| noResults | `string` | - | Текст для состояния "Отсутсвие результата" при поиске |
|
|
39
|
-
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
|
|
40
|
-
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
|
|
41
|
-
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
|
|
42
|
-
| selection | "single" \| "multiple" | - | Режим выбора |
|
|
43
43
|
| size | "s" \| "m" \| "l" | - | Размер списка |
|
|
44
44
|
| marker | `boolean` | - | Отображать ли маркер у выбранного жлемента списка |
|
|
45
45
|
| scroll | `boolean` | - | Включить ли скролл для основной части списка |
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
| loading | `boolean` | - | Флаг, отвещающий за состояние загрузки списка |
|
|
59
59
|
| noData | `string` | - | Текст для состояния "Отсутсвие данных" |
|
|
60
60
|
| noResults | `string` | - | Текст для состояния "Отсутсвие результата" при поиске |
|
|
61
|
+
| tabIndex | `number` | - | |
|
|
62
|
+
| onKeyDown | `(e: KeyboardEvent<HTMLElement>) => void` | - | |
|
|
61
63
|
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
|
|
62
64
|
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
|
|
63
65
|
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BaseItemPrivateProps, BaseItemProps
|
|
2
|
-
type AllBaseItemProps = BaseItemProps & BaseItemPrivateProps &
|
|
1
|
+
import { BaseItemPrivateProps, BaseItemProps } from '../types';
|
|
2
|
+
type AllBaseItemProps = BaseItemProps & BaseItemPrivateProps & {
|
|
3
3
|
indeterminate?: boolean;
|
|
4
4
|
onSelect?(): void;
|
|
5
5
|
isParentNode?: boolean;
|
|
6
6
|
};
|
|
7
|
-
export declare function BaseItem({ beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, ...rest }: AllBaseItemProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function BaseItem({ beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, className, inactive, ...rest }: AllBaseItemProps): import("react/jsx-runtime").JSX.Element;
|
|
8
8
|
export {};
|
|
@@ -19,7 +19,7 @@ import commonStyles from '../styles.module.css';
|
|
|
19
19
|
import { CHECKBOX_SIZE_MAP } from './constants';
|
|
20
20
|
import styles from './styles.module.css';
|
|
21
21
|
export function BaseItem(_a) {
|
|
22
|
-
var { beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode } = _a, rest = __rest(_a, ["beforeContent", "afterContent", "content", "onClick", "id", "expandIcon", "disabled", "open", "itemRef", "switch", "onKeyDown", "onFocus", "indeterminate", "onSelect", "isParentNode"]);
|
|
22
|
+
var { beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, className, inactive } = _a, rest = __rest(_a, ["beforeContent", "afterContent", "content", "onClick", "id", "expandIcon", "disabled", "open", "itemRef", "switch", "onKeyDown", "onFocus", "indeterminate", "onSelect", "isParentNode", "className", "inactive"]);
|
|
23
23
|
const { option, caption, description } = content || {};
|
|
24
24
|
const { parentResetActiveFocusIndex } = useParentListContext();
|
|
25
25
|
const { size, marker } = useListContext();
|
|
@@ -30,9 +30,11 @@ export function BaseItem(_a) {
|
|
|
30
30
|
onChange === null || onChange === void 0 ? void 0 : onChange(id !== null && id !== void 0 ? id : '');
|
|
31
31
|
};
|
|
32
32
|
const handleItemClick = (e) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (!inactive) {
|
|
34
|
+
parentResetActiveFocusIndex === null || parentResetActiveFocusIndex === void 0 ? void 0 : parentResetActiveFocusIndex();
|
|
35
|
+
if (!isParentNode) {
|
|
36
|
+
handleChange();
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
38
40
|
};
|
|
@@ -65,5 +67,5 @@ export function BaseItem(_a) {
|
|
|
65
67
|
e.stopPropagation();
|
|
66
68
|
};
|
|
67
69
|
const props = extractSupportProps(rest);
|
|
68
|
-
return (_jsx("li", { role: 'menuitem', "data-test-id": props['data-test-id'] || 'list__base-item_' + id, children: _jsxs("button", { ref: itemRef, className: cn(commonStyles.listItem, styles.droplistItem), "data-size": size, onClick: handleItemClick, tabIndex: -1, "data-checked": (isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined, "data-variant": selection || undefined, "data-open": open || undefined, disabled: disabled, onKeyDown: handleItemKeyDown, onFocus: handleItemFocus, style: { '--level': level }, children: [!switchProp && isSelectionSingle && marker && !isParentNode && (_jsx("div", { className: styles.markerContainer, "data-test-id": 'list__base-item-marker' })), !switchProp && isSelectionMultiple && (_jsx("div", { className: styles.checkbox, children: _jsx(Checkbox, { size: CHECKBOX_SIZE_MAP[size !== null && size !== void 0 ? size : 's'], disabled: disabled, tabIndex: -1, onChange: handleCheckboxChange, checked: isChecked, "data-test-id": 'list__base-item-checkbox', onClick: handleCheckboxClick, indeterminate: indeterminate }) })), beforeContent && _jsx("div", { className: styles.beforeContent, children: beforeContent }), _jsxs("div", { className: styles.content, children: [_jsxs("div", { className: styles.headline, children: [_jsx("span", { className: styles.option, children: _jsx(TruncateString, { text: option, maxLines: 1 }) }), caption && _jsx("span", { className: styles.caption, children: caption })] }), description && (_jsx("div", { className: styles.description, children: _jsx(TruncateString, { text: description, maxLines: 2 }) }))] }), afterContent, switchProp && _jsx(Switch, { disabled: disabled, checked: isChecked, "data-test-id": 'list__base-item-switch' }), !switchProp && expandIcon && _jsx("span", { className: styles.expandableIcon, children: expandIcon })] }) }));
|
|
70
|
+
return (_jsx("li", { role: 'menuitem', "data-test-id": props['data-test-id'] || 'list__base-item_' + id, children: _jsxs("button", { ref: itemRef, className: cn(commonStyles.listItem, styles.droplistItem, className), "data-size": size, onClick: handleItemClick, tabIndex: -1, "data-non-pointer": inactive && !onClick, "data-inactive": inactive || undefined, "data-checked": (isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined, "data-variant": selection || undefined, "data-open": open || undefined, disabled: disabled, onKeyDown: handleItemKeyDown, onFocus: handleItemFocus, style: { '--level': level }, children: [!switchProp && isSelectionSingle && marker && !isParentNode && !inactive && (_jsx("div", { className: styles.markerContainer, "data-test-id": 'list__base-item-marker' })), !switchProp && isSelectionMultiple && !inactive && (_jsx("div", { className: styles.checkbox, children: _jsx(Checkbox, { size: CHECKBOX_SIZE_MAP[size !== null && size !== void 0 ? size : 's'], disabled: disabled, tabIndex: -1, onChange: handleCheckboxChange, checked: isChecked, "data-test-id": 'list__base-item-checkbox', onClick: handleCheckboxClick, indeterminate: indeterminate }) })), beforeContent && _jsx("div", { className: styles.beforeContent, children: beforeContent }), _jsxs("div", { className: styles.content, children: [_jsxs("div", { className: styles.headline, children: [_jsx("span", { className: styles.option, children: _jsx(TruncateString, { text: option, maxLines: 1 }) }), caption && _jsx("span", { className: styles.caption, children: caption })] }), description && (_jsx("div", { className: styles.description, children: _jsx(TruncateString, { text: description, maxLines: 2 }) }))] }), afterContent, switchProp && !inactive && (_jsx(Switch, { disabled: disabled, checked: isChecked, "data-test-id": 'list__base-item-switch' })), !switchProp && expandIcon && _jsx("span", { className: styles.expandableIcon, children: expandIcon })] }) }));
|
|
69
71
|
}
|
|
@@ -170,6 +170,9 @@
|
|
|
170
170
|
letter-spacing:var(--sans-body-m-letter-spacing, 0.1px);
|
|
171
171
|
paragraph-spacing:var(--sans-body-m-paragraph-spacing, 7.7px);
|
|
172
172
|
}
|
|
173
|
+
.droplistItem[data-non-pointer]{
|
|
174
|
+
cursor:inherit;
|
|
175
|
+
}
|
|
173
176
|
.droplistItem:disabled .option,
|
|
174
177
|
.droplistItem:disabled .description,
|
|
175
178
|
.droplistItem:disabled .caption,
|
|
@@ -50,16 +50,6 @@
|
|
|
50
50
|
height:100%;
|
|
51
51
|
background-color:transparent;
|
|
52
52
|
}
|
|
53
|
-
.listItem[data-open]::after, .listItem[data-focused]::after, .listItem:hover::after, .listItem:focus-visible::after{
|
|
54
|
-
opacity:var(--opacity-a008, 0.08);
|
|
55
|
-
background-color:var(--sys-neutral-accent-default, #757575);
|
|
56
|
-
}
|
|
57
|
-
.listItem[data-focused], .listItem:focus-visible{
|
|
58
|
-
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
59
|
-
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
60
|
-
outline-color:var(--border-state-focus-s-border-color, );
|
|
61
|
-
outline-offset:calc(var(--border-state-focus-s-border-width, 2px) * -1);
|
|
62
|
-
}
|
|
63
53
|
.listItem:disabled{
|
|
64
54
|
cursor:not-allowed;
|
|
65
55
|
background-color:transparent;
|
|
@@ -71,4 +61,14 @@
|
|
|
71
61
|
.listItem:disabled::after{
|
|
72
62
|
cursor:not-allowed;
|
|
73
63
|
background-color:transparent;
|
|
64
|
+
}
|
|
65
|
+
.listItem:not([data-inactive])[data-open]::after, .listItem:not([data-inactive])[data-focused]::after, .listItem:not([data-inactive]):hover::after, .listItem:not([data-inactive]):focus-visible::after{
|
|
66
|
+
opacity:var(--opacity-a008, 0.08);
|
|
67
|
+
background-color:var(--sys-neutral-accent-default, #757575);
|
|
68
|
+
}
|
|
69
|
+
.listItem:not([data-inactive])[data-focused], .listItem:not([data-inactive]):focus-visible{
|
|
70
|
+
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
71
|
+
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
72
|
+
outline-color:var(--border-state-focus-s-border-color, );
|
|
73
|
+
outline-offset:calc(var(--border-state-focus-s-border-width, 2px) * -1);
|
|
74
74
|
}
|
|
@@ -22,22 +22,24 @@ export type BaseItemProps = WithSupportProps<{
|
|
|
22
22
|
id?: string | number;
|
|
23
23
|
disabled?: boolean;
|
|
24
24
|
itemRef?: RefObject<HTMLButtonElement>;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
className?: string;
|
|
26
|
+
inactive?: boolean;
|
|
27
27
|
switch?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type
|
|
28
|
+
}>;
|
|
29
|
+
type BaseItemsWithoutNonGroupProps = Omit<BaseItemProps, 'switch' | 'inactive'>;
|
|
30
|
+
export type ItemProps = BaseItemProps | AccordionItemProps | NextListItemProps | GroupItemProps;
|
|
31
|
+
export type AccordionItemProps = BaseItemsWithoutNonGroupProps & {
|
|
31
32
|
items: ItemProps[];
|
|
32
33
|
type: 'collapse';
|
|
33
34
|
};
|
|
34
|
-
export type NextListItemProps =
|
|
35
|
+
export type NextListItemProps = BaseItemsWithoutNonGroupProps & {
|
|
35
36
|
items: ItemProps[];
|
|
36
37
|
type: 'next-list';
|
|
37
38
|
placement?: 'right-start' | 'left-start' | 'left' | 'right' | 'left-end' | 'right-end';
|
|
38
39
|
search?: SearchState;
|
|
39
40
|
} & ScrollProps;
|
|
40
|
-
export type GroupItemProps = SeparatorProps & {
|
|
41
|
+
export type GroupItemProps = Omit<SeparatorProps, 'size'> & {
|
|
41
42
|
items: ItemProps[];
|
|
42
43
|
id?: string | number;
|
|
43
44
|
};
|
|
45
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { KeyboardEvent } from 'react';
|
|
2
2
|
export declare const List: import("react").ForwardRefExoticComponent<{
|
|
3
3
|
'data-test-id'?: string | undefined;
|
|
4
4
|
} & import("react").AriaAttributes & {
|
|
@@ -11,4 +11,6 @@ export declare const List: import("react").ForwardRefExoticComponent<{
|
|
|
11
11
|
loading?: boolean | undefined;
|
|
12
12
|
noData?: string | undefined;
|
|
13
13
|
noResults?: string | undefined;
|
|
14
|
+
tabIndex?: number | undefined;
|
|
15
|
+
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
14
16
|
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -10,6 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import mergeRefs from 'merge-refs';
|
|
13
14
|
import { forwardRef, useMemo, useRef, useState } from 'react';
|
|
14
15
|
import { HiddenTabButton } from '../../../helperComponents';
|
|
15
16
|
import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
|
|
@@ -18,8 +19,8 @@ import { extractSelectionProps, ParentListContext, SelectionProvider, useParentL
|
|
|
18
19
|
import { useKeyboardNavigation } from '../hooks';
|
|
19
20
|
import { ListPrivate } from '../ListPrivate';
|
|
20
21
|
import styles from '../styles.module.css';
|
|
21
|
-
export const List = forwardRef((_a) => {
|
|
22
|
-
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs"]);
|
|
22
|
+
export const List = forwardRef((_a, ref) => {
|
|
23
|
+
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, onKeyDown, tabIndex = 0 } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "onKeyDown", "tabIndex"]);
|
|
23
24
|
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
24
25
|
const memorizedItems = useMemo(() => addItemsIds((pinTop !== null && pinTop !== void 0 ? pinTop : []).concat(itemsProp).concat(pinBottom !== null && pinBottom !== void 0 ? pinBottom : [])), [itemsProp, pinBottom, pinTop]);
|
|
25
26
|
const [openCollapsedItems, setOpenCollapsedItems] = useState([]);
|
|
@@ -52,6 +53,16 @@ export const List = forwardRef((_a) => {
|
|
|
52
53
|
itemRefs,
|
|
53
54
|
});
|
|
54
55
|
const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
|
|
56
|
+
const mergedHandlerKeyDown = (e) => {
|
|
57
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
58
|
+
handleListKeyDown === null || handleListKeyDown === void 0 ? void 0 : handleListKeyDown(e);
|
|
59
|
+
};
|
|
60
|
+
const handleOnFocus = (e) => {
|
|
61
|
+
if (e.relatedTarget === null ||
|
|
62
|
+
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))) {
|
|
63
|
+
resetActiveFocusIndex();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
55
66
|
return (
|
|
56
67
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
57
68
|
// @ts-ignore
|
|
@@ -68,10 +79,5 @@ export const List = forwardRef((_a) => {
|
|
|
68
79
|
parentResetActiveFocusIndex: resetActiveFocusIndex,
|
|
69
80
|
openCollapsedItems,
|
|
70
81
|
toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
|
|
71
|
-
}, children: _jsxs("div", { className: styles.wrapper, "data-active": isActive || undefined, children: [_jsx(ListPrivate, Object.assign({}, props, slicedItems, { ref: listRef, onFocus:
|
|
72
|
-
if (e.relatedTarget === null ||
|
|
73
|
-
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))) {
|
|
74
|
-
resetActiveFocusIndex();
|
|
75
|
-
}
|
|
76
|
-
}, onKeyDown: handleListKeyDown, tabIndex: 0, search: search, nested: false })), _jsx(HiddenTabButton, { ref: btnRef, listRef: listRef })] }) }) })));
|
|
82
|
+
}, children: _jsxs("div", { className: styles.wrapper, "data-active": isActive || undefined, children: [_jsx(ListPrivate, Object.assign({}, props, slicedItems, { ref: mergeRefs(ref, listRef), onFocus: handleOnFocus, onKeyDown: mergedHandlerKeyDown, tabIndex: tabIndex, search: search, nested: false })), _jsx(HiddenTabButton, { ref: btnRef, listRef: listRef, tabIndex: tabIndex })] }) }) })));
|
|
77
83
|
});
|
|
@@ -11,6 +11,8 @@ export declare const ListPrivate: import("react").ForwardRefExoticComponent<{
|
|
|
11
11
|
loading?: boolean | undefined;
|
|
12
12
|
noData?: string | undefined;
|
|
13
13
|
noResults?: string | undefined;
|
|
14
|
+
tabIndex?: number | undefined;
|
|
15
|
+
onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
|
|
14
16
|
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & {
|
|
15
17
|
nested?: boolean | undefined;
|
|
16
18
|
active?: boolean | undefined;
|
|
@@ -23,13 +23,15 @@ export type ListProps = WithSupportProps<{
|
|
|
23
23
|
noData?: string;
|
|
24
24
|
/** Текст для состояния "Отсутсвие результата" при поиске */
|
|
25
25
|
noResults?: string;
|
|
26
|
+
tabIndex?: number;
|
|
27
|
+
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
26
28
|
} & Pick<SelectionProviderProps, 'value' | 'defaultValue' | 'onChange' | 'selection'> & ListContextType & ScrollProps>;
|
|
27
29
|
export type DroplistProps = {
|
|
28
30
|
/** Ссылка на элемент-триггер для дроплиста */
|
|
29
31
|
triggerElemRef?: RefObject<HTMLElement>;
|
|
30
32
|
/** Триггер для дроплиста */
|
|
31
33
|
children?: ReactNode;
|
|
32
|
-
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> & ListProps
|
|
34
|
+
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> & Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
|
|
33
35
|
export type ListPrivateProps = ListProps & {
|
|
34
36
|
nested?: boolean;
|
|
35
37
|
active?: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
2
|
type HiddenTabButtonProps = {
|
|
3
3
|
listRef: RefObject<HTMLElement>;
|
|
4
|
+
tabIndex?: number;
|
|
4
5
|
};
|
|
5
6
|
export declare const HiddenTabButton: import("react").ForwardRefExoticComponent<HiddenTabButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
6
7
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useCallback } from 'react';
|
|
3
3
|
import styles from './styles.module.css';
|
|
4
|
-
export const HiddenTabButton = forwardRef(({ listRef }, ref) => {
|
|
4
|
+
export const HiddenTabButton = forwardRef(({ listRef, tabIndex }, ref) => {
|
|
5
5
|
const handleFocus = useCallback((e) => {
|
|
6
6
|
var _a;
|
|
7
7
|
if (e.relatedTarget !== listRef.current) {
|
|
@@ -13,5 +13,5 @@ export const HiddenTabButton = forwardRef(({ listRef }, ref) => {
|
|
|
13
13
|
const handleKeyDown = useCallback((e) => {
|
|
14
14
|
e.stopPropagation();
|
|
15
15
|
}, []);
|
|
16
|
-
return _jsx("button", { "aria-hidden": true, ref: ref, onKeyDown: handleKeyDown, onFocus: handleFocus, className: styles.hiddenBtn });
|
|
16
|
+
return (_jsx("button", { "aria-hidden": true, ref: ref, onKeyDown: handleKeyDown, onFocus: handleFocus, className: styles.hiddenBtn, tabIndex: tabIndex }));
|
|
17
17
|
});
|
package/dist/utils.js
CHANGED
|
@@ -6,7 +6,8 @@ export function withCollapsedItems({ items, openCollapsedItems }) {
|
|
|
6
6
|
let expandedIds = [];
|
|
7
7
|
items.forEach(item => {
|
|
8
8
|
var _a;
|
|
9
|
-
if ((isBaseItemProps(item) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
|
|
9
|
+
if (((isBaseItemProps(item) && !item.inactive) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
|
|
10
|
+
!item.disabled) {
|
|
10
11
|
newItems = newItems.concat([item]);
|
|
11
12
|
ids = ids.concat([(_a = item.id) !== null && _a !== void 0 ? _a : '']);
|
|
12
13
|
if (item.itemRef) {
|
|
@@ -55,7 +56,10 @@ export function extractItemIds(items) {
|
|
|
55
56
|
}
|
|
56
57
|
export function extractChildIds({ items }) {
|
|
57
58
|
return items
|
|
58
|
-
.filter(item => isAccordionItemProps(item) ||
|
|
59
|
+
.filter(item => isAccordionItemProps(item) ||
|
|
60
|
+
isNextListItemProps(item) ||
|
|
61
|
+
isGroupItemProps(item) ||
|
|
62
|
+
(isBaseItemProps(item) && !item.disabled && !item.inactive))
|
|
59
63
|
.reduce((prev, item) => {
|
|
60
64
|
var _a;
|
|
61
65
|
if (isAccordionItemProps(item) || isNextListItemProps(item)) {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "List",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.3-preview-3a43a07a.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"@snack-uikit/truncate-string": "0.4.5",
|
|
43
43
|
"@snack-uikit/utils": "3.2.0",
|
|
44
44
|
"classnames": "2.5.1",
|
|
45
|
+
"merge-refs": "1.2.2",
|
|
45
46
|
"uncontrollable": "8.0.4"
|
|
46
47
|
},
|
|
47
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "cd354254d1d434829a3d3235ac4b46723a853636"
|
|
48
49
|
}
|
|
@@ -7,13 +7,12 @@ import { extractSupportProps } from '@snack-uikit/utils';
|
|
|
7
7
|
|
|
8
8
|
import { useCollapseContext, useListContext, useParentListContext, useSelectionContext } from '../../Lists/contexts';
|
|
9
9
|
import commonStyles from '../styles.module.scss';
|
|
10
|
-
import { BaseItemPrivateProps, BaseItemProps
|
|
10
|
+
import { BaseItemPrivateProps, BaseItemProps } from '../types';
|
|
11
11
|
import { CHECKBOX_SIZE_MAP } from './constants';
|
|
12
12
|
import styles from './styles.module.scss';
|
|
13
13
|
|
|
14
14
|
type AllBaseItemProps = BaseItemProps &
|
|
15
|
-
BaseItemPrivateProps &
|
|
16
|
-
SwitchProps & { indeterminate?: boolean; onSelect?(): void; isParentNode?: boolean };
|
|
15
|
+
BaseItemPrivateProps & { indeterminate?: boolean; onSelect?(): void; isParentNode?: boolean };
|
|
17
16
|
|
|
18
17
|
export function BaseItem({
|
|
19
18
|
beforeContent,
|
|
@@ -31,6 +30,8 @@ export function BaseItem({
|
|
|
31
30
|
indeterminate,
|
|
32
31
|
onSelect,
|
|
33
32
|
isParentNode,
|
|
33
|
+
className,
|
|
34
|
+
inactive,
|
|
34
35
|
...rest
|
|
35
36
|
}: AllBaseItemProps) {
|
|
36
37
|
const { option, caption, description } = content || {};
|
|
@@ -47,10 +48,12 @@ export function BaseItem({
|
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
const handleItemClick = (e: MouseEvent<HTMLButtonElement>) => {
|
|
50
|
-
|
|
51
|
+
if (!inactive) {
|
|
52
|
+
parentResetActiveFocusIndex?.();
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
if (!isParentNode) {
|
|
55
|
+
handleChange();
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
onClick?.(e);
|
|
@@ -96,10 +99,12 @@ export function BaseItem({
|
|
|
96
99
|
<li role={'menuitem'} data-test-id={props['data-test-id'] || 'list__base-item_' + id}>
|
|
97
100
|
<button
|
|
98
101
|
ref={itemRef}
|
|
99
|
-
className={cn(commonStyles.listItem, styles.droplistItem)}
|
|
102
|
+
className={cn(commonStyles.listItem, styles.droplistItem, className)}
|
|
100
103
|
data-size={size}
|
|
101
104
|
onClick={handleItemClick}
|
|
102
105
|
tabIndex={-1}
|
|
106
|
+
data-non-pointer={inactive && !onClick}
|
|
107
|
+
data-inactive={inactive || undefined}
|
|
103
108
|
data-checked={(isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined}
|
|
104
109
|
data-variant={selection || undefined}
|
|
105
110
|
data-open={open || undefined}
|
|
@@ -108,10 +113,10 @@ export function BaseItem({
|
|
|
108
113
|
onFocus={handleItemFocus}
|
|
109
114
|
style={{ '--level': level }}
|
|
110
115
|
>
|
|
111
|
-
{!switchProp && isSelectionSingle && marker && !isParentNode && (
|
|
116
|
+
{!switchProp && isSelectionSingle && marker && !isParentNode && !inactive && (
|
|
112
117
|
<div className={styles.markerContainer} data-test-id='list__base-item-marker' />
|
|
113
118
|
)}
|
|
114
|
-
{!switchProp && isSelectionMultiple && (
|
|
119
|
+
{!switchProp && isSelectionMultiple && !inactive && (
|
|
115
120
|
<div className={styles.checkbox}>
|
|
116
121
|
<Checkbox
|
|
117
122
|
size={CHECKBOX_SIZE_MAP[size ?? 's']}
|
|
@@ -145,7 +150,9 @@ export function BaseItem({
|
|
|
145
150
|
|
|
146
151
|
{afterContent}
|
|
147
152
|
|
|
148
|
-
{switchProp &&
|
|
153
|
+
{switchProp && !inactive && (
|
|
154
|
+
<Switch disabled={disabled} checked={isChecked} data-test-id='list__base-item-switch' />
|
|
155
|
+
)}
|
|
149
156
|
{!switchProp && expandIcon && <span className={styles.expandableIcon}>{expandIcon}</span>}
|
|
150
157
|
</button>
|
|
151
158
|
</li>
|
|
@@ -46,21 +46,6 @@ $sizes: 's', 'm', 'l';
|
|
|
46
46
|
background-color: transparent;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
&[data-open],
|
|
50
|
-
&[data-focused],
|
|
51
|
-
&:hover,
|
|
52
|
-
&:focus-visible {
|
|
53
|
-
&::after {
|
|
54
|
-
opacity: $opacity-a008;
|
|
55
|
-
background-color: $sys-neutral-accent-default;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
&[data-focused],
|
|
60
|
-
&:focus-visible {
|
|
61
|
-
@include outline-inside-var($container-focused-s);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
49
|
&:disabled {
|
|
65
50
|
cursor: not-allowed;
|
|
66
51
|
background-color: transparent;
|
|
@@ -75,4 +60,21 @@ $sizes: 's', 'm', 'l';
|
|
|
75
60
|
background-color: transparent;
|
|
76
61
|
}
|
|
77
62
|
}
|
|
63
|
+
|
|
64
|
+
&:not([data-inactive]) {
|
|
65
|
+
&[data-open],
|
|
66
|
+
&[data-focused],
|
|
67
|
+
&:hover,
|
|
68
|
+
&:focus-visible {
|
|
69
|
+
&::after {
|
|
70
|
+
opacity: $opacity-a008;
|
|
71
|
+
background-color: $sys-neutral-accent-default;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&[data-focused],
|
|
76
|
+
&:focus-visible {
|
|
77
|
+
@include outline-inside-var($container-focused-s);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
78
80
|
}
|
|
@@ -32,30 +32,32 @@ export type BaseItemProps = WithSupportProps<{
|
|
|
32
32
|
disabled?: boolean;
|
|
33
33
|
|
|
34
34
|
itemRef?: RefObject<HTMLButtonElement>;
|
|
35
|
-
|
|
35
|
+
className?: string;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
inactive?: boolean;
|
|
38
38
|
switch?: boolean;
|
|
39
|
-
}
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
type BaseItemsWithoutNonGroupProps = Omit<BaseItemProps, 'switch' | 'inactive'>;
|
|
40
42
|
|
|
41
43
|
// eslint-disable-next-line no-use-before-define
|
|
42
|
-
export type ItemProps =
|
|
44
|
+
export type ItemProps = BaseItemProps | AccordionItemProps | NextListItemProps | GroupItemProps;
|
|
43
45
|
|
|
44
|
-
export type AccordionItemProps =
|
|
46
|
+
export type AccordionItemProps = BaseItemsWithoutNonGroupProps & {
|
|
45
47
|
items: ItemProps[];
|
|
46
48
|
// TODO: add later
|
|
47
49
|
// mode?: 'single' | 'multiple';
|
|
48
50
|
type: 'collapse';
|
|
49
51
|
};
|
|
50
52
|
|
|
51
|
-
export type NextListItemProps =
|
|
53
|
+
export type NextListItemProps = BaseItemsWithoutNonGroupProps & {
|
|
52
54
|
items: ItemProps[];
|
|
53
55
|
type: 'next-list';
|
|
54
56
|
placement?: 'right-start' | 'left-start' | 'left' | 'right' | 'left-end' | 'right-end';
|
|
55
57
|
search?: SearchState;
|
|
56
58
|
} & ScrollProps;
|
|
57
59
|
|
|
58
|
-
export type GroupItemProps = SeparatorProps & {
|
|
60
|
+
export type GroupItemProps = Omit<SeparatorProps, 'size'> & {
|
|
59
61
|
items: ItemProps[];
|
|
60
62
|
id?: string | number;
|
|
61
63
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import mergeRefs from 'merge-refs';
|
|
2
|
+
import { FocusEvent, forwardRef, KeyboardEvent, useMemo, useRef, useState } from 'react';
|
|
2
3
|
|
|
3
4
|
import { HiddenTabButton } from '../../../helperComponents';
|
|
4
5
|
import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';
|
|
@@ -10,7 +11,10 @@ import styles from '../styles.module.scss';
|
|
|
10
11
|
import { ListProps } from '../types';
|
|
11
12
|
|
|
12
13
|
export const List = forwardRef<HTMLElement, ListProps>(
|
|
13
|
-
(
|
|
14
|
+
(
|
|
15
|
+
{ items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, onKeyDown, tabIndex = 0, ...props },
|
|
16
|
+
ref,
|
|
17
|
+
) => {
|
|
14
18
|
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
15
19
|
|
|
16
20
|
const memorizedItems = useMemo(
|
|
@@ -68,6 +72,20 @@ export const List = forwardRef<HTMLElement, ListProps>(
|
|
|
68
72
|
|
|
69
73
|
const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
|
|
70
74
|
|
|
75
|
+
const mergedHandlerKeyDown = (e: KeyboardEvent<HTMLUListElement>) => {
|
|
76
|
+
onKeyDown?.(e);
|
|
77
|
+
handleListKeyDown?.(e);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleOnFocus = (e: FocusEvent<HTMLElement>) => {
|
|
81
|
+
if (
|
|
82
|
+
e.relatedTarget === null ||
|
|
83
|
+
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))
|
|
84
|
+
) {
|
|
85
|
+
resetActiveFocusIndex();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
71
89
|
return (
|
|
72
90
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
73
91
|
// @ts-ignore
|
|
@@ -96,22 +114,15 @@ export const List = forwardRef<HTMLElement, ListProps>(
|
|
|
96
114
|
<ListPrivate
|
|
97
115
|
{...props}
|
|
98
116
|
{...slicedItems}
|
|
99
|
-
ref={listRef}
|
|
100
|
-
onFocus={
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))
|
|
104
|
-
) {
|
|
105
|
-
resetActiveFocusIndex();
|
|
106
|
-
}
|
|
107
|
-
}}
|
|
108
|
-
onKeyDown={handleListKeyDown}
|
|
109
|
-
tabIndex={0}
|
|
117
|
+
ref={mergeRefs(ref, listRef)}
|
|
118
|
+
onFocus={handleOnFocus}
|
|
119
|
+
onKeyDown={mergedHandlerKeyDown}
|
|
120
|
+
tabIndex={tabIndex}
|
|
110
121
|
search={search}
|
|
111
122
|
nested={false}
|
|
112
123
|
/>
|
|
113
124
|
|
|
114
|
-
<HiddenTabButton ref={btnRef} listRef={listRef} />
|
|
125
|
+
<HiddenTabButton ref={btnRef} listRef={listRef} tabIndex={tabIndex} />
|
|
115
126
|
</div>
|
|
116
127
|
</ParentListContext.Provider>
|
|
117
128
|
</SelectionProvider>
|
|
@@ -29,6 +29,8 @@ export type ListProps = WithSupportProps<
|
|
|
29
29
|
noData?: string;
|
|
30
30
|
/** Текст для состояния "Отсутсвие результата" при поиске */
|
|
31
31
|
noResults?: string;
|
|
32
|
+
tabIndex?: number;
|
|
33
|
+
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
32
34
|
} & Pick<SelectionProviderProps, 'value' | 'defaultValue' | 'onChange' | 'selection'> &
|
|
33
35
|
ListContextType &
|
|
34
36
|
ScrollProps
|
|
@@ -40,7 +42,7 @@ export type DroplistProps = {
|
|
|
40
42
|
/** Триггер для дроплиста */
|
|
41
43
|
children?: ReactNode;
|
|
42
44
|
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> &
|
|
43
|
-
ListProps
|
|
45
|
+
Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
|
|
44
46
|
|
|
45
47
|
export type ListPrivateProps = ListProps & {
|
|
46
48
|
nested?: boolean;
|
|
@@ -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)) {
|