@codacy/ui-components 0.67.41 → 0.67.43
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/lib/Breadcrumbs/Breadcrumbs.d.ts +5 -0
- package/lib/Breadcrumbs/Breadcrumbs.js +63 -0
- package/lib/Breadcrumbs/Breadcrumbs.styles.d.ts +5 -0
- package/lib/Breadcrumbs/Breadcrumbs.styles.js +34 -0
- package/lib/Breadcrumbs/Breadcrumbs.types.d.ts +15 -0
- package/lib/Breadcrumbs/Breadcrumbs.types.js +1 -0
- package/lib/Breadcrumbs/index.d.ts +1 -0
- package/lib/Breadcrumbs/index.js +1 -0
- package/lib/Breadcrumbs/useBreadcrumbsOverflow.d.ts +11 -0
- package/lib/Breadcrumbs/useBreadcrumbsOverflow.js +69 -0
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Caption } from '../Typography';
|
|
3
|
+
import { Flex } from '../Flexbox';
|
|
4
|
+
import { BreadcrumbsItemWrapper } from './Breadcrumbs.styles';
|
|
5
|
+
import { useBreadcrumbsOverflow, SEGMENT_ATTR } from './useBreadcrumbsOverflow';
|
|
6
|
+
const ELLIPSIS = Symbol('breadcrumbs-ellipsis');
|
|
7
|
+
function findChildren(ofType, children) {
|
|
8
|
+
return React.Children.toArray(children).filter(child => /*#__PURE__*/React.isValidElement(child) && child.type === ofType);
|
|
9
|
+
}
|
|
10
|
+
const BreadcrumbsItem = ({
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}) => {
|
|
14
|
+
return /*#__PURE__*/React.createElement(BreadcrumbsItemWrapper, props, children);
|
|
15
|
+
};
|
|
16
|
+
const BreadcrumbsList = ({
|
|
17
|
+
children,
|
|
18
|
+
separator,
|
|
19
|
+
size = 'sm',
|
|
20
|
+
itemMinWidth
|
|
21
|
+
}) => {
|
|
22
|
+
const breadcrumbItems = findChildren(BreadcrumbsItem, children);
|
|
23
|
+
const {
|
|
24
|
+
containerRef,
|
|
25
|
+
collapsed
|
|
26
|
+
} = useBreadcrumbsOverflow(breadcrumbItems.length, itemMinWidth);
|
|
27
|
+
|
|
28
|
+
// When collapsed, keep only the first and last segment and replace the
|
|
29
|
+
// middle with an ellipsis. Needs at least 3 segments to be worth collapsing.
|
|
30
|
+
const displayed = collapsed && breadcrumbItems.length > 2 ? [breadcrumbItems[0], ELLIPSIS, breadcrumbItems[breadcrumbItems.length - 1]] : breadcrumbItems;
|
|
31
|
+
return /*#__PURE__*/React.createElement(Flex, {
|
|
32
|
+
ref: containerRef,
|
|
33
|
+
overflow: "hidden"
|
|
34
|
+
}, displayed.map((item, index) => {
|
|
35
|
+
const isLast = index === displayed.length - 1;
|
|
36
|
+
const separatorNode = !isLast && /*#__PURE__*/React.createElement(Caption, {
|
|
37
|
+
size: size,
|
|
38
|
+
fontWeight: "medium",
|
|
39
|
+
color: "text-primary",
|
|
40
|
+
mx: 1,
|
|
41
|
+
flexShrink: 0
|
|
42
|
+
}, separator);
|
|
43
|
+
if (item === ELLIPSIS) {
|
|
44
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
45
|
+
key: "ellipsis"
|
|
46
|
+
}, /*#__PURE__*/React.createElement(Caption, {
|
|
47
|
+
size: size,
|
|
48
|
+
color: "text-secondary",
|
|
49
|
+
flexShrink: 0
|
|
50
|
+
}, "\u2026"), separatorNode);
|
|
51
|
+
}
|
|
52
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
53
|
+
key: index
|
|
54
|
+
}, /*#__PURE__*/React.cloneElement(item, {
|
|
55
|
+
current: isLast,
|
|
56
|
+
size: size,
|
|
57
|
+
[SEGMENT_ATTR]: ''
|
|
58
|
+
}), separatorNode);
|
|
59
|
+
}));
|
|
60
|
+
};
|
|
61
|
+
export const Breadcrumbs = Object.assign(BreadcrumbsList, {
|
|
62
|
+
Item: BreadcrumbsItem
|
|
63
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { BreadcrumbsItemProps } from './Breadcrumbs.types';
|
|
3
|
+
export declare const BreadcrumbsItemWrapper: import("@emotion/styled").StyledComponent<import("../Typography").CaptionProps & import("react").RefAttributes<HTMLParagraphElement> & {
|
|
4
|
+
theme?: import("@emotion/react").Theme | undefined;
|
|
5
|
+
} & BreadcrumbsItemProps, {}, {}>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import _styled from "@emotion/styled/base";
|
|
2
|
+
import { Caption } from '../Typography';
|
|
3
|
+
const fontSizeKeyBySize = {
|
|
4
|
+
lg: 'l',
|
|
5
|
+
md: 'm',
|
|
6
|
+
sm: 's',
|
|
7
|
+
xs: 'xs'
|
|
8
|
+
};
|
|
9
|
+
export const BreadcrumbsItemWrapper = /*#__PURE__*/_styled(Caption, process.env.NODE_ENV === "production" ? {
|
|
10
|
+
shouldForwardProp: prop => prop !== 'current',
|
|
11
|
+
target: "e1gjlqct0"
|
|
12
|
+
} : {
|
|
13
|
+
shouldForwardProp: prop => prop !== 'current',
|
|
14
|
+
target: "e1gjlqct0",
|
|
15
|
+
label: "codacy"
|
|
16
|
+
})("flex-shrink:1;", ({
|
|
17
|
+
theme,
|
|
18
|
+
size = 'sm'
|
|
19
|
+
}) => `
|
|
20
|
+
&,
|
|
21
|
+
& * {
|
|
22
|
+
font-size: ${theme.fontSizes[fontSizeKeyBySize[size]]} !important;
|
|
23
|
+
}
|
|
24
|
+
`, " ", ({
|
|
25
|
+
current,
|
|
26
|
+
theme
|
|
27
|
+
}) => current && `
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
&,
|
|
31
|
+
& * {
|
|
32
|
+
color: ${theme.colors['text-secondary']} !important;
|
|
33
|
+
}
|
|
34
|
+
`, ";" + (process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9CcmVhZGNydW1icy9CcmVhZGNydW1icy5zdHlsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBYXdCIiwiZmlsZSI6Ii4uLy4uL3NyYy9CcmVhZGNydW1icy9CcmVhZGNydW1icy5zdHlsZXMudHMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCdcbmltcG9ydCB7IEJyZWFkY3J1bWJzSXRlbVByb3BzIH0gZnJvbSAnLi9CcmVhZGNydW1icy50eXBlcydcbmltcG9ydCB7IENhcHRpb24sIFRleHRTaXplIH0gZnJvbSAnLi4vVHlwb2dyYXBoeSdcblxuY29uc3QgZm9udFNpemVLZXlCeVNpemU6IFJlY29yZDxUZXh0U2l6ZSwgJ2wnIHwgJ20nIHwgJ3MnIHwgJ3hzJz4gPSB7XG4gIGxnOiAnbCcsXG4gIG1kOiAnbScsXG4gIHNtOiAncycsXG4gIHhzOiAneHMnLFxufVxuXG5leHBvcnQgY29uc3QgQnJlYWRjcnVtYnNJdGVtV3JhcHBlciA9IHN0eWxlZChDYXB0aW9uLCB7XG4gIHNob3VsZEZvcndhcmRQcm9wOiBwcm9wID0+IHByb3AgIT09ICdjdXJyZW50Jyxcbn0pPEJyZWFkY3J1bWJzSXRlbVByb3BzPmBcbiAgZmxleC1zaHJpbms6IDE7XG5cbiAgJHsoeyB0aGVtZSwgc2l6ZSA9ICdzbScgfSkgPT4gYFxuICAgICYsXG4gICAgJiAqIHtcbiAgICAgIGZvbnQtc2l6ZTogJHt0aGVtZS5mb250U2l6ZXNbZm9udFNpemVLZXlCeVNpemVbc2l6ZV1dfSAhaW1wb3J0YW50O1xuICAgIH1cbiAgYH1cblxuICAkeyh7IGN1cnJlbnQsIHRoZW1lIH0pID0+XG4gICAgY3VycmVudCAmJlxuICAgIGBcbiAgICAgIGZsZXgtc2hyaW5rOiAwO1xuICAgICAgcG9pbnRlci1ldmVudHM6IG5vbmU7XG4gICAgICAmLFxuICAgICAgJiAqIHtcbiAgICAgICAgY29sb3I6ICR7dGhlbWUuY29sb3JzWyd0ZXh0LXNlY29uZGFyeSddfSAhaW1wb3J0YW50O1xuICAgICAgfVxuICAgIGB9XG5gXG4iXX0= */"));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { ColorProps, LayoutProps, SpaceProps, TypographyProps } from '../system-props/types';
|
|
3
|
+
import { ContainerComponentProps } from '../types';
|
|
4
|
+
import { TextSize } from '../Typography';
|
|
5
|
+
export interface BreadcrumbsProps extends ContainerComponentProps, SpaceProps, LayoutProps, ColorProps, TypographyProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
separator: string;
|
|
8
|
+
size?: TextSize;
|
|
9
|
+
itemMinWidth?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface BreadcrumbsItemProps extends ContainerComponentProps, SpaceProps, LayoutProps, ColorProps, TypographyProps {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
current?: boolean;
|
|
14
|
+
size?: TextSize;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Breadcrumbs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Breadcrumbs';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/** Marks the segment elements the hook should measure and floor. */
|
|
3
|
+
export declare const SEGMENT_ATTR = "data-breadcrumb-segment";
|
|
4
|
+
/**
|
|
5
|
+
* Detects whether a horizontal row of breadcrumb segments overflows its
|
|
6
|
+
* container and should collapse its middle segments
|
|
7
|
+
*/
|
|
8
|
+
export declare const useBreadcrumbsOverflow: (itemCount: number, itemMinWidth?: number) => {
|
|
9
|
+
containerRef: import("react").RefObject<HTMLDivElement>;
|
|
10
|
+
collapsed: boolean;
|
|
11
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { throttle } from 'lodash';
|
|
3
|
+
const FLOOR_REM = 4;
|
|
4
|
+
|
|
5
|
+
/** Marks the segment elements the hook should measure and floor. */
|
|
6
|
+
export const SEGMENT_ATTR = 'data-breadcrumb-segment';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detects whether a horizontal row of breadcrumb segments overflows its
|
|
10
|
+
* container and should collapse its middle segments
|
|
11
|
+
*/
|
|
12
|
+
export const useBreadcrumbsOverflow = (itemCount, itemMinWidth = FLOOR_REM) => {
|
|
13
|
+
const containerRef = useRef(null);
|
|
14
|
+
// Natural width of the row, cached while expanded so we can still decide
|
|
15
|
+
// whether it would fit again once we're already collapsed.
|
|
16
|
+
const naturalWidthRef = useRef(0);
|
|
17
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
18
|
+
// Latest `collapsed` for reading inside `measure` without making it a dep.
|
|
19
|
+
const collapsedRef = useRef(collapsed);
|
|
20
|
+
collapsedRef.current = collapsed;
|
|
21
|
+
|
|
22
|
+
// Floor every visible segment at min(4rem, its content width)
|
|
23
|
+
const applyFloors = useCallback(() => {
|
|
24
|
+
const el = containerRef.current;
|
|
25
|
+
if (!el) return;
|
|
26
|
+
const remPx = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
27
|
+
const floor = itemMinWidth * remPx;
|
|
28
|
+
const segments = Array.from(el.querySelectorAll(`[${SEGMENT_ATTR}]`));
|
|
29
|
+
segments.forEach(node => node.style.minWidth = '0px');
|
|
30
|
+
const contentWidths = segments.map(node => node.scrollWidth);
|
|
31
|
+
segments.forEach((node, i) => node.style.minWidth = `${Math.min(floor, contentWidths[i])}px`);
|
|
32
|
+
}, [itemMinWidth]);
|
|
33
|
+
const measure = useCallback(() => {
|
|
34
|
+
const el = containerRef.current;
|
|
35
|
+
if (!el) return;
|
|
36
|
+
applyFloors();
|
|
37
|
+
if (!collapsedRef.current) naturalWidthRef.current = el.scrollWidth;
|
|
38
|
+
// +1 tolerance avoids ping-ponging at the exact boundary.
|
|
39
|
+
setCollapsed(naturalWidthRef.current > el.clientWidth + 1);
|
|
40
|
+
}, [applyFloors]);
|
|
41
|
+
|
|
42
|
+
// Measure on mount and whenever the set of segments changes.
|
|
43
|
+
useLayoutEffect(() => {
|
|
44
|
+
naturalWidthRef.current = 0;
|
|
45
|
+
setCollapsed(false);
|
|
46
|
+
measure();
|
|
47
|
+
}, [itemCount, measure]);
|
|
48
|
+
|
|
49
|
+
// After a collapse toggle the rendered segments change; re-floor them without
|
|
50
|
+
// re-deciding, so a shrink-to-fit parent's size change can't cause a flip.
|
|
51
|
+
useLayoutEffect(() => {
|
|
52
|
+
applyFloors();
|
|
53
|
+
}, [collapsed, applyFloors]);
|
|
54
|
+
const throttledMeasure = useMemo(() => throttle(measure, 100), [measure]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const el = containerRef.current;
|
|
57
|
+
if (!el) return;
|
|
58
|
+
const observer = new ResizeObserver(throttledMeasure);
|
|
59
|
+
observer.observe(el);
|
|
60
|
+
return () => {
|
|
61
|
+
observer.disconnect();
|
|
62
|
+
throttledMeasure.cancel();
|
|
63
|
+
};
|
|
64
|
+
}, [throttledMeasure]);
|
|
65
|
+
return {
|
|
66
|
+
containerRef,
|
|
67
|
+
collapsed
|
|
68
|
+
};
|
|
69
|
+
};
|