@trackunit/react-components 2.13.11 → 2.13.13
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/index.cjs.js +133 -56
- package/index.esm.js +133 -56
- package/package.json +6 -6
- package/src/components/HorizontalOverflowScroller/HorizontalOverflowScroller.d.ts +17 -9
- package/src/components/HorizontalOverflowScroller/HorizontalOverflowScroller.variants.d.ts +35 -0
- package/src/components/HorizontalOverflowScroller/OverflowIndicator.d.ts +10 -6
- package/src/components/HorizontalOverflowScroller/OverflowIndicator.variants.d.ts +17 -24
- package/src/components/Menu/MoreMenu/MoreMenu.d.ts +1 -0
package/index.cjs.js
CHANGED
|
@@ -29,6 +29,8 @@ var defaultTranslations = {
|
|
|
29
29
|
"breadcrumb.expandButton": "expand",
|
|
30
30
|
"copyButton.copied": "Copied!",
|
|
31
31
|
"copyButton.copy": "Copy",
|
|
32
|
+
"horizontalOverflowScroller.scrollLeft": "Scroll left",
|
|
33
|
+
"horizontalOverflowScroller.scrollRight": "Scroll right",
|
|
32
34
|
"moreMenu.buttonLabel": "Show more"
|
|
33
35
|
};
|
|
34
36
|
|
|
@@ -5788,34 +5790,79 @@ const ZStack = ({ children, className, "data-testid": dataTestId, style, ref })
|
|
|
5788
5790
|
}) }));
|
|
5789
5791
|
};
|
|
5790
5792
|
|
|
5793
|
+
const fadeMaskStyleSheet = `
|
|
5794
|
+
@property --tu-hos-solid-left {
|
|
5795
|
+
syntax: '<length>';
|
|
5796
|
+
initial-value: 0px;
|
|
5797
|
+
inherits: false;
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
@property --tu-hos-solid-right {
|
|
5801
|
+
syntax: '<length>';
|
|
5802
|
+
initial-value: 0px;
|
|
5803
|
+
inherits: false;
|
|
5804
|
+
}
|
|
5805
|
+
|
|
5806
|
+
@property --tu-hos-fade-left {
|
|
5807
|
+
syntax: '<length>';
|
|
5808
|
+
initial-value: 0px;
|
|
5809
|
+
inherits: false;
|
|
5810
|
+
}
|
|
5811
|
+
|
|
5812
|
+
@property --tu-hos-fade-right {
|
|
5813
|
+
syntax: '<length>';
|
|
5814
|
+
initial-value: 0px;
|
|
5815
|
+
inherits: false;
|
|
5816
|
+
}
|
|
5817
|
+
`;
|
|
5791
5818
|
const cvaHorizontalOverflowScroller = cssClassVarianceUtilities.cva({
|
|
5792
|
-
base: [
|
|
5819
|
+
base: [
|
|
5820
|
+
"flex",
|
|
5821
|
+
"flex-nowrap",
|
|
5822
|
+
"gap-1",
|
|
5823
|
+
"overflow-y-hidden",
|
|
5824
|
+
"overflow-x-scroll",
|
|
5825
|
+
"scroll-px-9",
|
|
5826
|
+
"w-full",
|
|
5827
|
+
"no-scrollbar",
|
|
5828
|
+
],
|
|
5793
5829
|
});
|
|
5794
|
-
const cvaHorizontalOverflowScrollerAndIndicatorsContainer = cssClassVarianceUtilities.cva({
|
|
5795
|
-
|
|
5796
|
-
const cvaOverflowIndicatorContainer = cssClassVarianceUtilities.cva({
|
|
5797
|
-
base: ["pointer-events-none", "h-full", "w-full", "isolate", "pb-px"],
|
|
5830
|
+
const cvaHorizontalOverflowScrollerAndIndicatorsContainer = cssClassVarianceUtilities.cva({
|
|
5831
|
+
base: ["group", "w-full", "overflow-clip"],
|
|
5798
5832
|
});
|
|
5799
|
-
const
|
|
5833
|
+
const cvaHorizontalOverflowScrollerFadeMask = cssClassVarianceUtilities.cva({
|
|
5800
5834
|
base: [
|
|
5801
|
-
"pointer-events-none",
|
|
5802
|
-
"self-stretch",
|
|
5803
|
-
"w-16",
|
|
5804
|
-
"opacity-0",
|
|
5805
|
-
"transition-opacity",
|
|
5806
5835
|
"duration-200",
|
|
5807
|
-
"
|
|
5808
|
-
"
|
|
5836
|
+
"ease-out",
|
|
5837
|
+
"[mask-image:linear-gradient(to_right,transparent_0,transparent_var(--tu-hos-solid-left),#000_var(--tu-hos-fade-left),#000_calc(100%_-_var(--tu-hos-fade-right)),transparent_calc(100%_-_var(--tu-hos-solid-right)),transparent_100%)]",
|
|
5838
|
+
"[transition-property:--tu-hos-solid-left,--tu-hos-solid-right,--tu-hos-fade-left,--tu-hos-fade-right]",
|
|
5809
5839
|
],
|
|
5810
5840
|
variants: {
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5841
|
+
canScrollLeft: {
|
|
5842
|
+
true: [
|
|
5843
|
+
"[--tu-hos-solid-left:0px]",
|
|
5844
|
+
"[--tu-hos-fade-left:1.25rem]",
|
|
5845
|
+
"group-hover:[--tu-hos-solid-left:1rem]",
|
|
5846
|
+
"group-hover:[--tu-hos-fade-left:2.25rem]",
|
|
5847
|
+
"group-has-[:focus-visible]:[--tu-hos-fade-left:2.25rem]",
|
|
5848
|
+
],
|
|
5849
|
+
false: ["[--tu-hos-solid-left:0px]", "[--tu-hos-fade-left:0px]"],
|
|
5850
|
+
},
|
|
5851
|
+
canScrollRight: {
|
|
5852
|
+
true: [
|
|
5853
|
+
"[--tu-hos-solid-right:0px]",
|
|
5854
|
+
"[--tu-hos-fade-right:1.25rem]",
|
|
5855
|
+
"group-hover:[--tu-hos-solid-right:1rem]",
|
|
5856
|
+
"group-hover:[--tu-hos-fade-right:2.25rem]",
|
|
5857
|
+
"group-has-[:focus-visible]:[--tu-hos-fade-right:2.25rem]",
|
|
5858
|
+
],
|
|
5859
|
+
false: ["[--tu-hos-solid-right:0px]", "[--tu-hos-fade-right:0px]"],
|
|
5814
5860
|
},
|
|
5815
5861
|
},
|
|
5816
5862
|
});
|
|
5817
|
-
|
|
5818
|
-
|
|
5863
|
+
|
|
5864
|
+
const cvaOverflowIndicatorHitRow = cssClassVarianceUtilities.cva({
|
|
5865
|
+
base: ["flex", "h-full", "w-full", "items-center", "pointer-events-none", "z-10"],
|
|
5819
5866
|
variants: {
|
|
5820
5867
|
direction: {
|
|
5821
5868
|
left: ["justify-start"],
|
|
@@ -5823,43 +5870,72 @@ const cvaJustificationContainer = cssClassVarianceUtilities.cva({
|
|
|
5823
5870
|
},
|
|
5824
5871
|
},
|
|
5825
5872
|
});
|
|
5826
|
-
const
|
|
5873
|
+
const cvaOverflowIndicatorHit = cssClassVarianceUtilities.cva({
|
|
5827
5874
|
base: [
|
|
5828
|
-
"
|
|
5875
|
+
"pointer-events-auto",
|
|
5876
|
+
"inline-flex",
|
|
5877
|
+
"h-full",
|
|
5878
|
+
"items-center",
|
|
5879
|
+
"justify-center",
|
|
5880
|
+
"px-1",
|
|
5881
|
+
"border-0",
|
|
5882
|
+
"bg-transparent",
|
|
5883
|
+
"cursor-pointer",
|
|
5884
|
+
"supports-[color:contrast-color(white)]:[color:contrast-color(var(--hos-surface,Canvas))]",
|
|
5885
|
+
"supports-[not_(color:contrast-color(white))]:text-neutral-900",
|
|
5886
|
+
"supports-[not_(color:contrast-color(white))]:[filter:drop-shadow(0_0_0.5px_#fff)_drop-shadow(0_1px_0_#fff)_drop-shadow(0_-1px_0_#fff)_drop-shadow(1px_0_0_#fff)_drop-shadow(-1px_0_0_#fff)]",
|
|
5829
5887
|
"opacity-0",
|
|
5888
|
+
"starting:opacity-0",
|
|
5830
5889
|
"transition-opacity",
|
|
5831
5890
|
"duration-200",
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
"group-focus-
|
|
5836
|
-
"rounded-full",
|
|
5837
|
-
"mx-1",
|
|
5891
|
+
// Keyboard focus (not a plain mouse-click `:focus`) always reveals the control it landed
|
|
5892
|
+
// on, even the disabled one at an exhausted edge -- that's the only way a sighted keyboard
|
|
5893
|
+
// user gets to see what they just tabbed to.
|
|
5894
|
+
"group-has-[:focus-visible]:opacity-100",
|
|
5838
5895
|
],
|
|
5896
|
+
variants: {
|
|
5897
|
+
disabled: {
|
|
5898
|
+
// Nothing to scroll to on this side: don't let a mouse hover over the row reveal a
|
|
5899
|
+
// control that would be a no-op if clicked.
|
|
5900
|
+
true: [],
|
|
5901
|
+
false: ["group-hover:opacity-100"],
|
|
5902
|
+
},
|
|
5903
|
+
},
|
|
5904
|
+
defaultVariants: {
|
|
5905
|
+
disabled: false,
|
|
5906
|
+
},
|
|
5839
5907
|
});
|
|
5840
5908
|
|
|
5841
5909
|
/**
|
|
5842
|
-
*
|
|
5843
|
-
*
|
|
5910
|
+
* Edge control for HorizontalOverflowScroller. The fade lives on the content
|
|
5911
|
+
* mask; this is the hover/focus hit target that pages left or right.
|
|
5844
5912
|
*
|
|
5845
5913
|
* @param {OverflowIndicatorProps} props - The props for the component
|
|
5846
5914
|
* @returns {ReactElement} OverflowIndicator component
|
|
5847
5915
|
*/
|
|
5848
|
-
const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, onClickScroll, style, }) => {
|
|
5916
|
+
const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, onClickScroll, disabled = false, style, }) => {
|
|
5917
|
+
const [t] = useTranslation();
|
|
5849
5918
|
const iconName = direction === "left" ? "ChevronLeft" : "ChevronRight";
|
|
5850
|
-
|
|
5851
|
-
|
|
5919
|
+
const ariaLabel = direction === "left" ? t("horizontalOverflowScroller.scrollLeft") : t("horizontalOverflowScroller.scrollRight");
|
|
5920
|
+
return (jsxRuntime.jsx("div", { className: cvaOverflowIndicatorHitRow({ className, direction }), "data-testid": dataTestId, style: style, children: jsxRuntime.jsx("button", { "aria-disabled": disabled, "aria-label": ariaLabel, className: cvaOverflowIndicatorHit({ disabled }), "data-testid": dataTestId ? `${dataTestId}-button` : undefined, onClick: disabled ? undefined : onClickScroll, type: "button", children: jsxRuntime.jsx(Icon, { ariaHidden: true, name: iconName, size: "small" }) }) }));
|
|
5852
5921
|
};
|
|
5853
5922
|
|
|
5854
5923
|
/**
|
|
5855
5924
|
* HorizontalOverflowScroller displays child elements in a horizontal row with automatic overflow detection.
|
|
5856
|
-
* When content overflows, it
|
|
5925
|
+
* When content overflows, it fades the overflowing edge and reveals chevrons on hover or focus so the row can be paged.
|
|
5926
|
+
*
|
|
5927
|
+
* The fade is a CSS mask on the content itself, so it works on any background. At rest the ramp is short so more
|
|
5928
|
+
* items stay visible; hover grows a solid plateau under the chevron and pushes the ramp further in, while
|
|
5929
|
+
* keyboard focus only extends the ramp — a plateau there would erase the focus ring of a child flush with the
|
|
5930
|
+
* edge. Set `--hos-surface` on an ancestor to the background color behind the scroller so the chevrons pick a
|
|
5931
|
+
* contrasting color. It defaults to `Canvas`.
|
|
5857
5932
|
*
|
|
5858
5933
|
* ### When to use
|
|
5859
5934
|
* Use HorizontalOverflowScroller to display a row of items (cards, tags, buttons) that may overflow on smaller screens, with visual cues that more content is available.
|
|
5860
5935
|
*
|
|
5861
5936
|
* ### When not to use
|
|
5862
5937
|
* Do not use HorizontalOverflowScroller for tab navigation — use `TabList` which has its own scroll handling.
|
|
5938
|
+
* Do not use it to hide extra actions — use `MoreMenu` when the overflowing items are secondary actions rather than a row of content to browse.
|
|
5863
5939
|
*
|
|
5864
5940
|
* @example Horizontal scroller for KPI cards
|
|
5865
5941
|
* ```tsx
|
|
@@ -5879,36 +5955,36 @@ const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, on
|
|
|
5879
5955
|
* @returns {ReactElement} HorizontalOverflowScroller component
|
|
5880
5956
|
*/
|
|
5881
5957
|
const HorizontalOverflowScroller = ({ className, "data-testid": dataTestId, children, onScrollStateChange, style, ref, }) => {
|
|
5882
|
-
const
|
|
5883
|
-
|
|
5884
|
-
|
|
5958
|
+
const onScrollStateChangeRef = react.useRef(onScrollStateChange);
|
|
5959
|
+
react.useEffect(() => {
|
|
5960
|
+
onScrollStateChangeRef.current = onScrollStateChange;
|
|
5961
|
+
}, [onScrollStateChange]);
|
|
5962
|
+
const handleScrollStateChange = react.useCallback((state) => {
|
|
5963
|
+
onScrollStateChangeRef.current?.({
|
|
5964
|
+
isScrollable: state.isScrollable,
|
|
5965
|
+
isAtBeginning: state.isAtBeginning,
|
|
5966
|
+
isAtEnd: state.isAtEnd,
|
|
5967
|
+
});
|
|
5968
|
+
}, []);
|
|
5969
|
+
const { ref: scrollRef, element, isScrollable, isAtBeginning, isAtEnd, } = useScrollDetection({
|
|
5885
5970
|
direction: "horizontal",
|
|
5886
|
-
onScrollStateChange:
|
|
5887
|
-
? (state) => onScrollStateChange({
|
|
5888
|
-
isScrollable: state.isScrollable,
|
|
5889
|
-
isAtBeginning: state.isAtBeginning,
|
|
5890
|
-
isAtEnd: state.isAtEnd,
|
|
5891
|
-
})
|
|
5892
|
-
: undefined,
|
|
5971
|
+
onScrollStateChange: handleScrollStateChange,
|
|
5893
5972
|
});
|
|
5894
|
-
const mergedRef = useMergeRefs([
|
|
5895
|
-
const
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
element
|
|
5899
|
-
left: -containerGeometry.width,
|
|
5900
|
-
behavior: "smooth",
|
|
5901
|
-
});
|
|
5902
|
-
};
|
|
5903
|
-
const handleScrollRight = () => {
|
|
5904
|
-
if (!element || containerGeometry?.width === undefined)
|
|
5973
|
+
const mergedRef = useMergeRefs([scrollRef, ref]);
|
|
5974
|
+
const canScrollLeft = isScrollable && !isAtBeginning;
|
|
5975
|
+
const canScrollRight = isScrollable && !isAtEnd;
|
|
5976
|
+
const scrollByPage = react.useCallback((direction) => {
|
|
5977
|
+
if (!element) {
|
|
5905
5978
|
return;
|
|
5979
|
+
}
|
|
5906
5980
|
element.scrollBy({
|
|
5907
|
-
left:
|
|
5981
|
+
left: direction * element.clientWidth,
|
|
5908
5982
|
behavior: "smooth",
|
|
5909
5983
|
});
|
|
5910
|
-
};
|
|
5911
|
-
return (jsxRuntime.jsxs(
|
|
5984
|
+
}, [element]);
|
|
5985
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: fadeMaskStyleSheet }), jsxRuntime.jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), style: style, children: [jsxRuntime.jsx("div", { className: cvaHorizontalOverflowScroller({
|
|
5986
|
+
className: cvaHorizontalOverflowScrollerFadeMask({ canScrollLeft, canScrollRight }),
|
|
5987
|
+
}), "data-testid": dataTestId, ref: mergedRef, children: children }), isScrollable ? (jsxRuntime.jsx(OverflowIndicator, { "data-testid": dataTestId ? `${dataTestId}-left-indicator` : undefined, direction: "left", disabled: !canScrollLeft, onClickScroll: () => scrollByPage(-1) })) : null, isScrollable ? (jsxRuntime.jsx(OverflowIndicator, { "data-testid": dataTestId ? `${dataTestId}-right-indicator` : undefined, direction: "right", disabled: !canScrollRight, onClickScroll: () => scrollByPage(1) })) : null] })] }));
|
|
5912
5988
|
};
|
|
5913
5989
|
|
|
5914
5990
|
const cvaIndicator = cssClassVarianceUtilities.cva({ base: ["inline-flex", "items-center"] });
|
|
@@ -8290,6 +8366,7 @@ const cvaMoreMenu = cssClassVarianceUtilities.cva({ base: ["p-0"] });
|
|
|
8290
8366
|
*
|
|
8291
8367
|
* ### When not to use
|
|
8292
8368
|
* Do not use MoreMenu for primary actions that should always be visible. Use `Button` instead.
|
|
8369
|
+
* Do not use it to overflow a row of content (tags, KPIs, chips) — use `HorizontalOverflowScroller` when those items should stay visible and be browsed horizontally.
|
|
8293
8370
|
*
|
|
8294
8371
|
* @example Action items with render prop — Pass a function as `children` to receive the `close` callback and dismiss the menu after an action.
|
|
8295
8372
|
* ```tsx
|
package/index.esm.js
CHANGED
|
@@ -28,6 +28,8 @@ var defaultTranslations = {
|
|
|
28
28
|
"breadcrumb.expandButton": "expand",
|
|
29
29
|
"copyButton.copied": "Copied!",
|
|
30
30
|
"copyButton.copy": "Copy",
|
|
31
|
+
"horizontalOverflowScroller.scrollLeft": "Scroll left",
|
|
32
|
+
"horizontalOverflowScroller.scrollRight": "Scroll right",
|
|
31
33
|
"moreMenu.buttonLabel": "Show more"
|
|
32
34
|
};
|
|
33
35
|
|
|
@@ -5787,34 +5789,79 @@ const ZStack = ({ children, className, "data-testid": dataTestId, style, ref })
|
|
|
5787
5789
|
}) }));
|
|
5788
5790
|
};
|
|
5789
5791
|
|
|
5792
|
+
const fadeMaskStyleSheet = `
|
|
5793
|
+
@property --tu-hos-solid-left {
|
|
5794
|
+
syntax: '<length>';
|
|
5795
|
+
initial-value: 0px;
|
|
5796
|
+
inherits: false;
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
@property --tu-hos-solid-right {
|
|
5800
|
+
syntax: '<length>';
|
|
5801
|
+
initial-value: 0px;
|
|
5802
|
+
inherits: false;
|
|
5803
|
+
}
|
|
5804
|
+
|
|
5805
|
+
@property --tu-hos-fade-left {
|
|
5806
|
+
syntax: '<length>';
|
|
5807
|
+
initial-value: 0px;
|
|
5808
|
+
inherits: false;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
@property --tu-hos-fade-right {
|
|
5812
|
+
syntax: '<length>';
|
|
5813
|
+
initial-value: 0px;
|
|
5814
|
+
inherits: false;
|
|
5815
|
+
}
|
|
5816
|
+
`;
|
|
5790
5817
|
const cvaHorizontalOverflowScroller = cva({
|
|
5791
|
-
base: [
|
|
5818
|
+
base: [
|
|
5819
|
+
"flex",
|
|
5820
|
+
"flex-nowrap",
|
|
5821
|
+
"gap-1",
|
|
5822
|
+
"overflow-y-hidden",
|
|
5823
|
+
"overflow-x-scroll",
|
|
5824
|
+
"scroll-px-9",
|
|
5825
|
+
"w-full",
|
|
5826
|
+
"no-scrollbar",
|
|
5827
|
+
],
|
|
5792
5828
|
});
|
|
5793
|
-
const cvaHorizontalOverflowScrollerAndIndicatorsContainer = cva({
|
|
5794
|
-
|
|
5795
|
-
const cvaOverflowIndicatorContainer = cva({
|
|
5796
|
-
base: ["pointer-events-none", "h-full", "w-full", "isolate", "pb-px"],
|
|
5829
|
+
const cvaHorizontalOverflowScrollerAndIndicatorsContainer = cva({
|
|
5830
|
+
base: ["group", "w-full", "overflow-clip"],
|
|
5797
5831
|
});
|
|
5798
|
-
const
|
|
5832
|
+
const cvaHorizontalOverflowScrollerFadeMask = cva({
|
|
5799
5833
|
base: [
|
|
5800
|
-
"pointer-events-none",
|
|
5801
|
-
"self-stretch",
|
|
5802
|
-
"w-16",
|
|
5803
|
-
"opacity-0",
|
|
5804
|
-
"transition-opacity",
|
|
5805
5834
|
"duration-200",
|
|
5806
|
-
"
|
|
5807
|
-
"
|
|
5835
|
+
"ease-out",
|
|
5836
|
+
"[mask-image:linear-gradient(to_right,transparent_0,transparent_var(--tu-hos-solid-left),#000_var(--tu-hos-fade-left),#000_calc(100%_-_var(--tu-hos-fade-right)),transparent_calc(100%_-_var(--tu-hos-solid-right)),transparent_100%)]",
|
|
5837
|
+
"[transition-property:--tu-hos-solid-left,--tu-hos-solid-right,--tu-hos-fade-left,--tu-hos-fade-right]",
|
|
5808
5838
|
],
|
|
5809
5839
|
variants: {
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5840
|
+
canScrollLeft: {
|
|
5841
|
+
true: [
|
|
5842
|
+
"[--tu-hos-solid-left:0px]",
|
|
5843
|
+
"[--tu-hos-fade-left:1.25rem]",
|
|
5844
|
+
"group-hover:[--tu-hos-solid-left:1rem]",
|
|
5845
|
+
"group-hover:[--tu-hos-fade-left:2.25rem]",
|
|
5846
|
+
"group-has-[:focus-visible]:[--tu-hos-fade-left:2.25rem]",
|
|
5847
|
+
],
|
|
5848
|
+
false: ["[--tu-hos-solid-left:0px]", "[--tu-hos-fade-left:0px]"],
|
|
5849
|
+
},
|
|
5850
|
+
canScrollRight: {
|
|
5851
|
+
true: [
|
|
5852
|
+
"[--tu-hos-solid-right:0px]",
|
|
5853
|
+
"[--tu-hos-fade-right:1.25rem]",
|
|
5854
|
+
"group-hover:[--tu-hos-solid-right:1rem]",
|
|
5855
|
+
"group-hover:[--tu-hos-fade-right:2.25rem]",
|
|
5856
|
+
"group-has-[:focus-visible]:[--tu-hos-fade-right:2.25rem]",
|
|
5857
|
+
],
|
|
5858
|
+
false: ["[--tu-hos-solid-right:0px]", "[--tu-hos-fade-right:0px]"],
|
|
5813
5859
|
},
|
|
5814
5860
|
},
|
|
5815
5861
|
});
|
|
5816
|
-
|
|
5817
|
-
|
|
5862
|
+
|
|
5863
|
+
const cvaOverflowIndicatorHitRow = cva({
|
|
5864
|
+
base: ["flex", "h-full", "w-full", "items-center", "pointer-events-none", "z-10"],
|
|
5818
5865
|
variants: {
|
|
5819
5866
|
direction: {
|
|
5820
5867
|
left: ["justify-start"],
|
|
@@ -5822,43 +5869,72 @@ const cvaJustificationContainer = cva({
|
|
|
5822
5869
|
},
|
|
5823
5870
|
},
|
|
5824
5871
|
});
|
|
5825
|
-
const
|
|
5872
|
+
const cvaOverflowIndicatorHit = cva({
|
|
5826
5873
|
base: [
|
|
5827
|
-
"
|
|
5874
|
+
"pointer-events-auto",
|
|
5875
|
+
"inline-flex",
|
|
5876
|
+
"h-full",
|
|
5877
|
+
"items-center",
|
|
5878
|
+
"justify-center",
|
|
5879
|
+
"px-1",
|
|
5880
|
+
"border-0",
|
|
5881
|
+
"bg-transparent",
|
|
5882
|
+
"cursor-pointer",
|
|
5883
|
+
"supports-[color:contrast-color(white)]:[color:contrast-color(var(--hos-surface,Canvas))]",
|
|
5884
|
+
"supports-[not_(color:contrast-color(white))]:text-neutral-900",
|
|
5885
|
+
"supports-[not_(color:contrast-color(white))]:[filter:drop-shadow(0_0_0.5px_#fff)_drop-shadow(0_1px_0_#fff)_drop-shadow(0_-1px_0_#fff)_drop-shadow(1px_0_0_#fff)_drop-shadow(-1px_0_0_#fff)]",
|
|
5828
5886
|
"opacity-0",
|
|
5887
|
+
"starting:opacity-0",
|
|
5829
5888
|
"transition-opacity",
|
|
5830
5889
|
"duration-200",
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
"group-focus-
|
|
5835
|
-
"rounded-full",
|
|
5836
|
-
"mx-1",
|
|
5890
|
+
// Keyboard focus (not a plain mouse-click `:focus`) always reveals the control it landed
|
|
5891
|
+
// on, even the disabled one at an exhausted edge -- that's the only way a sighted keyboard
|
|
5892
|
+
// user gets to see what they just tabbed to.
|
|
5893
|
+
"group-has-[:focus-visible]:opacity-100",
|
|
5837
5894
|
],
|
|
5895
|
+
variants: {
|
|
5896
|
+
disabled: {
|
|
5897
|
+
// Nothing to scroll to on this side: don't let a mouse hover over the row reveal a
|
|
5898
|
+
// control that would be a no-op if clicked.
|
|
5899
|
+
true: [],
|
|
5900
|
+
false: ["group-hover:opacity-100"],
|
|
5901
|
+
},
|
|
5902
|
+
},
|
|
5903
|
+
defaultVariants: {
|
|
5904
|
+
disabled: false,
|
|
5905
|
+
},
|
|
5838
5906
|
});
|
|
5839
5907
|
|
|
5840
5908
|
/**
|
|
5841
|
-
*
|
|
5842
|
-
*
|
|
5909
|
+
* Edge control for HorizontalOverflowScroller. The fade lives on the content
|
|
5910
|
+
* mask; this is the hover/focus hit target that pages left or right.
|
|
5843
5911
|
*
|
|
5844
5912
|
* @param {OverflowIndicatorProps} props - The props for the component
|
|
5845
5913
|
* @returns {ReactElement} OverflowIndicator component
|
|
5846
5914
|
*/
|
|
5847
|
-
const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, onClickScroll, style, }) => {
|
|
5915
|
+
const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, onClickScroll, disabled = false, style, }) => {
|
|
5916
|
+
const [t] = useTranslation();
|
|
5848
5917
|
const iconName = direction === "left" ? "ChevronLeft" : "ChevronRight";
|
|
5849
|
-
|
|
5850
|
-
|
|
5918
|
+
const ariaLabel = direction === "left" ? t("horizontalOverflowScroller.scrollLeft") : t("horizontalOverflowScroller.scrollRight");
|
|
5919
|
+
return (jsx("div", { className: cvaOverflowIndicatorHitRow({ className, direction }), "data-testid": dataTestId, style: style, children: jsx("button", { "aria-disabled": disabled, "aria-label": ariaLabel, className: cvaOverflowIndicatorHit({ disabled }), "data-testid": dataTestId ? `${dataTestId}-button` : undefined, onClick: disabled ? undefined : onClickScroll, type: "button", children: jsx(Icon, { ariaHidden: true, name: iconName, size: "small" }) }) }));
|
|
5851
5920
|
};
|
|
5852
5921
|
|
|
5853
5922
|
/**
|
|
5854
5923
|
* HorizontalOverflowScroller displays child elements in a horizontal row with automatic overflow detection.
|
|
5855
|
-
* When content overflows, it
|
|
5924
|
+
* When content overflows, it fades the overflowing edge and reveals chevrons on hover or focus so the row can be paged.
|
|
5925
|
+
*
|
|
5926
|
+
* The fade is a CSS mask on the content itself, so it works on any background. At rest the ramp is short so more
|
|
5927
|
+
* items stay visible; hover grows a solid plateau under the chevron and pushes the ramp further in, while
|
|
5928
|
+
* keyboard focus only extends the ramp — a plateau there would erase the focus ring of a child flush with the
|
|
5929
|
+
* edge. Set `--hos-surface` on an ancestor to the background color behind the scroller so the chevrons pick a
|
|
5930
|
+
* contrasting color. It defaults to `Canvas`.
|
|
5856
5931
|
*
|
|
5857
5932
|
* ### When to use
|
|
5858
5933
|
* Use HorizontalOverflowScroller to display a row of items (cards, tags, buttons) that may overflow on smaller screens, with visual cues that more content is available.
|
|
5859
5934
|
*
|
|
5860
5935
|
* ### When not to use
|
|
5861
5936
|
* Do not use HorizontalOverflowScroller for tab navigation — use `TabList` which has its own scroll handling.
|
|
5937
|
+
* Do not use it to hide extra actions — use `MoreMenu` when the overflowing items are secondary actions rather than a row of content to browse.
|
|
5862
5938
|
*
|
|
5863
5939
|
* @example Horizontal scroller for KPI cards
|
|
5864
5940
|
* ```tsx
|
|
@@ -5878,36 +5954,36 @@ const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, on
|
|
|
5878
5954
|
* @returns {ReactElement} HorizontalOverflowScroller component
|
|
5879
5955
|
*/
|
|
5880
5956
|
const HorizontalOverflowScroller = ({ className, "data-testid": dataTestId, children, onScrollStateChange, style, ref, }) => {
|
|
5881
|
-
const
|
|
5882
|
-
|
|
5883
|
-
|
|
5957
|
+
const onScrollStateChangeRef = useRef(onScrollStateChange);
|
|
5958
|
+
useEffect(() => {
|
|
5959
|
+
onScrollStateChangeRef.current = onScrollStateChange;
|
|
5960
|
+
}, [onScrollStateChange]);
|
|
5961
|
+
const handleScrollStateChange = useCallback((state) => {
|
|
5962
|
+
onScrollStateChangeRef.current?.({
|
|
5963
|
+
isScrollable: state.isScrollable,
|
|
5964
|
+
isAtBeginning: state.isAtBeginning,
|
|
5965
|
+
isAtEnd: state.isAtEnd,
|
|
5966
|
+
});
|
|
5967
|
+
}, []);
|
|
5968
|
+
const { ref: scrollRef, element, isScrollable, isAtBeginning, isAtEnd, } = useScrollDetection({
|
|
5884
5969
|
direction: "horizontal",
|
|
5885
|
-
onScrollStateChange:
|
|
5886
|
-
? (state) => onScrollStateChange({
|
|
5887
|
-
isScrollable: state.isScrollable,
|
|
5888
|
-
isAtBeginning: state.isAtBeginning,
|
|
5889
|
-
isAtEnd: state.isAtEnd,
|
|
5890
|
-
})
|
|
5891
|
-
: undefined,
|
|
5970
|
+
onScrollStateChange: handleScrollStateChange,
|
|
5892
5971
|
});
|
|
5893
|
-
const mergedRef = useMergeRefs([
|
|
5894
|
-
const
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
element
|
|
5898
|
-
left: -containerGeometry.width,
|
|
5899
|
-
behavior: "smooth",
|
|
5900
|
-
});
|
|
5901
|
-
};
|
|
5902
|
-
const handleScrollRight = () => {
|
|
5903
|
-
if (!element || containerGeometry?.width === undefined)
|
|
5972
|
+
const mergedRef = useMergeRefs([scrollRef, ref]);
|
|
5973
|
+
const canScrollLeft = isScrollable && !isAtBeginning;
|
|
5974
|
+
const canScrollRight = isScrollable && !isAtEnd;
|
|
5975
|
+
const scrollByPage = useCallback((direction) => {
|
|
5976
|
+
if (!element) {
|
|
5904
5977
|
return;
|
|
5978
|
+
}
|
|
5905
5979
|
element.scrollBy({
|
|
5906
|
-
left:
|
|
5980
|
+
left: direction * element.clientWidth,
|
|
5907
5981
|
behavior: "smooth",
|
|
5908
5982
|
});
|
|
5909
|
-
};
|
|
5910
|
-
return (jsxs(
|
|
5983
|
+
}, [element]);
|
|
5984
|
+
return (jsxs(Fragment, { children: [jsx("style", { children: fadeMaskStyleSheet }), jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), style: style, children: [jsx("div", { className: cvaHorizontalOverflowScroller({
|
|
5985
|
+
className: cvaHorizontalOverflowScrollerFadeMask({ canScrollLeft, canScrollRight }),
|
|
5986
|
+
}), "data-testid": dataTestId, ref: mergedRef, children: children }), isScrollable ? (jsx(OverflowIndicator, { "data-testid": dataTestId ? `${dataTestId}-left-indicator` : undefined, direction: "left", disabled: !canScrollLeft, onClickScroll: () => scrollByPage(-1) })) : null, isScrollable ? (jsx(OverflowIndicator, { "data-testid": dataTestId ? `${dataTestId}-right-indicator` : undefined, direction: "right", disabled: !canScrollRight, onClickScroll: () => scrollByPage(1) })) : null] })] }));
|
|
5911
5987
|
};
|
|
5912
5988
|
|
|
5913
5989
|
const cvaIndicator = cva({ base: ["inline-flex", "items-center"] });
|
|
@@ -8289,6 +8365,7 @@ const cvaMoreMenu = cva({ base: ["p-0"] });
|
|
|
8289
8365
|
*
|
|
8290
8366
|
* ### When not to use
|
|
8291
8367
|
* Do not use MoreMenu for primary actions that should always be visible. Use `Button` instead.
|
|
8368
|
+
* Do not use it to overflow a row of content (tags, KPIs, chips) — use `HorizontalOverflowScroller` when those items should stay visible and be browsed horizontally.
|
|
8292
8369
|
*
|
|
8293
8370
|
* @example Action items with render prop — Pass a function as `children` to receive the `close` callback and dismiss the menu after an action.
|
|
8294
8371
|
* ```tsx
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.13",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"@floating-ui/react": "^0.26.25",
|
|
15
15
|
"string-ts": "^2.0.0",
|
|
16
16
|
"tailwind-merge": "^2.0.0",
|
|
17
|
-
"@trackunit/ui-design-tokens": "1.15.
|
|
18
|
-
"@trackunit/css-class-variance-utilities": "2.0.
|
|
19
|
-
"@trackunit/shared-utils": "1.16.
|
|
20
|
-
"@trackunit/ui-icons": "1.14.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.15.28",
|
|
18
|
+
"@trackunit/css-class-variance-utilities": "2.0.9",
|
|
19
|
+
"@trackunit/shared-utils": "1.16.44",
|
|
20
|
+
"@trackunit/ui-icons": "1.14.37",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
22
|
"@tanstack/react-virtual": "^3.14.3",
|
|
23
23
|
"dequal": "^2.0.3",
|
|
24
24
|
"fflate": "^0.8.2",
|
|
25
25
|
"zod": "^3.25.76",
|
|
26
|
-
"@trackunit/i18n-library-translation": "2.4.
|
|
26
|
+
"@trackunit/i18n-library-translation": "2.4.53"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"react": "^19.0.0",
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import { ReactElement, ReactNode } from "react";
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
2
|
import { CommonProps } from "../../common/CommonProps";
|
|
3
|
-
import type { Styleable } from "../../common/Styleable";
|
|
4
3
|
import { Refable } from "../../common/Refable";
|
|
4
|
+
import type { Styleable } from "../../common/Styleable";
|
|
5
|
+
export type HorizontalOverflowScrollState = {
|
|
6
|
+
readonly isScrollable: boolean;
|
|
7
|
+
readonly isAtBeginning: boolean;
|
|
8
|
+
readonly isAtEnd: boolean;
|
|
9
|
+
};
|
|
5
10
|
export interface HorizontalOverflowScrollerProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
|
|
6
11
|
children: ReactNode;
|
|
7
12
|
/**
|
|
8
|
-
*
|
|
13
|
+
* Called when overflow or edge position changes.
|
|
9
14
|
*/
|
|
10
|
-
onScrollStateChange?: (state:
|
|
11
|
-
isScrollable: boolean;
|
|
12
|
-
isAtBeginning: boolean;
|
|
13
|
-
isAtEnd: boolean;
|
|
14
|
-
}) => void;
|
|
15
|
+
onScrollStateChange?: (state: HorizontalOverflowScrollState) => void;
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
18
|
* HorizontalOverflowScroller displays child elements in a horizontal row with automatic overflow detection.
|
|
18
|
-
* When content overflows, it
|
|
19
|
+
* When content overflows, it fades the overflowing edge and reveals chevrons on hover or focus so the row can be paged.
|
|
20
|
+
*
|
|
21
|
+
* The fade is a CSS mask on the content itself, so it works on any background. At rest the ramp is short so more
|
|
22
|
+
* items stay visible; hover grows a solid plateau under the chevron and pushes the ramp further in, while
|
|
23
|
+
* keyboard focus only extends the ramp — a plateau there would erase the focus ring of a child flush with the
|
|
24
|
+
* edge. Set `--hos-surface` on an ancestor to the background color behind the scroller so the chevrons pick a
|
|
25
|
+
* contrasting color. It defaults to `Canvas`.
|
|
19
26
|
*
|
|
20
27
|
* ### When to use
|
|
21
28
|
* Use HorizontalOverflowScroller to display a row of items (cards, tags, buttons) that may overflow on smaller screens, with visual cues that more content is available.
|
|
22
29
|
*
|
|
23
30
|
* ### When not to use
|
|
24
31
|
* Do not use HorizontalOverflowScroller for tab navigation — use `TabList` which has its own scroll handling.
|
|
32
|
+
* Do not use it to hide extra actions — use `MoreMenu` when the overflowing items are secondary actions rather than a row of content to browse.
|
|
25
33
|
*
|
|
26
34
|
* @example Horizontal scroller for KPI cards
|
|
27
35
|
* ```tsx
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const fadeMaskStyleSheet = "\n @property --tu-hos-solid-left {\n syntax: '<length>';\n initial-value: 0px;\n inherits: false;\n }\n\n @property --tu-hos-solid-right {\n syntax: '<length>';\n initial-value: 0px;\n inherits: false;\n }\n\n @property --tu-hos-fade-left {\n syntax: '<length>';\n initial-value: 0px;\n inherits: false;\n }\n\n @property --tu-hos-fade-right {\n syntax: '<length>';\n initial-value: 0px;\n inherits: false;\n }\n";
|
|
1
2
|
export declare const cvaHorizontalOverflowScroller: import("cva").CVAComponent<Omit<{
|
|
2
3
|
base: string[];
|
|
3
4
|
}, "defaultVariants"> & {
|
|
@@ -10,3 +11,37 @@ export declare const cvaHorizontalOverflowScrollerAndIndicatorsContainer: import
|
|
|
10
11
|
variants: unknown;
|
|
11
12
|
defaultVariants: Omit<{}, never>;
|
|
12
13
|
}, unknown>;
|
|
14
|
+
export declare const cvaHorizontalOverflowScrollerFadeMask: import("cva").CVAComponent<Omit<{
|
|
15
|
+
base: string[];
|
|
16
|
+
variants: {
|
|
17
|
+
canScrollLeft: {
|
|
18
|
+
true: string[];
|
|
19
|
+
false: string[];
|
|
20
|
+
};
|
|
21
|
+
canScrollRight: {
|
|
22
|
+
true: string[];
|
|
23
|
+
false: string[];
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}, "defaultVariants"> & {
|
|
27
|
+
variants: {
|
|
28
|
+
canScrollLeft: {
|
|
29
|
+
true: string[];
|
|
30
|
+
false: string[];
|
|
31
|
+
};
|
|
32
|
+
canScrollRight: {
|
|
33
|
+
true: string[];
|
|
34
|
+
false: string[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
defaultVariants: Omit<{}, never>;
|
|
38
|
+
}, {
|
|
39
|
+
canScrollLeft: {
|
|
40
|
+
true: string[];
|
|
41
|
+
false: string[];
|
|
42
|
+
};
|
|
43
|
+
canScrollRight: {
|
|
44
|
+
true: string[];
|
|
45
|
+
false: string[];
|
|
46
|
+
};
|
|
47
|
+
}>;
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import { MouseEventHandler, ReactElement } from "react";
|
|
1
|
+
import { type MouseEventHandler, type ReactElement } from "react";
|
|
2
2
|
import { CommonProps } from "../../common/CommonProps";
|
|
3
3
|
import type { Styleable } from "../../common/Styleable";
|
|
4
4
|
type OverflowDirection = "left" | "right";
|
|
5
5
|
export interface OverflowIndicatorProps extends CommonProps, Styleable {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Which edge of the scroller this control sits on.
|
|
8
8
|
*/
|
|
9
9
|
direction: OverflowDirection;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Called when the chevron is activated to page in this direction.
|
|
12
12
|
*/
|
|
13
13
|
onClickScroll?: MouseEventHandler<HTMLButtonElement>;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the control has reached the corresponding scroll edge.
|
|
16
|
+
*/
|
|
17
|
+
disabled?: boolean;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* Edge control for HorizontalOverflowScroller. The fade lives on the content
|
|
21
|
+
* mask; this is the hover/focus hit target that pages left or right.
|
|
18
22
|
*
|
|
19
23
|
* @param {OverflowIndicatorProps} props - The props for the component
|
|
20
24
|
* @returns {ReactElement} OverflowIndicator component
|
|
21
25
|
*/
|
|
22
|
-
export declare const OverflowIndicator: ({ className, "data-testid": dataTestId, direction, onClickScroll, style, }: OverflowIndicatorProps) => ReactElement;
|
|
26
|
+
export declare const OverflowIndicator: ({ className, "data-testid": dataTestId, direction, onClickScroll, disabled, style, }: OverflowIndicatorProps) => ReactElement;
|
|
23
27
|
export {};
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
base: string[];
|
|
3
|
-
}, "defaultVariants"> & {
|
|
4
|
-
variants: unknown;
|
|
5
|
-
defaultVariants: Omit<{}, never>;
|
|
6
|
-
}, unknown>;
|
|
7
|
-
export declare const cvaOverflowIndicatorGradient: import("cva").CVAComponent<Omit<{
|
|
1
|
+
export declare const cvaOverflowIndicatorHitRow: import("cva").CVAComponent<Omit<{
|
|
8
2
|
base: string[];
|
|
9
3
|
variants: {
|
|
10
4
|
direction: {
|
|
@@ -26,31 +20,30 @@ export declare const cvaOverflowIndicatorGradient: import("cva").CVAComponent<Om
|
|
|
26
20
|
right: string[];
|
|
27
21
|
};
|
|
28
22
|
}>;
|
|
29
|
-
export declare const
|
|
23
|
+
export declare const cvaOverflowIndicatorHit: import("cva").CVAComponent<Omit<{
|
|
30
24
|
base: string[];
|
|
31
25
|
variants: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
disabled: {
|
|
27
|
+
true: never[];
|
|
28
|
+
false: string[];
|
|
35
29
|
};
|
|
36
30
|
};
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
disabled: false;
|
|
33
|
+
};
|
|
37
34
|
}, "defaultVariants"> & {
|
|
38
35
|
variants: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
disabled: {
|
|
37
|
+
true: never[];
|
|
38
|
+
false: string[];
|
|
42
39
|
};
|
|
43
40
|
};
|
|
44
|
-
defaultVariants: Omit<{},
|
|
41
|
+
defaultVariants: Omit<{}, "disabled"> & {
|
|
42
|
+
disabled: false;
|
|
43
|
+
};
|
|
45
44
|
}, {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
disabled: {
|
|
46
|
+
true: never[];
|
|
47
|
+
false: string[];
|
|
49
48
|
};
|
|
50
49
|
}>;
|
|
51
|
-
export declare const cvaOverflowIndicatorButton: import("cva").CVAComponent<Omit<{
|
|
52
|
-
base: string[];
|
|
53
|
-
}, "defaultVariants"> & {
|
|
54
|
-
variants: unknown;
|
|
55
|
-
defaultVariants: Omit<{}, never>;
|
|
56
|
-
}, unknown>;
|
|
@@ -50,6 +50,7 @@ export interface MoreMenuProps extends CommonProps, Styleable, Refable<HTMLDivEl
|
|
|
50
50
|
*
|
|
51
51
|
* ### When not to use
|
|
52
52
|
* Do not use MoreMenu for primary actions that should always be visible. Use `Button` instead.
|
|
53
|
+
* Do not use it to overflow a row of content (tags, KPIs, chips) — use `HorizontalOverflowScroller` when those items should stay visible and be browsed horizontally.
|
|
53
54
|
*
|
|
54
55
|
* @example Action items with render prop — Pass a function as `children` to receive the `close` callback and dismiss the menu after an action.
|
|
55
56
|
* ```tsx
|