@snack-uikit/breadcrumbs 0.4.14-preview-104ad22c.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 +286 -0
- package/LICENSE +201 -0
- package/README.md +40 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +33 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.js +49 -0
- package/dist/components/Breadcrumbs/index.d.ts +1 -0
- package/dist/components/Breadcrumbs/index.js +1 -0
- package/dist/components/Collapse/Collapse.d.ts +6 -0
- package/dist/components/Collapse/Collapse.js +23 -0
- package/dist/components/Collapse/index.d.ts +1 -0
- package/dist/components/Collapse/index.js +1 -0
- package/dist/components/Collapse/styles.module.css +18 -0
- package/dist/components/Crumb/Crumb.d.ts +13 -0
- package/dist/components/Crumb/Crumb.js +50 -0
- package/dist/components/Crumb/index.d.ts +1 -0
- package/dist/components/Crumb/index.js +1 -0
- package/dist/components/Crumb/styles.module.css +55 -0
- package/dist/components/CrumbsTypography/CrumbsTypography.d.ts +7 -0
- package/dist/components/CrumbsTypography/CrumbsTypography.js +6 -0
- package/dist/components/CrumbsTypography/index.d.ts +1 -0
- package/dist/components/CrumbsTypography/index.js +1 -0
- package/dist/components/HiddenChain/HiddenChain.d.ts +11 -0
- package/dist/components/HiddenChain/HiddenChain.js +27 -0
- package/dist/components/HiddenChain/hooks.d.ts +6 -0
- package/dist/components/HiddenChain/hooks.js +28 -0
- package/dist/components/HiddenChain/index.d.ts +1 -0
- package/dist/components/HiddenChain/index.js +1 -0
- package/dist/components/Separator/Separator.d.ts +1 -0
- package/dist/components/Separator/Separator.js +11 -0
- package/dist/components/Separator/index.d.ts +1 -0
- package/dist/components/Separator/index.js +1 -0
- package/dist/components/Separator/styles.module.css +15 -0
- package/dist/components/Wrapper/Wrapper.d.ts +19 -0
- package/dist/components/Wrapper/Wrapper.js +21 -0
- package/dist/components/Wrapper/index.d.ts +1 -0
- package/dist/components/Wrapper/index.js +1 -0
- package/dist/components/Wrapper/styles.module.css +19 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/styles.module.css +3 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +19 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.js +7 -0
- package/dist/hooks.d.ts +21 -0
- package/dist/hooks.js +78 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +1 -0
- package/dist/utils/buildBreadcrumbsConfigs.d.ts +2 -0
- package/dist/utils/buildBreadcrumbsConfigs.js +77 -0
- package/dist/utils/buildSizeMap.d.ts +2 -0
- package/dist/utils/buildSizeMap.js +26 -0
- package/dist/utils/getMaxPossibleWidth.d.ts +6 -0
- package/dist/utils/getMaxPossibleWidth.js +16 -0
- package/dist/utils/getTestId.d.ts +1 -0
- package/dist/utils/getTestId.js +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +4 -0
- package/package.json +42 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +112 -0
- package/src/components/Breadcrumbs/index.ts +1 -0
- package/src/components/Collapse/Collapse.tsx +52 -0
- package/src/components/Collapse/index.ts +1 -0
- package/src/components/Collapse/styles.module.scss +22 -0
- package/src/components/Crumb/Crumb.tsx +118 -0
- package/src/components/Crumb/index.ts +1 -0
- package/src/components/Crumb/styles.module.scss +66 -0
- package/src/components/CrumbsTypography/CrumbsTypography.tsx +24 -0
- package/src/components/CrumbsTypography/index.ts +1 -0
- package/src/components/HiddenChain/HiddenChain.tsx +54 -0
- package/src/components/HiddenChain/hooks.tsx +41 -0
- package/src/components/HiddenChain/index.ts +1 -0
- package/src/components/Separator/Separator.tsx +23 -0
- package/src/components/Separator/index.ts +1 -0
- package/src/components/Separator/styles.module.scss +16 -0
- package/src/components/Wrapper/Wrapper.tsx +38 -0
- package/src/components/Wrapper/index.ts +1 -0
- package/src/components/Wrapper/styles.module.scss +26 -0
- package/src/components/index.ts +1 -0
- package/src/components/styles.module.scss +3 -0
- package/src/constants.ts +19 -0
- package/src/context.ts +16 -0
- package/src/hooks.tsx +99 -0
- package/src/index.ts +2 -0
- package/src/types.ts +60 -0
- package/src/utils/buildBreadcrumbsConfigs.ts +116 -0
- package/src/utils/buildSizeMap.ts +33 -0
- package/src/utils/getMaxPossibleWidth.ts +17 -0
- package/src/utils/getTestId.ts +1 -0
- package/src/utils/index.ts +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
3
|
+
import { ItemRenderMode } from '../../constants';
|
|
4
|
+
import { Item } from '../../types';
|
|
5
|
+
export type CrumbProps = WithSupportProps<{
|
|
6
|
+
renderMode: ItemRenderMode;
|
|
7
|
+
className?: string;
|
|
8
|
+
minWidth?: number;
|
|
9
|
+
current?: boolean;
|
|
10
|
+
item: Item;
|
|
11
|
+
useIconOnly?: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const Crumb: import("react").NamedExoticComponent<CrumbProps>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import { memo, useContext } from 'react';
|
|
15
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
16
|
+
import { ElementType, ItemRenderMode } from '../../constants';
|
|
17
|
+
import { BreadcrumbsContext } from '../../context';
|
|
18
|
+
import { getTestId } from '../../utils';
|
|
19
|
+
import { CrumbsTypography } from '../CrumbsTypography';
|
|
20
|
+
import styles from './styles.module.css';
|
|
21
|
+
const ELLIPSIS_LABEL_LENGTH = 8;
|
|
22
|
+
export const Crumb = memo(function Crumb(_a) {
|
|
23
|
+
var { minWidth: minWidthProp = 0, current = false, renderMode, className, item: { label: labelProp, shortLabel, icon: Icon, id, onClick, href }, useIconOnly: useIconOnlyProp } = _a, rest = __rest(_a, ["minWidth", "current", "renderMode", "className", "item", "useIconOnly"]);
|
|
24
|
+
const { hidden, size, testId } = useContext(BreadcrumbsContext);
|
|
25
|
+
const isLabelShouldBeEllipse = hidden && labelProp.length > ELLIPSIS_LABEL_LENGTH;
|
|
26
|
+
const label = isLabelShouldBeEllipse && renderMode === ItemRenderMode.Ellipsis ? `${labelProp.substring(0, 4)}...` : labelProp;
|
|
27
|
+
const useIconOnly = Boolean(Icon && useIconOnlyProp);
|
|
28
|
+
const minWidth = minWidthProp && renderMode === ItemRenderMode.Ellipsis ? minWidthProp : 'auto';
|
|
29
|
+
const dataAttributes = {
|
|
30
|
+
'data-render-mode': renderMode,
|
|
31
|
+
'data-current': current,
|
|
32
|
+
'data-hidden': hidden,
|
|
33
|
+
'data-size': size,
|
|
34
|
+
'data-id': id,
|
|
35
|
+
};
|
|
36
|
+
const content = (_jsxs(_Fragment, { children: [Icon && (_jsx("div", Object.assign({ className: styles.icon }, { children: _jsx(Icon, { size: 24 }) }))), !useIconOnly && (_jsx(CrumbsTypography, Object.assign({ size: size, className: styles.label }, { children: renderMode === ItemRenderMode.ShortLabel ? shortLabel : label })))] }));
|
|
37
|
+
const tabIndex = hidden ? { tabIndex: -1 } : {};
|
|
38
|
+
const title = renderMode === ItemRenderMode.Ellipsis ? label : '';
|
|
39
|
+
let crumb = null;
|
|
40
|
+
if (href) {
|
|
41
|
+
crumb = (_jsx("a", Object.assign({ className: styles.crumb, onClick: onClick, href: href, "aria-label": useIconOnly ? label : undefined, "data-clickable": true }, dataAttributes, tabIndex, { children: content })));
|
|
42
|
+
}
|
|
43
|
+
else if (onClick) {
|
|
44
|
+
crumb = (_jsx("button", Object.assign({ title: title, onClick: onClick, className: styles.crumb, "aria-label": useIconOnly ? label : undefined, "data-clickable": true }, dataAttributes, tabIndex, { children: content })));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
crumb = (_jsx("div", Object.assign({ className: styles.crumb }, dataAttributes, { children: content })));
|
|
48
|
+
}
|
|
49
|
+
return (_jsx("li", Object.assign({}, extractSupportProps(rest), dataAttributes, { "data-test-id": getTestId('crumb', testId), className: cn(styles.wrapper, className), "data-element-type": ElementType.Item, style: { minWidth }, title: title }, { children: crumb })));
|
|
50
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Crumb';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Crumb';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.wrapper{
|
|
2
|
+
flex-shrink:0;
|
|
3
|
+
list-style:none;
|
|
4
|
+
}
|
|
5
|
+
.wrapper[data-render-mode=ellipsis]{
|
|
6
|
+
flex-shrink:1;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.crumb, a.crumb{
|
|
10
|
+
display:flex;
|
|
11
|
+
align-items:center;
|
|
12
|
+
width:100%;
|
|
13
|
+
margin:0;
|
|
14
|
+
padding:0;
|
|
15
|
+
color:var(--sys-neutral-text-light, #898989);
|
|
16
|
+
text-decoration:none;
|
|
17
|
+
background:none;
|
|
18
|
+
border:none;
|
|
19
|
+
}
|
|
20
|
+
.crumb[data-clickable], a.crumb[data-clickable]{
|
|
21
|
+
cursor:pointer;
|
|
22
|
+
}
|
|
23
|
+
.crumb[data-clickable]:focus-visible, .crumb[data-clickable]:hover, a.crumb[data-clickable]:focus-visible, a.crumb[data-clickable]:hover{
|
|
24
|
+
color:var(--sys-neutral-text-support, #565656);
|
|
25
|
+
}
|
|
26
|
+
.crumb[data-current=true], a.crumb[data-current=true]{
|
|
27
|
+
color:var(--sys-neutral-text-support, #565656);
|
|
28
|
+
}
|
|
29
|
+
.crumb:focus-visible, a.crumb:focus-visible{
|
|
30
|
+
outline-width:var(--border-state-focus-s-border-width, 2px);
|
|
31
|
+
outline-style:var(--border-state-focus-s-border-style, solid);
|
|
32
|
+
outline-color:var(--border-state-focus-s-border-color, );
|
|
33
|
+
outline-color:var(--sys-available-complementary, #131313);
|
|
34
|
+
}
|
|
35
|
+
.crumb[data-size=xs], a.crumb[data-size=xs]{
|
|
36
|
+
height:var(--size-breadcrumbs-xs, 24px);
|
|
37
|
+
gap:var(--space-breadcrumbs-crumb-xs-gap, 2px);
|
|
38
|
+
}
|
|
39
|
+
.crumb[data-size=s], a.crumb[data-size=s]{
|
|
40
|
+
height:var(--size-breadcrumbs-s, 24px);
|
|
41
|
+
gap:var(--space-breadcrumbs-crumb-s-gap, 2px);
|
|
42
|
+
}
|
|
43
|
+
.crumb .icon, a.crumb .icon{
|
|
44
|
+
display:flex;
|
|
45
|
+
flex-shrink:0;
|
|
46
|
+
align-items:center;
|
|
47
|
+
width:-moz-fit-content;
|
|
48
|
+
width:fit-content;
|
|
49
|
+
}
|
|
50
|
+
.crumb .label, a.crumb .label{
|
|
51
|
+
overflow:hidden;
|
|
52
|
+
width:100%;
|
|
53
|
+
text-overflow:ellipsis;
|
|
54
|
+
white-space:nowrap;
|
|
55
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { Size } from '../../constants';
|
|
3
|
+
export type CrumbsTypographyProps = PropsWithChildren<{
|
|
4
|
+
size: Size;
|
|
5
|
+
className?: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function CrumbsTypography({ size, children, className }: CrumbsTypographyProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Typography } from '@snack-uikit/typography';
|
|
3
|
+
import { Size } from '../../constants';
|
|
4
|
+
export function CrumbsTypography({ size, children, className }) {
|
|
5
|
+
return (_jsx(Typography, Object.assign({ className: className, size: size === Size.S ? Typography.sizes.L : Typography.sizes.M, tag: Typography.tags.div, family: Typography.families.Sans, role: Typography.roles.Body }, { children: children })));
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CrumbsTypography';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CrumbsTypography';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Size } from '../../constants';
|
|
3
|
+
import { BreadcrumbsConfig, Item } from '../../types';
|
|
4
|
+
export type HiddenChainProps = {
|
|
5
|
+
separator: string;
|
|
6
|
+
items: Item[];
|
|
7
|
+
size: Size;
|
|
8
|
+
onConfigsBuilt(config: BreadcrumbsConfig[]): void;
|
|
9
|
+
firstItemIconOnly?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare const HiddenChain: import("react").NamedExoticComponent<HiddenChainProps>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useEffect, useRef } from 'react';
|
|
3
|
+
import { buildBreadcrumbsConfigs, buildSizeMap } from '../../utils';
|
|
4
|
+
import { Collapse } from '../Collapse';
|
|
5
|
+
import { Separator } from '../Separator';
|
|
6
|
+
import { Wrapper } from '../Wrapper';
|
|
7
|
+
import { useItemModesRender } from './hooks';
|
|
8
|
+
export const HiddenChain = memo(function HiddenChain({ size, separator, items, onConfigsBuilt, firstItemIconOnly = false, }) {
|
|
9
|
+
const containerRef = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const container = containerRef.current;
|
|
12
|
+
if (container) {
|
|
13
|
+
const observer = new ResizeObserver(([{ target }]) => {
|
|
14
|
+
const sizeMap = buildSizeMap(target);
|
|
15
|
+
if (sizeMap) {
|
|
16
|
+
onConfigsBuilt(buildBreadcrumbsConfigs(items, sizeMap));
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
observer.observe(container);
|
|
20
|
+
return () => {
|
|
21
|
+
observer.disconnect();
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}, [items, onConfigsBuilt]);
|
|
25
|
+
const renderItemModes = useItemModesRender({ firstItemIconOnly });
|
|
26
|
+
return (_jsxs(Wrapper, Object.assign({ size: size, hidden: true, ref: containerRef, separator: separator, "data-test-id": 'hidden-wrapper' }, { children: [_jsx(Separator, {}), _jsx(Collapse, { currentConfig: [] }), items.map(renderItemModes)] })));
|
|
27
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Item } from '../../types';
|
|
2
|
+
type UseItemModesRenderProps = {
|
|
3
|
+
firstItemIconOnly: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare const useItemModesRender: ({ firstItemIconOnly }: UseItemModesRenderProps) => (item: Item, index: number) => (import("react/jsx-runtime").JSX.Element | null)[];
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { ItemRenderMode } from '../../constants';
|
|
4
|
+
import { Crumb } from '../Crumb';
|
|
5
|
+
/**
|
|
6
|
+
* Варианты отображения для обычного айтема
|
|
7
|
+
*/
|
|
8
|
+
const NORMAL_MODES = [
|
|
9
|
+
[ItemRenderMode.ShortLabel, 'shortLabel'],
|
|
10
|
+
[ItemRenderMode.Ellipsis, 'label'],
|
|
11
|
+
[ItemRenderMode.Full, 'label'],
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Варианты отображения айтемов состоящих из одной иконки
|
|
15
|
+
*/
|
|
16
|
+
const ICON_ONLY_MODES = [[ItemRenderMode.Full, 'label']];
|
|
17
|
+
const getItemModesRenderer = (firstItemIconOnly) => (item, index) => {
|
|
18
|
+
const isIconOnlyMode = !index && firstItemIconOnly;
|
|
19
|
+
const modes = isIconOnlyMode ? ICON_ONLY_MODES : NORMAL_MODES;
|
|
20
|
+
return modes.map(([mode, labelProp]) => {
|
|
21
|
+
const label = item[labelProp];
|
|
22
|
+
if (!label) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return _jsx(Crumb, { useIconOnly: isIconOnlyMode, renderMode: mode, item: item }, item.id + mode);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
export const useItemModesRender = ({ firstItemIconOnly }) => useMemo(() => getItemModesRenderer(firstItemIconOnly), [firstItemIconOnly]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './HiddenChain';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './HiddenChain';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Separator(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { ElementType } from '../../constants';
|
|
4
|
+
import { BreadcrumbsContext } from '../../context';
|
|
5
|
+
import { getTestId } from '../../utils';
|
|
6
|
+
import { CrumbsTypography } from '../CrumbsTypography';
|
|
7
|
+
import styles from './styles.module.css';
|
|
8
|
+
export function Separator() {
|
|
9
|
+
const { size, separator, testId } = useContext(BreadcrumbsContext);
|
|
10
|
+
return (_jsx("div", Object.assign({ "aria-hidden": true, "data-size": size, className: styles.separator, "data-test-id": getTestId('separator', testId), "data-element-type": ElementType.Separator }, { children: _jsx(CrumbsTypography, Object.assign({ size: size }, { children: separator })) })));
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Separator';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Separator';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.separator{
|
|
2
|
+
display:flex;
|
|
3
|
+
align-items:center;
|
|
4
|
+
color:var(--sys-neutral-text-disabled, #9e9e9e);
|
|
5
|
+
}
|
|
6
|
+
.separator[data-size=xs]{
|
|
7
|
+
height:var(--size-breadcrumbs-xs, 24px);
|
|
8
|
+
padding-left:var(--space-breadcrumbs-separator-padding-xs, 8px);
|
|
9
|
+
padding-right:var(--space-breadcrumbs-separator-padding-xs, 8px);
|
|
10
|
+
}
|
|
11
|
+
.separator[data-size=s]{
|
|
12
|
+
height:var(--size-breadcrumbs-s, 24px);
|
|
13
|
+
padding-left:var(--space-breadcrumbs-separator-padding-s, 8px);
|
|
14
|
+
padding-right:var(--space-breadcrumbs-separator-padding-s, 8px);
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { WithSupportProps } from '@snack-uikit/utils';
|
|
3
|
+
import { Size } from '../../constants';
|
|
4
|
+
export type WrapperProps = PropsWithChildren<WithSupportProps<{
|
|
5
|
+
hidden: boolean;
|
|
6
|
+
className?: string;
|
|
7
|
+
size: Size;
|
|
8
|
+
separator: string;
|
|
9
|
+
}>>;
|
|
10
|
+
export declare const Wrapper: import("react").ForwardRefExoticComponent<{
|
|
11
|
+
'data-test-id'?: string | undefined;
|
|
12
|
+
} & import("react").AriaAttributes & {
|
|
13
|
+
hidden: boolean;
|
|
14
|
+
className?: string | undefined;
|
|
15
|
+
size: Size;
|
|
16
|
+
separator: string;
|
|
17
|
+
} & {
|
|
18
|
+
children?: import("react").ReactNode;
|
|
19
|
+
} & import("react").RefAttributes<HTMLUListElement>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import cn from 'classnames';
|
|
14
|
+
import { forwardRef } from 'react';
|
|
15
|
+
import { extractSupportProps } from '@snack-uikit/utils';
|
|
16
|
+
import { BreadcrumbsContext } from '../../context';
|
|
17
|
+
import styles from './styles.module.css';
|
|
18
|
+
export const Wrapper = forwardRef(function HiddenWrapper(_a, ref) {
|
|
19
|
+
var { children, className, size, hidden, separator, 'data-test-id': testId } = _a, rest = __rest(_a, ["children", "className", "size", "hidden", "separator", 'data-test-id']);
|
|
20
|
+
return (_jsx("nav", Object.assign({}, extractSupportProps(rest), { className: cn(styles.wrapper, className), "data-test-id": testId, "data-hidden": hidden, "aria-hidden": hidden }, { children: _jsx(BreadcrumbsContext.Provider, Object.assign({ value: { hidden, size, separator, testId } }, { children: _jsx("ul", Object.assign({ className: styles.row, ref: ref, "data-hidden": hidden, "data-size": size }, { children: children })) })) })));
|
|
21
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Wrapper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Wrapper';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.wrapper{
|
|
2
|
+
padding:var(--space-breadcrumbs-container, 2px);
|
|
3
|
+
overflow:hidden;
|
|
4
|
+
}
|
|
5
|
+
.wrapper[data-hidden=true]{
|
|
6
|
+
position:absolute;
|
|
7
|
+
width:1px;
|
|
8
|
+
height:1px;
|
|
9
|
+
opacity:0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.row{
|
|
13
|
+
display:flex;
|
|
14
|
+
margin:0;
|
|
15
|
+
padding:0;
|
|
16
|
+
}
|
|
17
|
+
.row[data-hidden=true]{
|
|
18
|
+
display:inline-flex;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Breadcrumbs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Breadcrumbs';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SEPARATOR = "\u203A";
|
|
2
|
+
export declare enum ItemRenderMode {
|
|
3
|
+
Full = "full",
|
|
4
|
+
ShortLabel = "shortLabel",
|
|
5
|
+
Ellipsis = "ellipsis",
|
|
6
|
+
Collapsed = "collapsed"
|
|
7
|
+
}
|
|
8
|
+
export declare enum ElementType {
|
|
9
|
+
Separator = "separator",
|
|
10
|
+
Collapse = "collapse",
|
|
11
|
+
Item = "item"
|
|
12
|
+
}
|
|
13
|
+
export declare enum Size {
|
|
14
|
+
Xs = "xs",
|
|
15
|
+
S = "s"
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const SEPARATOR = '›';
|
|
2
|
+
export var ItemRenderMode;
|
|
3
|
+
(function (ItemRenderMode) {
|
|
4
|
+
ItemRenderMode["Full"] = "full";
|
|
5
|
+
ItemRenderMode["ShortLabel"] = "shortLabel";
|
|
6
|
+
ItemRenderMode["Ellipsis"] = "ellipsis";
|
|
7
|
+
ItemRenderMode["Collapsed"] = "collapsed";
|
|
8
|
+
})(ItemRenderMode || (ItemRenderMode = {}));
|
|
9
|
+
export var ElementType;
|
|
10
|
+
(function (ElementType) {
|
|
11
|
+
ElementType["Separator"] = "separator";
|
|
12
|
+
ElementType["Collapse"] = "collapse";
|
|
13
|
+
ElementType["Item"] = "item";
|
|
14
|
+
})(ElementType || (ElementType = {}));
|
|
15
|
+
export var Size;
|
|
16
|
+
(function (Size) {
|
|
17
|
+
Size["Xs"] = "xs";
|
|
18
|
+
Size["S"] = "s";
|
|
19
|
+
})(Size || (Size = {}));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Size } from './constants';
|
|
3
|
+
type BreadcrumbsContextValue = {
|
|
4
|
+
hidden: boolean;
|
|
5
|
+
size: Size;
|
|
6
|
+
separator: string;
|
|
7
|
+
testId?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const BreadcrumbsContext: import("react").Context<BreadcrumbsContextValue>;
|
|
10
|
+
export {};
|
package/dist/context.js
ADDED
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { BreadcrumbsConfig, CurrentConfigState } from './types';
|
|
3
|
+
export type BreadcrumbsLayout = {
|
|
4
|
+
setConfigs: (configs: BreadcrumbsConfig[]) => void;
|
|
5
|
+
currentConfig?: CurrentConfigState;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Расчет способа отображения breadcrumbs работает следующим образом:
|
|
9
|
+
* Каждый item может быть отображен в одном из режимов:
|
|
10
|
+
* full - целиком
|
|
11
|
+
* shortLabel - с использованием укороченного лейбла
|
|
12
|
+
* ellipsis - cо скруглением текста в ...
|
|
13
|
+
* collapse - спрятан в специальный узел "..." с тултипом.
|
|
14
|
+
* Для того чтоб эффективно распределить свободное по ширине пространство между айтемами в компоненте создается скрытый контейнер (HiddenChain),
|
|
15
|
+
* в котором рендерятся все айтемы во всех возможных способах отображения. Замеряется размер каждого из айтемов и записывается в sizeMap.
|
|
16
|
+
* Далее перебираются все возможные варианты отображения всей цепочки - конфиги (buildBreadcrumbsConfigs). У такого конфига есть два атрибута:
|
|
17
|
+
* width - суммарная ширина
|
|
18
|
+
* weight - вес, отображает общее количество сокращений, примененных к цепочке (например, отображение в режиме full - 0, сокращение shortLabel - +1, а ellipsis - +100 и тд)
|
|
19
|
+
* Для того чтоб отобразить breadcrumbs, нужно подобрать конфиг, максимальный по ширине, влезающий в контейнер, но с минимальным весом (наименьшим количеством сокращений).
|
|
20
|
+
*/
|
|
21
|
+
export declare function useBreadcrumbsLayout(containerRef: RefObject<HTMLElement>): BreadcrumbsLayout;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import debounce from 'lodash.debounce';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { getMaxPossibleWidth } from './utils';
|
|
4
|
+
const selectConfig = (containerWidth, configs) => {
|
|
5
|
+
if (!configs.length) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
let [bestConfig] = configs;
|
|
9
|
+
for (const config of configs) {
|
|
10
|
+
if (config.width <= containerWidth) {
|
|
11
|
+
if (bestConfig.width > containerWidth) {
|
|
12
|
+
bestConfig = config;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
if (bestConfig.weight > config.weight) {
|
|
16
|
+
bestConfig = config;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (bestConfig) {
|
|
22
|
+
return { chain: bestConfig.chain, containerWidth, chainWidth: bestConfig.width };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Расчет способа отображения breadcrumbs работает следующим образом:
|
|
27
|
+
* Каждый item может быть отображен в одном из режимов:
|
|
28
|
+
* full - целиком
|
|
29
|
+
* shortLabel - с использованием укороченного лейбла
|
|
30
|
+
* ellipsis - cо скруглением текста в ...
|
|
31
|
+
* collapse - спрятан в специальный узел "..." с тултипом.
|
|
32
|
+
* Для того чтоб эффективно распределить свободное по ширине пространство между айтемами в компоненте создается скрытый контейнер (HiddenChain),
|
|
33
|
+
* в котором рендерятся все айтемы во всех возможных способах отображения. Замеряется размер каждого из айтемов и записывается в sizeMap.
|
|
34
|
+
* Далее перебираются все возможные варианты отображения всей цепочки - конфиги (buildBreadcrumbsConfigs). У такого конфига есть два атрибута:
|
|
35
|
+
* width - суммарная ширина
|
|
36
|
+
* weight - вес, отображает общее количество сокращений, примененных к цепочке (например, отображение в режиме full - 0, сокращение shortLabel - +1, а ellipsis - +100 и тд)
|
|
37
|
+
* Для того чтоб отобразить breadcrumbs, нужно подобрать конфиг, максимальный по ширине, влезающий в контейнер, но с минимальным весом (наименьшим количеством сокращений).
|
|
38
|
+
*/
|
|
39
|
+
export function useBreadcrumbsLayout(containerRef) {
|
|
40
|
+
const [currentConfig, setCurrentConfig] = useState(undefined);
|
|
41
|
+
const [configs, setConfigs] = useState([]);
|
|
42
|
+
const selectConfigForWidth = useCallback((width) => selectConfig(width, configs), [configs]);
|
|
43
|
+
/**
|
|
44
|
+
* Подбор подходящего конфига триггерится изменением ширины контейнера и изменением набора конфигов
|
|
45
|
+
*/
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
var _a;
|
|
48
|
+
const visibleContainer = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
49
|
+
if (!visibleContainer) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (configs.length) {
|
|
53
|
+
setCurrentConfig(selectConfig(getMaxPossibleWidth(visibleContainer), configs));
|
|
54
|
+
}
|
|
55
|
+
}, [configs, containerRef]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
var _a;
|
|
58
|
+
const visibleContainer = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
59
|
+
if (!visibleContainer) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const reselectConfig = debounce(() => {
|
|
63
|
+
const width = getMaxPossibleWidth(visibleContainer);
|
|
64
|
+
setCurrentConfig(prevConfig => {
|
|
65
|
+
if ((prevConfig === null || prevConfig === void 0 ? void 0 : prevConfig.containerWidth) === width) {
|
|
66
|
+
return prevConfig;
|
|
67
|
+
}
|
|
68
|
+
const newConf = selectConfigForWidth(width);
|
|
69
|
+
return newConf || (prevConfig ? Object.assign(Object.assign({}, prevConfig), { containerWidth: width }) : prevConfig);
|
|
70
|
+
});
|
|
71
|
+
}, 100);
|
|
72
|
+
const visibleContainerObserver = new ResizeObserver(reselectConfig);
|
|
73
|
+
visibleContainerObserver.observe(visibleContainer);
|
|
74
|
+
visibleContainerObserver.observe(window.document.body);
|
|
75
|
+
return () => visibleContainerObserver.disconnect();
|
|
76
|
+
}, [containerRef, selectConfigForWidth]);
|
|
77
|
+
return { setConfigs, currentConfig };
|
|
78
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { JSXElementConstructor, MouseEventHandler } from 'react';
|
|
2
|
+
import { ElementType, ItemRenderMode } from './constants';
|
|
3
|
+
export type Item = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon?: JSXElementConstructor<{
|
|
7
|
+
size: number;
|
|
8
|
+
}>;
|
|
9
|
+
href?: string;
|
|
10
|
+
shortLabel?: string;
|
|
11
|
+
onClick?: MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>;
|
|
12
|
+
};
|
|
13
|
+
export type InnerItem = Item & {
|
|
14
|
+
renderMode: ItemRenderMode;
|
|
15
|
+
};
|
|
16
|
+
export type ItemSizeMap = {
|
|
17
|
+
[ItemRenderMode.ShortLabel]: number;
|
|
18
|
+
[ItemRenderMode.Collapsed]: number;
|
|
19
|
+
[ItemRenderMode.Ellipsis]: number;
|
|
20
|
+
[ItemRenderMode.Full]: number;
|
|
21
|
+
};
|
|
22
|
+
export type SizeMap = {
|
|
23
|
+
separator: number;
|
|
24
|
+
collapse: number;
|
|
25
|
+
items: {
|
|
26
|
+
[key: string]: ItemSizeMap;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type BreadcrumbsConfigChain = Array<{
|
|
30
|
+
element: ElementType.Collapse;
|
|
31
|
+
width: number;
|
|
32
|
+
} | {
|
|
33
|
+
element: ElementType.Separator;
|
|
34
|
+
width: number;
|
|
35
|
+
} | {
|
|
36
|
+
element: ElementType.Item;
|
|
37
|
+
width: number;
|
|
38
|
+
item: InnerItem;
|
|
39
|
+
}>;
|
|
40
|
+
export type CurrentConfigState = {
|
|
41
|
+
chain: BreadcrumbsConfigChain;
|
|
42
|
+
chainWidth: number;
|
|
43
|
+
containerWidth: number;
|
|
44
|
+
};
|
|
45
|
+
export type BreadcrumbsConfig = {
|
|
46
|
+
chain: BreadcrumbsConfigChain;
|
|
47
|
+
hasCollapsed: boolean;
|
|
48
|
+
weight: number;
|
|
49
|
+
width: number;
|
|
50
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { ItemRenderMode } from './constants';
|