@sima-land/ui-nucleons 44.0.1 → 44.1.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/chips/chips.module.scss +7 -4
- package/expandable/expandable-group.module.scss +18 -0
- package/expandable/index.d.ts +30 -0
- package/expandable/index.js +78 -0
- package/expandable/utils.d.ts +20 -0
- package/expandable/utils.js +73 -0
- package/package.json +1 -1
package/chips/chips.module.scss
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
@use '../colors';
|
|
2
2
|
|
|
3
|
-
$gutter: 8px;
|
|
4
|
-
|
|
5
3
|
.list {
|
|
6
4
|
display: flex;
|
|
7
5
|
flex-wrap: wrap;
|
|
8
|
-
|
|
6
|
+
--chips-gutter: 8px;
|
|
7
|
+
margin-right: calc(-1 * var(--chips-gutter));
|
|
8
|
+
margin-bottom: calc(-1 * var(--chips-gutter));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
.item {
|
|
@@ -15,7 +15,10 @@ $gutter: 8px;
|
|
|
15
15
|
display: flex;
|
|
16
16
|
align-items: center;
|
|
17
17
|
height: 32px;
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
// по умолчанию 0 чтобы вне Chips отступов не было
|
|
20
|
+
margin-right: var(--chips-gutter, 0);
|
|
21
|
+
margin-bottom: var(--chips-gutter, 0);
|
|
19
22
|
overflow: hidden;
|
|
20
23
|
outline: 0;
|
|
21
24
|
border: 0;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { ReactNode, MouseEventHandler, CSSProperties } from 'react';
|
|
2
|
+
export interface ExpandableGroupProps {
|
|
3
|
+
gap?: number;
|
|
4
|
+
itemHeight?: number;
|
|
5
|
+
defaultExpanded?: boolean;
|
|
6
|
+
lineLimit?: number;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
opener?: (data: {
|
|
9
|
+
hiddenCount: number;
|
|
10
|
+
}) => ReactNode;
|
|
11
|
+
onExpand?: () => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
export interface GroupItemProps {
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
onClick?: MouseEventHandler;
|
|
18
|
+
'data-testid'?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Группа элементов с ограничением на число выводимых строк и возможностью показать все.
|
|
22
|
+
* @param props Свойства.
|
|
23
|
+
* @return Элемент.
|
|
24
|
+
*/
|
|
25
|
+
declare function ExpandableGroup({ gap, itemHeight, lineLimit, defaultExpanded, children, onExpand, opener, className, style, }: ExpandableGroupProps): JSX.Element;
|
|
26
|
+
export declare const Expandable: {
|
|
27
|
+
readonly Group: typeof ExpandableGroup;
|
|
28
|
+
readonly Item: React.ForwardRefExoticComponent<GroupItemProps & React.RefAttributes<HTMLDivElement>>;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.Expandable = void 0;
|
|
30
|
+
const react_1 = __importStar(require("react"));
|
|
31
|
+
const utils_1 = require("./utils");
|
|
32
|
+
const bind_1 = __importDefault(require("classnames/bind"));
|
|
33
|
+
const expandable_group_module_scss_1 = __importDefault(require("./expandable-group.module.scss"));
|
|
34
|
+
const cx = bind_1.default.bind(expandable_group_module_scss_1.default);
|
|
35
|
+
const GroupContext = (0, react_1.createContext)({ gap: 8, itemHeight: 32 });
|
|
36
|
+
const ItemContext = (0, react_1.createContext)({});
|
|
37
|
+
/**
|
|
38
|
+
* Группа элементов с ограничением на число выводимых строк и возможностью показать все.
|
|
39
|
+
* @param props Свойства.
|
|
40
|
+
* @return Элемент.
|
|
41
|
+
*/
|
|
42
|
+
function ExpandableGroup({ gap = 8, itemHeight = 32, lineLimit = 2, defaultExpanded = false, children, onExpand, opener = data => react_1.default.createElement(react_1.default.Fragment, null,
|
|
43
|
+
"\u0415\u0449\u0451 ",
|
|
44
|
+
data.hiddenCount), className, style, }) {
|
|
45
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
46
|
+
const openerRef = (0, react_1.useRef)(null);
|
|
47
|
+
const [expanded, setExpanded] = (0, react_1.useState)(defaultExpanded);
|
|
48
|
+
const viewState = (0, utils_1.useViewState)(containerRef, openerRef);
|
|
49
|
+
const items = react_1.Children.toArray(children).reduce((result, child, index) => {
|
|
50
|
+
const needHideItems = viewState.lastVisibleIndex !== -1 && !expanded;
|
|
51
|
+
const isDisplayed = !needHideItems || index < viewState.lastVisibleIndex;
|
|
52
|
+
(0, react_1.isValidElement)(child) &&
|
|
53
|
+
child.type === ExpandableItem &&
|
|
54
|
+
result.push(react_1.default.createElement(ItemContext.Provider, { key: index, value: { hidden: !isDisplayed } }, (0, react_1.cloneElement)(child, { hidden: !isDisplayed })));
|
|
55
|
+
return result;
|
|
56
|
+
}, []);
|
|
57
|
+
return (react_1.default.createElement(GroupContext.Provider, { value: { gap, itemHeight } },
|
|
58
|
+
react_1.default.createElement("div", { ref: containerRef, className: cx('root', className), style: Object.assign(Object.assign({}, style), { maxHeight: expanded ? undefined : `${itemHeight * lineLimit + gap * (lineLimit - 1)}px` }) },
|
|
59
|
+
react_1.default.createElement("div", { className: expandable_group_module_scss_1.default.inner, style: { marginRight: `${-gap}px` } },
|
|
60
|
+
items,
|
|
61
|
+
!expanded && (react_1.default.createElement(ItemContext.Provider, { value: { invisible: viewState.lastVisibleIndex === -1 } },
|
|
62
|
+
react_1.default.createElement(ExpandableItem, { ref: openerRef, onClick: () => {
|
|
63
|
+
setExpanded(true);
|
|
64
|
+
onExpand === null || onExpand === void 0 ? void 0 : onExpand();
|
|
65
|
+
}, "data-testid": 'expandable:opener' }, opener({ hiddenCount: items.length - viewState.lastVisibleIndex }))))))));
|
|
66
|
+
}
|
|
67
|
+
const ExpandableItem = (0, react_1.forwardRef)(({ children, onClick, 'data-testid': testId = 'expandable:item' }, ref) => {
|
|
68
|
+
const { gap, itemHeight } = (0, react_1.useContext)(GroupContext);
|
|
69
|
+
const { invisible, hidden } = (0, react_1.useContext)(ItemContext);
|
|
70
|
+
return (react_1.default.createElement("div", { ref: ref, className: cx('item', { hidden, invisible }), "data-testid": testId, onClick: onClick, style: {
|
|
71
|
+
height: `${itemHeight}px`,
|
|
72
|
+
margin: `0 ${gap}px ${gap}px 0`,
|
|
73
|
+
} }, children));
|
|
74
|
+
});
|
|
75
|
+
exports.Expandable = {
|
|
76
|
+
Group: ExpandableGroup,
|
|
77
|
+
Item: ExpandableItem,
|
|
78
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export interface ViewState {
|
|
3
|
+
phase: 'default' | 'ready';
|
|
4
|
+
lastVisibleIndex: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const initialViewState: ViewState;
|
|
7
|
+
/**
|
|
8
|
+
* Хук состояния группы элементов ограниченных несколькими строками с возможностью показать все.
|
|
9
|
+
* @param containerRef Ref элемента-контейнера.
|
|
10
|
+
* @param openerRef Ref элемента кнопки, показывающей всю группу.
|
|
11
|
+
* @return Состояние.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useViewState(containerRef: RefObject<HTMLElement>, openerRef: RefObject<HTMLElement>): ViewState;
|
|
14
|
+
/**
|
|
15
|
+
* Определяет индекс последнего видимого элемента для свернутого списка.
|
|
16
|
+
* @param containerRef Ref элемента-контейнера.
|
|
17
|
+
* @param openerRef Ref элемента кнопки, показывающей весь список.
|
|
18
|
+
* @return Состояние.
|
|
19
|
+
*/
|
|
20
|
+
export declare function defineViewState(containerRef: RefObject<HTMLElement>, openerRef: RefObject<HTMLElement>): ViewState;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.defineViewState = exports.useViewState = exports.initialViewState = void 0;
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const hooks_1 = require("../hooks");
|
|
9
|
+
const on_1 = __importDefault(require("../helpers/on"));
|
|
10
|
+
exports.initialViewState = {
|
|
11
|
+
phase: 'default',
|
|
12
|
+
lastVisibleIndex: -1,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Хук состояния группы элементов ограниченных несколькими строками с возможностью показать все.
|
|
16
|
+
* @param containerRef Ref элемента-контейнера.
|
|
17
|
+
* @param openerRef Ref элемента кнопки, показывающей всю группу.
|
|
18
|
+
* @return Состояние.
|
|
19
|
+
*/
|
|
20
|
+
function useViewState(containerRef, openerRef) {
|
|
21
|
+
const [viewState, setViewState] = (0, react_1.useState)(exports.initialViewState);
|
|
22
|
+
(0, react_1.useEffect)(() => {
|
|
23
|
+
const off = (0, on_1.default)(window, 'resize', () => {
|
|
24
|
+
setViewState(exports.initialViewState);
|
|
25
|
+
});
|
|
26
|
+
return off;
|
|
27
|
+
}, []);
|
|
28
|
+
(0, hooks_1.useIsomorphicLayoutEffect)(() => {
|
|
29
|
+
if (viewState.phase === 'default') {
|
|
30
|
+
setViewState(defineViewState(containerRef, openerRef));
|
|
31
|
+
}
|
|
32
|
+
}, [viewState.phase, containerRef, openerRef]);
|
|
33
|
+
return viewState;
|
|
34
|
+
}
|
|
35
|
+
exports.useViewState = useViewState;
|
|
36
|
+
/**
|
|
37
|
+
* Определяет индекс последнего видимого элемента для свернутого списка.
|
|
38
|
+
* @param containerRef Ref элемента-контейнера.
|
|
39
|
+
* @param openerRef Ref элемента кнопки, показывающей весь список.
|
|
40
|
+
* @return Состояние.
|
|
41
|
+
*/
|
|
42
|
+
function defineViewState(containerRef, openerRef) {
|
|
43
|
+
const state = {
|
|
44
|
+
phase: 'ready',
|
|
45
|
+
lastVisibleIndex: -1,
|
|
46
|
+
};
|
|
47
|
+
const container = containerRef.current;
|
|
48
|
+
const opener = openerRef.current;
|
|
49
|
+
if (container && opener && opener.parentNode) {
|
|
50
|
+
const parentRect = container.getBoundingClientRect();
|
|
51
|
+
const childList = [...opener.parentNode.children];
|
|
52
|
+
const firstHiddenNodeIndex = childList
|
|
53
|
+
.filter(child => child !== opener)
|
|
54
|
+
.findIndex(child => {
|
|
55
|
+
const childRect = child.getBoundingClientRect();
|
|
56
|
+
return childRect.top - parentRect.top >= parentRect.height;
|
|
57
|
+
});
|
|
58
|
+
if (firstHiddenNodeIndex !== -1) {
|
|
59
|
+
const lastVisibleIndex = firstHiddenNodeIndex - 1;
|
|
60
|
+
const lastVisible = childList[lastVisibleIndex];
|
|
61
|
+
if (lastVisible) {
|
|
62
|
+
const rightBound = lastVisible.getBoundingClientRect().right;
|
|
63
|
+
const rightContainerBound = container.getBoundingClientRect().right;
|
|
64
|
+
// проверяем, хватает ли кнопке места (с запасом в половину) после последнего видимого дочернего элемента
|
|
65
|
+
const isOpenerFit = rightContainerBound - rightBound >= opener.clientWidth * 1.5;
|
|
66
|
+
// если хватает места - ставим кнопку после последнего, иначе - вместо
|
|
67
|
+
state.lastVisibleIndex = lastVisibleIndex + Number(isOpenerFit);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
exports.defineViewState = defineViewState;
|