@trackunit/react-components 2.13.27 → 3.0.1
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 +69 -180
- package/index.esm.js +73 -183
- package/migrations/v3-0-0/copyabletext-to-text-copybutton.js +234 -0
- package/migrations.json +5 -0
- package/package.json +2 -2
- package/src/components/CopyButton/CopyButton.d.ts +4 -4
- package/src/components/Icon/Icon.d.ts +4 -4
- package/src/components/Icon/Icon.variants.d.ts +3 -3
- package/src/components/buttons/IconButton/IconButton.d.ts +1 -3
- package/src/components/buttons/shared/Button.variants.d.ts +0 -12
- package/src/hooks/persistence/useHashParamSync.d.ts +4 -4
- package/src/index.d.ts +1 -2
- package/src/components/CopyableText/CopyableText.d.ts +0 -76
- package/src/components/CopyableText/CopyableText.variants.d.ts +0 -41
package/index.cjs.js
CHANGED
|
@@ -25,7 +25,7 @@ var fflate = require('fflate');
|
|
|
25
25
|
var dequal = require('dequal');
|
|
26
26
|
|
|
27
27
|
var defaultTranslations = {
|
|
28
|
-
"breadcrumb.backButton": "
|
|
28
|
+
"breadcrumb.backButton": "Back",
|
|
29
29
|
"breadcrumb.expandButton": "expand",
|
|
30
30
|
"copyButton.copied": "Copied!",
|
|
31
31
|
"copyButton.copy": "Copy",
|
|
@@ -81,7 +81,7 @@ const cvaIcon = cssClassVarianceUtilities.cva({
|
|
|
81
81
|
variants: {
|
|
82
82
|
color: {
|
|
83
83
|
primary: "text-primary-600",
|
|
84
|
-
|
|
84
|
+
trackunit_branding: "text-trackunit_branding",
|
|
85
85
|
neutral: "text-neutral-400",
|
|
86
86
|
info: "text-info-600",
|
|
87
87
|
success: "text-success-600",
|
|
@@ -143,7 +143,7 @@ const cvaIcon = cssClassVarianceUtilities.cva({
|
|
|
143
143
|
const iconPalette = {
|
|
144
144
|
...uiDesignTokens.intentPalette,
|
|
145
145
|
...uiDesignTokens.generalPalette,
|
|
146
|
-
...uiDesignTokens.
|
|
146
|
+
...uiDesignTokens.trackunitBrandingPalette,
|
|
147
147
|
...uiDesignTokens.criticalityPalette,
|
|
148
148
|
...uiDesignTokens.activityPalette,
|
|
149
149
|
...uiDesignTokens.utilizationPalette,
|
|
@@ -1835,15 +1835,6 @@ const cvaButtonPrefixSuffix = cssClassVarianceUtilities.cva({
|
|
|
1835
1835
|
const cvaIconButton = cssClassVarianceUtilities.cva({
|
|
1836
1836
|
base: [],
|
|
1837
1837
|
variants: {
|
|
1838
|
-
ai: {
|
|
1839
|
-
true: [
|
|
1840
|
-
"bg-ai",
|
|
1841
|
-
"hover:bg-[color-mix(in_srgb,var(--color-ai),black_10%)]",
|
|
1842
|
-
"active:bg-[color-mix(in_srgb,var(--color-ai),black_20%)]",
|
|
1843
|
-
"focus:bg-[color-mix(in_srgb,var(--color-ai),black_20%)]",
|
|
1844
|
-
],
|
|
1845
|
-
false: [],
|
|
1846
|
-
},
|
|
1847
1838
|
size: {
|
|
1848
1839
|
/**
|
|
1849
1840
|
* Sized to match a single text line (20px) so the button fits inside
|
|
@@ -2617,7 +2608,7 @@ const toButtonSize = (size) => (size === "extraSmall" ? "small" : size);
|
|
|
2617
2608
|
*/
|
|
2618
2609
|
const IconButton = ({ icon, variant = "primary", size = "medium", square = true, loading = false, disabled = false, className, title, ariaLabel, "data-testid": dataTestId, ref, ...rest }) => {
|
|
2619
2610
|
const accessibleLabel = ariaLabel ?? title;
|
|
2620
|
-
return (jsxRuntime.jsx(Tooltip, { "data-testid": "iconButton-tooltip", delayed: true, disabled: !title || disabled || loading, interactable: false, label: title, children: jsxRuntime.jsx(Button, { ...rest, ariaLabel: accessibleLabel, className: cvaIconButton({ size: size,
|
|
2611
|
+
return (jsxRuntime.jsx(Tooltip, { "data-testid": "iconButton-tooltip", delayed: true, disabled: !title || disabled || loading, interactable: false, label: title, children: jsxRuntime.jsx(Button, { ...rest, ariaLabel: accessibleLabel, className: cvaIconButton({ size: size, className }), "data-testid": dataTestId ? dataTestId : undefined, disabled: disabled || loading, loading: loading, prefix: !loading ? react.cloneElement(icon, { ariaHidden: true }) : undefined, ref: ref, size: toButtonSize(size), square: square, title: undefined, variant: variant }) }));
|
|
2621
2612
|
};
|
|
2622
2613
|
IconButton.displayName = "IconButton";
|
|
2623
2614
|
|
|
@@ -3957,160 +3948,10 @@ const useCopyToClipboard = () => {
|
|
|
3957
3948
|
return react.useMemo(() => [isCopied, copyToClipboard], [isCopied, copyToClipboard]);
|
|
3958
3949
|
};
|
|
3959
3950
|
|
|
3960
|
-
/**
|
|
3961
|
-
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
3962
|
-
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
3963
|
-
* Can be directionally debounced.
|
|
3964
|
-
*
|
|
3965
|
-
* @param {UseHoverProps} param0 The options
|
|
3966
|
-
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
3967
|
-
*/
|
|
3968
|
-
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
3969
|
-
const [isHovering, setIsHovering] = react.useState(false);
|
|
3970
|
-
const [debouncedIsHovering, setDebouncedIsHovering] = react.useState(false);
|
|
3971
|
-
const onMouseEnter = react.useCallback(() => {
|
|
3972
|
-
setIsHovering(true);
|
|
3973
|
-
}, []);
|
|
3974
|
-
const onMouseLeave = react.useCallback(() => {
|
|
3975
|
-
setIsHovering(false);
|
|
3976
|
-
}, []);
|
|
3977
|
-
// Determine if the current transition should be debounced based on direction
|
|
3978
|
-
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
3979
|
-
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
3980
|
-
react.useLayoutEffect(() => {
|
|
3981
|
-
if (!debounced || isTransitionDebounced) {
|
|
3982
|
-
return undefined;
|
|
3983
|
-
}
|
|
3984
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
3985
|
-
setDebouncedIsHovering(isHovering);
|
|
3986
|
-
}, [debounced, isTransitionDebounced, isHovering]);
|
|
3987
|
-
// Apply delay for debounced transitions
|
|
3988
|
-
react.useEffect(() => {
|
|
3989
|
-
if (!isTransitionDebounced) {
|
|
3990
|
-
return undefined;
|
|
3991
|
-
}
|
|
3992
|
-
const timer = setTimeout(() => {
|
|
3993
|
-
setDebouncedIsHovering(isHovering);
|
|
3994
|
-
}, delay);
|
|
3995
|
-
return () => clearTimeout(timer);
|
|
3996
|
-
}, [isTransitionDebounced, delay, isHovering]);
|
|
3997
|
-
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
3998
|
-
return react.useMemo(() => ({
|
|
3999
|
-
onMouseEnter,
|
|
4000
|
-
onMouseLeave,
|
|
4001
|
-
hovering: value,
|
|
4002
|
-
}), [onMouseEnter, onMouseLeave, value]);
|
|
4003
|
-
};
|
|
4004
|
-
|
|
4005
|
-
const cvaCopyableText = cssClassVarianceUtilities.cva({
|
|
4006
|
-
base: [
|
|
4007
|
-
"rounded",
|
|
4008
|
-
"cursor-pointer",
|
|
4009
|
-
"transition-all",
|
|
4010
|
-
"ease-in-out",
|
|
4011
|
-
"inline-flex",
|
|
4012
|
-
"items-center",
|
|
4013
|
-
"gap-1",
|
|
4014
|
-
"bg-transparent",
|
|
4015
|
-
"border-none",
|
|
4016
|
-
"outline-none",
|
|
4017
|
-
"hover:bg-neutral-200",
|
|
4018
|
-
"py-0.5",
|
|
4019
|
-
"px-1",
|
|
4020
|
-
],
|
|
4021
|
-
variants: {
|
|
4022
|
-
animating: {
|
|
4023
|
-
false: "",
|
|
4024
|
-
true: "animate-copy",
|
|
4025
|
-
},
|
|
4026
|
-
size: {
|
|
4027
|
-
sm: "text-sm",
|
|
4028
|
-
xs: "text-xs",
|
|
4029
|
-
},
|
|
4030
|
-
},
|
|
4031
|
-
defaultVariants: {
|
|
4032
|
-
animating: false,
|
|
4033
|
-
size: "sm",
|
|
4034
|
-
},
|
|
4035
|
-
});
|
|
4036
|
-
|
|
4037
|
-
/** Resolves the inner content for CopyableText based on withIcon. */
|
|
4038
|
-
const Content = ({ text, withIcon, }) => {
|
|
4039
|
-
if (withIcon) {
|
|
4040
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate", children: text }), jsxRuntime.jsx(Icon, { ariaHidden: true, className: "shrink-0 text-neutral-400", name: "Square2Stack", size: "small", type: "solid" })] }));
|
|
4041
|
-
}
|
|
4042
|
-
return text;
|
|
4043
|
-
};
|
|
4044
|
-
/**
|
|
4045
|
-
* CopyableText displays a text value that the user can click to copy to the clipboard.
|
|
4046
|
-
* It shows a brief animation and tooltip feedback on copy. What you see is what gets copied.
|
|
4047
|
-
*
|
|
4048
|
-
* ### When to use
|
|
4049
|
-
* Use CopyableText for identifiers, serial numbers, URLs, or any value the user may want to copy (e.g., asset IDs, error codes).
|
|
4050
|
-
*
|
|
4051
|
-
* ### When not to use
|
|
4052
|
-
* - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
|
|
4053
|
-
* - If the value is rendered as plain (non-clickable) text and only a dedicated icon should copy it
|
|
4054
|
-
* (e.g. inside a row or cell that should stay clickable), use `CopyButton` instead:
|
|
4055
|
-
* ```tsx
|
|
4056
|
-
* <div className="flex items-center gap-0.5">
|
|
4057
|
-
* <Text type="span">{value}</Text>
|
|
4058
|
-
* <CopyButton value={value} />
|
|
4059
|
-
* </div>
|
|
4060
|
-
* ```
|
|
4061
|
-
* - Only use `useCopyToClipboard` directly for genuinely custom copy behavior that neither `CopyableText` nor `CopyButton` supports.
|
|
4062
|
-
*
|
|
4063
|
-
* @example Copyable serial number
|
|
4064
|
-
* ```tsx
|
|
4065
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
4066
|
-
*
|
|
4067
|
-
* const AssetSerial = () => (
|
|
4068
|
-
* <CopyableText text="SN-2024-00142" data-testid="serial-number" />
|
|
4069
|
-
* );
|
|
4070
|
-
* ```
|
|
4071
|
-
* @example Copyable text with small size
|
|
4072
|
-
* ```tsx
|
|
4073
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
4074
|
-
*
|
|
4075
|
-
* const CopyableId = () => (
|
|
4076
|
-
* <CopyableText text="ABC-123" size="xs" />
|
|
4077
|
-
* );
|
|
4078
|
-
* ```
|
|
4079
|
-
* @param {CopyableTextProps} props - The props for the CopyableText component
|
|
4080
|
-
* @returns {ReactElement} CopyableText component
|
|
4081
|
-
*/
|
|
4082
|
-
const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy", copiedLabel = "Copied!", "data-testid": dataTestId, className, style, ref, }) => {
|
|
4083
|
-
const [animating, setAnimating] = react.useState(false);
|
|
4084
|
-
const [showCopied, setShowCopied] = react.useState(false);
|
|
4085
|
-
const [, copyToClipboard] = useCopyToClipboard();
|
|
4086
|
-
const { onMouseEnter, onMouseLeave, hovering } = useHover({ debounced: false });
|
|
4087
|
-
if (!text) {
|
|
4088
|
-
return null;
|
|
4089
|
-
}
|
|
4090
|
-
const handleOnClick = (e) => {
|
|
4091
|
-
e.stopPropagation();
|
|
4092
|
-
void copyToClipboard(text)
|
|
4093
|
-
.then(() => {
|
|
4094
|
-
setShowCopied(true);
|
|
4095
|
-
})
|
|
4096
|
-
.catch(() => undefined);
|
|
4097
|
-
setAnimating(true);
|
|
4098
|
-
};
|
|
4099
|
-
const handleMouseLeave = () => {
|
|
4100
|
-
onMouseLeave();
|
|
4101
|
-
setShowCopied(false);
|
|
4102
|
-
};
|
|
4103
|
-
return (jsxRuntime.jsx(Tooltip, { delayed: !showCopied, label: showCopied ? copiedLabel : copyLabel, placement: "top", children: jsxRuntime.jsx("button", { "aria-label": copyLabel, className: cvaCopyableText({ animating, size, className }), "data-testid": dataTestId, onAnimationEnd: () => setAnimating(false), onClick: handleOnClick, onFocus: e => {
|
|
4104
|
-
if (hovering) {
|
|
4105
|
-
e.currentTarget.blur();
|
|
4106
|
-
}
|
|
4107
|
-
}, onMouseEnter: onMouseEnter, onMouseLeave: handleMouseLeave, ref: ref, style: style, type: "button", children: jsxRuntime.jsx(Content, { text: text, withIcon: withIcon }) }) }));
|
|
4108
|
-
};
|
|
4109
|
-
|
|
4110
3951
|
/**
|
|
4111
3952
|
* CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
|
|
4112
|
-
* showing brief tooltip feedback.
|
|
4113
|
-
*
|
|
3953
|
+
* showing brief tooltip feedback. The copied value is not itself the clickable/displayed
|
|
3954
|
+
* element, so the button can be placed next to plain (non-clickable) text.
|
|
4114
3955
|
*
|
|
4115
3956
|
* ### When to use
|
|
4116
3957
|
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
@@ -4123,8 +3964,8 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4123
3964
|
* one copy control; pick icon-only or labelled mode instead.
|
|
4124
3965
|
*
|
|
4125
3966
|
* ### Empty values
|
|
4126
|
-
*
|
|
4127
|
-
*
|
|
3967
|
+
* CopyButton renders nothing when `value` is empty, so consumers never present a copy control
|
|
3968
|
+
* that has no meaningful result.
|
|
4128
3969
|
*
|
|
4129
3970
|
* @example Icon button next to plain text
|
|
4130
3971
|
* ```tsx
|
|
@@ -8947,6 +8788,7 @@ const PageHeaderTitle = ({ title, "data-testid": dataTestId, className, style, r
|
|
|
8947
8788
|
* @returns {ReactElement} PageHeader component
|
|
8948
8789
|
*/
|
|
8949
8790
|
const PageHeader = ({ className, "data-testid": dataTestId, showLoading = false, description, title, tagLabel, backTo, tagColor, tabsList, descriptionIcon = "QuestionMarkCircle", tagTooltipLabel, style, ref, ...discriminatedProps }) => {
|
|
8791
|
+
const [t] = useTranslation();
|
|
8950
8792
|
const tagRenderer = react.useMemo(() => {
|
|
8951
8793
|
if (tagLabel === undefined || tagLabel === "" || showLoading) {
|
|
8952
8794
|
return null;
|
|
@@ -8957,7 +8799,7 @@ const PageHeader = ({ className, "data-testid": dataTestId, showLoading = false,
|
|
|
8957
8799
|
return (jsxRuntime.jsxs("div", { className: cvaPageHeaderContainer({
|
|
8958
8800
|
className,
|
|
8959
8801
|
withBorder: tabsList === undefined,
|
|
8960
|
-
}), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsxs("div", { className: cvaPageHeader(), children: [backTo ? (jsxRuntime.jsx(reactRouter.Link, { to: backTo, children: jsxRuntime.jsx(Button, { className: "mr-4 bg-black/5 hover:bg-black/10", prefix: jsxRuntime.jsx(Icon, { name: "ArrowLeft", size: "small" }), size: "small", square: true, variant: "ghost-neutral" }) })) : undefined, typeof title === "string" ? jsxRuntime.jsx(PageHeaderTitle, { "data-testid": dataTestId, title: title }) : title, tagRenderer || (description !== null && description !== undefined) ? (jsxRuntime.jsxs("div", { className: "mx-2 flex items-center gap-2", children: [description !== null && description !== undefined && !showLoading ? (jsxRuntime.jsx(Tooltip, { "data-testid": dataTestId ? `${dataTestId}-description-tooltip` : undefined, iconProps: {
|
|
8802
|
+
}), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsxs("div", { className: cvaPageHeader(), children: [backTo ? (jsxRuntime.jsx(reactRouter.Link, { to: backTo, children: jsxRuntime.jsx(Button, { className: "mr-4 bg-black/5 hover:bg-black/10", "data-testid": dataTestId ? `${dataTestId}-back-button` : undefined, prefix: jsxRuntime.jsx(Icon, { name: "ArrowLeft", size: "small" }), size: "small", square: true, title: t("breadcrumb.backButton"), variant: "ghost-neutral" }) })) : undefined, typeof title === "string" ? jsxRuntime.jsx(PageHeaderTitle, { "data-testid": dataTestId, title: title }) : title, tagRenderer || (description !== null && description !== undefined) ? (jsxRuntime.jsxs("div", { className: "mx-2 flex items-center gap-2", children: [description !== null && description !== undefined && !showLoading ? (jsxRuntime.jsx(Tooltip, { "data-testid": dataTestId ? `${dataTestId}-description-tooltip` : undefined, iconProps: {
|
|
8961
8803
|
name: descriptionIcon,
|
|
8962
8804
|
"data-testid": "page-header-description-icon",
|
|
8963
8805
|
}, label: description, placement: "bottom" })) : undefined, tagRenderer] })) : null, jsxRuntime.jsxs("div", { className: "ml-auto flex gap-2", children: [discriminatedProps.accessoryType === "kpi-metrics" ? (jsxRuntime.jsx(PageHeaderKpiMetrics, { kpiMetrics: discriminatedProps.kpiMetrics })) : null, discriminatedProps.accessoryType === "actions" ? (Array.isArray(discriminatedProps.secondaryActions) ? (jsxRuntime.jsx(PageHeaderSecondaryActions, { actions: discriminatedProps.secondaryActions, groupActions: discriminatedProps.groupSecondaryActions ?? false, hasPrimaryAction: !!discriminatedProps.primaryAction })) : discriminatedProps.secondaryActions !== null && discriminatedProps.secondaryActions !== undefined ? (discriminatedProps.secondaryActions) : null) : null, discriminatedProps.accessoryType === "actions" &&
|
|
@@ -10027,6 +9869,51 @@ const sheetAnimationReducer = (state, action) => {
|
|
|
10027
9869
|
}
|
|
10028
9870
|
};
|
|
10029
9871
|
|
|
9872
|
+
/**
|
|
9873
|
+
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
9874
|
+
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
9875
|
+
* Can be directionally debounced.
|
|
9876
|
+
*
|
|
9877
|
+
* @param {UseHoverProps} param0 The options
|
|
9878
|
+
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
9879
|
+
*/
|
|
9880
|
+
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
9881
|
+
const [isHovering, setIsHovering] = react.useState(false);
|
|
9882
|
+
const [debouncedIsHovering, setDebouncedIsHovering] = react.useState(false);
|
|
9883
|
+
const onMouseEnter = react.useCallback(() => {
|
|
9884
|
+
setIsHovering(true);
|
|
9885
|
+
}, []);
|
|
9886
|
+
const onMouseLeave = react.useCallback(() => {
|
|
9887
|
+
setIsHovering(false);
|
|
9888
|
+
}, []);
|
|
9889
|
+
// Determine if the current transition should be debounced based on direction
|
|
9890
|
+
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
9891
|
+
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
9892
|
+
react.useLayoutEffect(() => {
|
|
9893
|
+
if (!debounced || isTransitionDebounced) {
|
|
9894
|
+
return undefined;
|
|
9895
|
+
}
|
|
9896
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
9897
|
+
setDebouncedIsHovering(isHovering);
|
|
9898
|
+
}, [debounced, isTransitionDebounced, isHovering]);
|
|
9899
|
+
// Apply delay for debounced transitions
|
|
9900
|
+
react.useEffect(() => {
|
|
9901
|
+
if (!isTransitionDebounced) {
|
|
9902
|
+
return undefined;
|
|
9903
|
+
}
|
|
9904
|
+
const timer = setTimeout(() => {
|
|
9905
|
+
setDebouncedIsHovering(isHovering);
|
|
9906
|
+
}, delay);
|
|
9907
|
+
return () => clearTimeout(timer);
|
|
9908
|
+
}, [isTransitionDebounced, delay, isHovering]);
|
|
9909
|
+
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
9910
|
+
return react.useMemo(() => ({
|
|
9911
|
+
onMouseEnter,
|
|
9912
|
+
onMouseLeave,
|
|
9913
|
+
hovering: value,
|
|
9914
|
+
}), [onMouseEnter, onMouseLeave, value]);
|
|
9915
|
+
};
|
|
9916
|
+
|
|
10030
9917
|
/**
|
|
10031
9918
|
* Manages the dismiss-intent visual on the sheet handle.
|
|
10032
9919
|
*
|
|
@@ -13105,8 +12992,8 @@ const writeHashKey = (hash, key, value) => {
|
|
|
13105
12992
|
|
|
13106
12993
|
/**
|
|
13107
12994
|
* Default maximum length of the resulting URL fragment after adding one
|
|
13108
|
-
* persisted entry.
|
|
13109
|
-
*
|
|
12995
|
+
* persisted entry. Oversized new entries are skipped; oversized replacements
|
|
12996
|
+
* remove the stale entry so localStorage remains authoritative.
|
|
13110
12997
|
*
|
|
13111
12998
|
* The fragment is never sent to the server, so it is not subject to the
|
|
13112
12999
|
* AWS WAF managed-rule `SizeRestrictions_QUERYSTRING` cap that applies to
|
|
@@ -13121,8 +13008,8 @@ const MAX_HASH_LENGTH = 64 * 1024;
|
|
|
13121
13008
|
*
|
|
13122
13009
|
* Behaviour mirrors `useSearchParamSync`, with two differences:
|
|
13123
13010
|
* - Reads from and writes to `location.hash` instead of `location.search`.
|
|
13124
|
-
* -
|
|
13125
|
-
* AWS-WAF-driven search-param cap.
|
|
13011
|
+
* - Oversized replacements remove stale values using
|
|
13012
|
+
* {@link MAX_HASH_LENGTH}, rather than the AWS-WAF-driven search-param cap.
|
|
13126
13013
|
*
|
|
13127
13014
|
* The fragment is treated as a sequence of `&`-separated tokens via the
|
|
13128
13015
|
* pure utilities in `./hashFragment`. Foreign tokens (bare anchors, other
|
|
@@ -13204,15 +13091,18 @@ const useHashParamSync = ({ key, enabled = true, onExternalChange, replace: repl
|
|
|
13204
13091
|
}
|
|
13205
13092
|
pendingWriteRef.current = null;
|
|
13206
13093
|
const { encodedValue, options } = pending;
|
|
13207
|
-
|
|
13094
|
+
let nextHash = writeHashKey(location.hash, key, encodedValue);
|
|
13095
|
+
let removedOversizedValue = false;
|
|
13208
13096
|
if (nextHash.length > MAX_HASH_LENGTH) {
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13097
|
+
if (currentHashValue === undefined) {
|
|
13098
|
+
return;
|
|
13099
|
+
}
|
|
13100
|
+
nextHash = writeHashKey(location.hash, key, undefined);
|
|
13101
|
+
removedOversizedValue = true;
|
|
13214
13102
|
}
|
|
13215
|
-
const shouldReplace =
|
|
13103
|
+
const shouldReplace = removedOversizedValue
|
|
13104
|
+
? true
|
|
13105
|
+
: (options?.replace ?? replaceOption ?? !Boolean(currentHashValue));
|
|
13216
13106
|
// Tanstack Router's `navigate({ hash })` expects the fragment body
|
|
13217
13107
|
// *without* the leading `#` — the router adds the delimiter itself
|
|
13218
13108
|
// when serialising the URL. Forwarding the `#`-prefixed value from
|
|
@@ -15151,7 +15041,6 @@ exports.CardHeader = CardHeader;
|
|
|
15151
15041
|
exports.Collapse = Collapse;
|
|
15152
15042
|
exports.CompletionStatusIndicator = CompletionStatusIndicator;
|
|
15153
15043
|
exports.CopyButton = CopyButton;
|
|
15154
|
-
exports.CopyableText = CopyableText;
|
|
15155
15044
|
exports.DEFAULT_SKELETON_PREFERENCE_CARD_PROPS = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS;
|
|
15156
15045
|
exports.DetailsList = DetailsList;
|
|
15157
15046
|
exports.EmptyState = EmptyState;
|
package/index.esm.js
CHANGED
|
@@ -4,7 +4,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
4
4
|
import { objectKeys, uuidv4, parseTailwindArbitraryValue, objectEntries, nonNullable, objectValues, validateState, writeToStorage, readFromStorage, storageSerializer, filterByMultiple } from '@trackunit/shared-utils';
|
|
5
5
|
export { storageSerializer } from '@trackunit/shared-utils';
|
|
6
6
|
import { useRef, useMemo, useEffect, useSyncExternalStore, useState, useLayoutEffect, createContext, useContext, isValidElement, cloneElement, createElement, useCallback, Fragment as Fragment$1, memo, forwardRef, useId, useReducer, Children } from 'react';
|
|
7
|
-
import { rentalStatusPalette, sitesPalette, utilizationPalette, activityPalette, criticalityPalette,
|
|
7
|
+
import { rentalStatusPalette, sitesPalette, utilizationPalette, activityPalette, criticalityPalette, trackunitBrandingPalette, generalPalette, intentPalette, themeScreenSizeAsNumber, themeContainerSize, color } from '@trackunit/ui-design-tokens';
|
|
8
8
|
import { iconNames } from '@trackunit/ui-icons';
|
|
9
9
|
import IconSpriteMicro from '@trackunit/ui-icons/icons-sprite-micro.svg';
|
|
10
10
|
import IconSpriteMini from '@trackunit/ui-icons/icons-sprite-mini.svg';
|
|
@@ -19,12 +19,12 @@ import { Link, useBlocker, useNavigate, useLocation, useRouter, useSearch } from
|
|
|
19
19
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
20
20
|
import { isTypeableElement } from '@floating-ui/react/utils';
|
|
21
21
|
import { HelmetProvider, Helmet } from 'react-helmet-async';
|
|
22
|
-
import { Trigger, Content
|
|
22
|
+
import { Trigger, Content, List as List$1, Root } from '@radix-ui/react-tabs';
|
|
23
23
|
import { gzipSync, gunzipSync } from 'fflate';
|
|
24
24
|
import { dequal } from 'dequal';
|
|
25
25
|
|
|
26
26
|
var defaultTranslations = {
|
|
27
|
-
"breadcrumb.backButton": "
|
|
27
|
+
"breadcrumb.backButton": "Back",
|
|
28
28
|
"breadcrumb.expandButton": "expand",
|
|
29
29
|
"copyButton.copied": "Copied!",
|
|
30
30
|
"copyButton.copy": "Copy",
|
|
@@ -80,7 +80,7 @@ const cvaIcon = cva({
|
|
|
80
80
|
variants: {
|
|
81
81
|
color: {
|
|
82
82
|
primary: "text-primary-600",
|
|
83
|
-
|
|
83
|
+
trackunit_branding: "text-trackunit_branding",
|
|
84
84
|
neutral: "text-neutral-400",
|
|
85
85
|
info: "text-info-600",
|
|
86
86
|
success: "text-success-600",
|
|
@@ -142,7 +142,7 @@ const cvaIcon = cva({
|
|
|
142
142
|
const iconPalette = {
|
|
143
143
|
...intentPalette,
|
|
144
144
|
...generalPalette,
|
|
145
|
-
...
|
|
145
|
+
...trackunitBrandingPalette,
|
|
146
146
|
...criticalityPalette,
|
|
147
147
|
...activityPalette,
|
|
148
148
|
...utilizationPalette,
|
|
@@ -1834,15 +1834,6 @@ const cvaButtonPrefixSuffix = cva({
|
|
|
1834
1834
|
const cvaIconButton = cva({
|
|
1835
1835
|
base: [],
|
|
1836
1836
|
variants: {
|
|
1837
|
-
ai: {
|
|
1838
|
-
true: [
|
|
1839
|
-
"bg-ai",
|
|
1840
|
-
"hover:bg-[color-mix(in_srgb,var(--color-ai),black_10%)]",
|
|
1841
|
-
"active:bg-[color-mix(in_srgb,var(--color-ai),black_20%)]",
|
|
1842
|
-
"focus:bg-[color-mix(in_srgb,var(--color-ai),black_20%)]",
|
|
1843
|
-
],
|
|
1844
|
-
false: [],
|
|
1845
|
-
},
|
|
1846
1837
|
size: {
|
|
1847
1838
|
/**
|
|
1848
1839
|
* Sized to match a single text line (20px) so the button fits inside
|
|
@@ -2616,7 +2607,7 @@ const toButtonSize = (size) => (size === "extraSmall" ? "small" : size);
|
|
|
2616
2607
|
*/
|
|
2617
2608
|
const IconButton = ({ icon, variant = "primary", size = "medium", square = true, loading = false, disabled = false, className, title, ariaLabel, "data-testid": dataTestId, ref, ...rest }) => {
|
|
2618
2609
|
const accessibleLabel = ariaLabel ?? title;
|
|
2619
|
-
return (jsx(Tooltip, { "data-testid": "iconButton-tooltip", delayed: true, disabled: !title || disabled || loading, interactable: false, label: title, children: jsx(Button, { ...rest, ariaLabel: accessibleLabel, className: cvaIconButton({ size: size,
|
|
2610
|
+
return (jsx(Tooltip, { "data-testid": "iconButton-tooltip", delayed: true, disabled: !title || disabled || loading, interactable: false, label: title, children: jsx(Button, { ...rest, ariaLabel: accessibleLabel, className: cvaIconButton({ size: size, className }), "data-testid": dataTestId ? dataTestId : undefined, disabled: disabled || loading, loading: loading, prefix: !loading ? cloneElement(icon, { ariaHidden: true }) : undefined, ref: ref, size: toButtonSize(size), square: square, title: undefined, variant: variant }) }));
|
|
2620
2611
|
};
|
|
2621
2612
|
IconButton.displayName = "IconButton";
|
|
2622
2613
|
|
|
@@ -3956,160 +3947,10 @@ const useCopyToClipboard = () => {
|
|
|
3956
3947
|
return useMemo(() => [isCopied, copyToClipboard], [isCopied, copyToClipboard]);
|
|
3957
3948
|
};
|
|
3958
3949
|
|
|
3959
|
-
/**
|
|
3960
|
-
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
3961
|
-
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
3962
|
-
* Can be directionally debounced.
|
|
3963
|
-
*
|
|
3964
|
-
* @param {UseHoverProps} param0 The options
|
|
3965
|
-
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
3966
|
-
*/
|
|
3967
|
-
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
3968
|
-
const [isHovering, setIsHovering] = useState(false);
|
|
3969
|
-
const [debouncedIsHovering, setDebouncedIsHovering] = useState(false);
|
|
3970
|
-
const onMouseEnter = useCallback(() => {
|
|
3971
|
-
setIsHovering(true);
|
|
3972
|
-
}, []);
|
|
3973
|
-
const onMouseLeave = useCallback(() => {
|
|
3974
|
-
setIsHovering(false);
|
|
3975
|
-
}, []);
|
|
3976
|
-
// Determine if the current transition should be debounced based on direction
|
|
3977
|
-
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
3978
|
-
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
3979
|
-
useLayoutEffect(() => {
|
|
3980
|
-
if (!debounced || isTransitionDebounced) {
|
|
3981
|
-
return undefined;
|
|
3982
|
-
}
|
|
3983
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
3984
|
-
setDebouncedIsHovering(isHovering);
|
|
3985
|
-
}, [debounced, isTransitionDebounced, isHovering]);
|
|
3986
|
-
// Apply delay for debounced transitions
|
|
3987
|
-
useEffect(() => {
|
|
3988
|
-
if (!isTransitionDebounced) {
|
|
3989
|
-
return undefined;
|
|
3990
|
-
}
|
|
3991
|
-
const timer = setTimeout(() => {
|
|
3992
|
-
setDebouncedIsHovering(isHovering);
|
|
3993
|
-
}, delay);
|
|
3994
|
-
return () => clearTimeout(timer);
|
|
3995
|
-
}, [isTransitionDebounced, delay, isHovering]);
|
|
3996
|
-
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
3997
|
-
return useMemo(() => ({
|
|
3998
|
-
onMouseEnter,
|
|
3999
|
-
onMouseLeave,
|
|
4000
|
-
hovering: value,
|
|
4001
|
-
}), [onMouseEnter, onMouseLeave, value]);
|
|
4002
|
-
};
|
|
4003
|
-
|
|
4004
|
-
const cvaCopyableText = cva({
|
|
4005
|
-
base: [
|
|
4006
|
-
"rounded",
|
|
4007
|
-
"cursor-pointer",
|
|
4008
|
-
"transition-all",
|
|
4009
|
-
"ease-in-out",
|
|
4010
|
-
"inline-flex",
|
|
4011
|
-
"items-center",
|
|
4012
|
-
"gap-1",
|
|
4013
|
-
"bg-transparent",
|
|
4014
|
-
"border-none",
|
|
4015
|
-
"outline-none",
|
|
4016
|
-
"hover:bg-neutral-200",
|
|
4017
|
-
"py-0.5",
|
|
4018
|
-
"px-1",
|
|
4019
|
-
],
|
|
4020
|
-
variants: {
|
|
4021
|
-
animating: {
|
|
4022
|
-
false: "",
|
|
4023
|
-
true: "animate-copy",
|
|
4024
|
-
},
|
|
4025
|
-
size: {
|
|
4026
|
-
sm: "text-sm",
|
|
4027
|
-
xs: "text-xs",
|
|
4028
|
-
},
|
|
4029
|
-
},
|
|
4030
|
-
defaultVariants: {
|
|
4031
|
-
animating: false,
|
|
4032
|
-
size: "sm",
|
|
4033
|
-
},
|
|
4034
|
-
});
|
|
4035
|
-
|
|
4036
|
-
/** Resolves the inner content for CopyableText based on withIcon. */
|
|
4037
|
-
const Content = ({ text, withIcon, }) => {
|
|
4038
|
-
if (withIcon) {
|
|
4039
|
-
return (jsxs(Fragment, { children: [jsx("span", { className: "truncate", children: text }), jsx(Icon, { ariaHidden: true, className: "shrink-0 text-neutral-400", name: "Square2Stack", size: "small", type: "solid" })] }));
|
|
4040
|
-
}
|
|
4041
|
-
return text;
|
|
4042
|
-
};
|
|
4043
|
-
/**
|
|
4044
|
-
* CopyableText displays a text value that the user can click to copy to the clipboard.
|
|
4045
|
-
* It shows a brief animation and tooltip feedback on copy. What you see is what gets copied.
|
|
4046
|
-
*
|
|
4047
|
-
* ### When to use
|
|
4048
|
-
* Use CopyableText for identifiers, serial numbers, URLs, or any value the user may want to copy (e.g., asset IDs, error codes).
|
|
4049
|
-
*
|
|
4050
|
-
* ### When not to use
|
|
4051
|
-
* - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
|
|
4052
|
-
* - If the value is rendered as plain (non-clickable) text and only a dedicated icon should copy it
|
|
4053
|
-
* (e.g. inside a row or cell that should stay clickable), use `CopyButton` instead:
|
|
4054
|
-
* ```tsx
|
|
4055
|
-
* <div className="flex items-center gap-0.5">
|
|
4056
|
-
* <Text type="span">{value}</Text>
|
|
4057
|
-
* <CopyButton value={value} />
|
|
4058
|
-
* </div>
|
|
4059
|
-
* ```
|
|
4060
|
-
* - Only use `useCopyToClipboard` directly for genuinely custom copy behavior that neither `CopyableText` nor `CopyButton` supports.
|
|
4061
|
-
*
|
|
4062
|
-
* @example Copyable serial number
|
|
4063
|
-
* ```tsx
|
|
4064
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
4065
|
-
*
|
|
4066
|
-
* const AssetSerial = () => (
|
|
4067
|
-
* <CopyableText text="SN-2024-00142" data-testid="serial-number" />
|
|
4068
|
-
* );
|
|
4069
|
-
* ```
|
|
4070
|
-
* @example Copyable text with small size
|
|
4071
|
-
* ```tsx
|
|
4072
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
4073
|
-
*
|
|
4074
|
-
* const CopyableId = () => (
|
|
4075
|
-
* <CopyableText text="ABC-123" size="xs" />
|
|
4076
|
-
* );
|
|
4077
|
-
* ```
|
|
4078
|
-
* @param {CopyableTextProps} props - The props for the CopyableText component
|
|
4079
|
-
* @returns {ReactElement} CopyableText component
|
|
4080
|
-
*/
|
|
4081
|
-
const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy", copiedLabel = "Copied!", "data-testid": dataTestId, className, style, ref, }) => {
|
|
4082
|
-
const [animating, setAnimating] = useState(false);
|
|
4083
|
-
const [showCopied, setShowCopied] = useState(false);
|
|
4084
|
-
const [, copyToClipboard] = useCopyToClipboard();
|
|
4085
|
-
const { onMouseEnter, onMouseLeave, hovering } = useHover({ debounced: false });
|
|
4086
|
-
if (!text) {
|
|
4087
|
-
return null;
|
|
4088
|
-
}
|
|
4089
|
-
const handleOnClick = (e) => {
|
|
4090
|
-
e.stopPropagation();
|
|
4091
|
-
void copyToClipboard(text)
|
|
4092
|
-
.then(() => {
|
|
4093
|
-
setShowCopied(true);
|
|
4094
|
-
})
|
|
4095
|
-
.catch(() => undefined);
|
|
4096
|
-
setAnimating(true);
|
|
4097
|
-
};
|
|
4098
|
-
const handleMouseLeave = () => {
|
|
4099
|
-
onMouseLeave();
|
|
4100
|
-
setShowCopied(false);
|
|
4101
|
-
};
|
|
4102
|
-
return (jsx(Tooltip, { delayed: !showCopied, label: showCopied ? copiedLabel : copyLabel, placement: "top", children: jsx("button", { "aria-label": copyLabel, className: cvaCopyableText({ animating, size, className }), "data-testid": dataTestId, onAnimationEnd: () => setAnimating(false), onClick: handleOnClick, onFocus: e => {
|
|
4103
|
-
if (hovering) {
|
|
4104
|
-
e.currentTarget.blur();
|
|
4105
|
-
}
|
|
4106
|
-
}, onMouseEnter: onMouseEnter, onMouseLeave: handleMouseLeave, ref: ref, style: style, type: "button", children: jsx(Content, { text: text, withIcon: withIcon }) }) }));
|
|
4107
|
-
};
|
|
4108
|
-
|
|
4109
3950
|
/**
|
|
4110
3951
|
* CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
|
|
4111
|
-
* showing brief tooltip feedback.
|
|
4112
|
-
*
|
|
3952
|
+
* showing brief tooltip feedback. The copied value is not itself the clickable/displayed
|
|
3953
|
+
* element, so the button can be placed next to plain (non-clickable) text.
|
|
4113
3954
|
*
|
|
4114
3955
|
* ### When to use
|
|
4115
3956
|
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
@@ -4122,8 +3963,8 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4122
3963
|
* one copy control; pick icon-only or labelled mode instead.
|
|
4123
3964
|
*
|
|
4124
3965
|
* ### Empty values
|
|
4125
|
-
*
|
|
4126
|
-
*
|
|
3966
|
+
* CopyButton renders nothing when `value` is empty, so consumers never present a copy control
|
|
3967
|
+
* that has no meaningful result.
|
|
4127
3968
|
*
|
|
4128
3969
|
* @example Icon button next to plain text
|
|
4129
3970
|
* ```tsx
|
|
@@ -8946,6 +8787,7 @@ const PageHeaderTitle = ({ title, "data-testid": dataTestId, className, style, r
|
|
|
8946
8787
|
* @returns {ReactElement} PageHeader component
|
|
8947
8788
|
*/
|
|
8948
8789
|
const PageHeader = ({ className, "data-testid": dataTestId, showLoading = false, description, title, tagLabel, backTo, tagColor, tabsList, descriptionIcon = "QuestionMarkCircle", tagTooltipLabel, style, ref, ...discriminatedProps }) => {
|
|
8790
|
+
const [t] = useTranslation();
|
|
8949
8791
|
const tagRenderer = useMemo(() => {
|
|
8950
8792
|
if (tagLabel === undefined || tagLabel === "" || showLoading) {
|
|
8951
8793
|
return null;
|
|
@@ -8956,7 +8798,7 @@ const PageHeader = ({ className, "data-testid": dataTestId, showLoading = false,
|
|
|
8956
8798
|
return (jsxs("div", { className: cvaPageHeaderContainer({
|
|
8957
8799
|
className,
|
|
8958
8800
|
withBorder: tabsList === undefined,
|
|
8959
|
-
}), "data-testid": dataTestId, ref: ref, style: style, children: [jsxs("div", { className: cvaPageHeader(), children: [backTo ? (jsx(Link, { to: backTo, children: jsx(Button, { className: "mr-4 bg-black/5 hover:bg-black/10", prefix: jsx(Icon, { name: "ArrowLeft", size: "small" }), size: "small", square: true, variant: "ghost-neutral" }) })) : undefined, typeof title === "string" ? jsx(PageHeaderTitle, { "data-testid": dataTestId, title: title }) : title, tagRenderer || (description !== null && description !== undefined) ? (jsxs("div", { className: "mx-2 flex items-center gap-2", children: [description !== null && description !== undefined && !showLoading ? (jsx(Tooltip, { "data-testid": dataTestId ? `${dataTestId}-description-tooltip` : undefined, iconProps: {
|
|
8801
|
+
}), "data-testid": dataTestId, ref: ref, style: style, children: [jsxs("div", { className: cvaPageHeader(), children: [backTo ? (jsx(Link, { to: backTo, children: jsx(Button, { className: "mr-4 bg-black/5 hover:bg-black/10", "data-testid": dataTestId ? `${dataTestId}-back-button` : undefined, prefix: jsx(Icon, { name: "ArrowLeft", size: "small" }), size: "small", square: true, title: t("breadcrumb.backButton"), variant: "ghost-neutral" }) })) : undefined, typeof title === "string" ? jsx(PageHeaderTitle, { "data-testid": dataTestId, title: title }) : title, tagRenderer || (description !== null && description !== undefined) ? (jsxs("div", { className: "mx-2 flex items-center gap-2", children: [description !== null && description !== undefined && !showLoading ? (jsx(Tooltip, { "data-testid": dataTestId ? `${dataTestId}-description-tooltip` : undefined, iconProps: {
|
|
8960
8802
|
name: descriptionIcon,
|
|
8961
8803
|
"data-testid": "page-header-description-icon",
|
|
8962
8804
|
}, label: description, placement: "bottom" })) : undefined, tagRenderer] })) : null, jsxs("div", { className: "ml-auto flex gap-2", children: [discriminatedProps.accessoryType === "kpi-metrics" ? (jsx(PageHeaderKpiMetrics, { kpiMetrics: discriminatedProps.kpiMetrics })) : null, discriminatedProps.accessoryType === "actions" ? (Array.isArray(discriminatedProps.secondaryActions) ? (jsx(PageHeaderSecondaryActions, { actions: discriminatedProps.secondaryActions, groupActions: discriminatedProps.groupSecondaryActions ?? false, hasPrimaryAction: !!discriminatedProps.primaryAction })) : discriminatedProps.secondaryActions !== null && discriminatedProps.secondaryActions !== undefined ? (discriminatedProps.secondaryActions) : null) : null, discriminatedProps.accessoryType === "actions" &&
|
|
@@ -10026,6 +9868,51 @@ const sheetAnimationReducer = (state, action) => {
|
|
|
10026
9868
|
}
|
|
10027
9869
|
};
|
|
10028
9870
|
|
|
9871
|
+
/**
|
|
9872
|
+
* The useHover hook returns a onMouseEnter, onMouseLeave and a boolean indicating whether the element is being hovered.
|
|
9873
|
+
* The boolean will be true if the element is being hovered, and false if it is not.
|
|
9874
|
+
* Can be directionally debounced.
|
|
9875
|
+
*
|
|
9876
|
+
* @param {UseHoverProps} param0 The options
|
|
9877
|
+
* @returns {object} The object containing the onMouseEnter, onMouseLeave and hovering props
|
|
9878
|
+
*/
|
|
9879
|
+
const useHover = ({ debounced = false, delay = 100, direction = "out" } = { debounced: false }) => {
|
|
9880
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
9881
|
+
const [debouncedIsHovering, setDebouncedIsHovering] = useState(false);
|
|
9882
|
+
const onMouseEnter = useCallback(() => {
|
|
9883
|
+
setIsHovering(true);
|
|
9884
|
+
}, []);
|
|
9885
|
+
const onMouseLeave = useCallback(() => {
|
|
9886
|
+
setIsHovering(false);
|
|
9887
|
+
}, []);
|
|
9888
|
+
// Determine if the current transition should be debounced based on direction
|
|
9889
|
+
const isTransitionDebounced = debounced && (direction === "both" || (direction === "in" && isHovering) || (direction === "out" && !isHovering));
|
|
9890
|
+
// Sync debouncedIsHovering immediately for non-debounced transitions
|
|
9891
|
+
useLayoutEffect(() => {
|
|
9892
|
+
if (!debounced || isTransitionDebounced) {
|
|
9893
|
+
return undefined;
|
|
9894
|
+
}
|
|
9895
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Synchronizing derived state for directional debouncing
|
|
9896
|
+
setDebouncedIsHovering(isHovering);
|
|
9897
|
+
}, [debounced, isTransitionDebounced, isHovering]);
|
|
9898
|
+
// Apply delay for debounced transitions
|
|
9899
|
+
useEffect(() => {
|
|
9900
|
+
if (!isTransitionDebounced) {
|
|
9901
|
+
return undefined;
|
|
9902
|
+
}
|
|
9903
|
+
const timer = setTimeout(() => {
|
|
9904
|
+
setDebouncedIsHovering(isHovering);
|
|
9905
|
+
}, delay);
|
|
9906
|
+
return () => clearTimeout(timer);
|
|
9907
|
+
}, [isTransitionDebounced, delay, isHovering]);
|
|
9908
|
+
const value = debounced ? (isTransitionDebounced ? debouncedIsHovering : isHovering) : isHovering;
|
|
9909
|
+
return useMemo(() => ({
|
|
9910
|
+
onMouseEnter,
|
|
9911
|
+
onMouseLeave,
|
|
9912
|
+
hovering: value,
|
|
9913
|
+
}), [onMouseEnter, onMouseLeave, value]);
|
|
9914
|
+
};
|
|
9915
|
+
|
|
10029
9916
|
/**
|
|
10030
9917
|
* Manages the dismiss-intent visual on the sheet handle.
|
|
10031
9918
|
*
|
|
@@ -12122,7 +12009,7 @@ const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid":
|
|
|
12122
12009
|
* @returns {ReactElement} TabContent component
|
|
12123
12010
|
*/
|
|
12124
12011
|
const TabContent = ({ className, "data-testid": dataTestId, children, ref, ...rest }) => {
|
|
12125
|
-
return (jsx(Content
|
|
12012
|
+
return (jsx(Content, { className: cvaTabContent({ className }), "data-testid": dataTestId ? `${dataTestId}-content` : undefined, ref: ref, ...rest, children: children }));
|
|
12126
12013
|
};
|
|
12127
12014
|
|
|
12128
12015
|
/**
|
|
@@ -13104,8 +12991,8 @@ const writeHashKey = (hash, key, value) => {
|
|
|
13104
12991
|
|
|
13105
12992
|
/**
|
|
13106
12993
|
* Default maximum length of the resulting URL fragment after adding one
|
|
13107
|
-
* persisted entry.
|
|
13108
|
-
*
|
|
12994
|
+
* persisted entry. Oversized new entries are skipped; oversized replacements
|
|
12995
|
+
* remove the stale entry so localStorage remains authoritative.
|
|
13109
12996
|
*
|
|
13110
12997
|
* The fragment is never sent to the server, so it is not subject to the
|
|
13111
12998
|
* AWS WAF managed-rule `SizeRestrictions_QUERYSTRING` cap that applies to
|
|
@@ -13120,8 +13007,8 @@ const MAX_HASH_LENGTH = 64 * 1024;
|
|
|
13120
13007
|
*
|
|
13121
13008
|
* Behaviour mirrors `useSearchParamSync`, with two differences:
|
|
13122
13009
|
* - Reads from and writes to `location.hash` instead of `location.search`.
|
|
13123
|
-
* -
|
|
13124
|
-
* AWS-WAF-driven search-param cap.
|
|
13010
|
+
* - Oversized replacements remove stale values using
|
|
13011
|
+
* {@link MAX_HASH_LENGTH}, rather than the AWS-WAF-driven search-param cap.
|
|
13125
13012
|
*
|
|
13126
13013
|
* The fragment is treated as a sequence of `&`-separated tokens via the
|
|
13127
13014
|
* pure utilities in `./hashFragment`. Foreign tokens (bare anchors, other
|
|
@@ -13203,15 +13090,18 @@ const useHashParamSync = ({ key, enabled = true, onExternalChange, replace: repl
|
|
|
13203
13090
|
}
|
|
13204
13091
|
pendingWriteRef.current = null;
|
|
13205
13092
|
const { encodedValue, options } = pending;
|
|
13206
|
-
|
|
13093
|
+
let nextHash = writeHashKey(location.hash, key, encodedValue);
|
|
13094
|
+
let removedOversizedValue = false;
|
|
13207
13095
|
if (nextHash.length > MAX_HASH_LENGTH) {
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13096
|
+
if (currentHashValue === undefined) {
|
|
13097
|
+
return;
|
|
13098
|
+
}
|
|
13099
|
+
nextHash = writeHashKey(location.hash, key, undefined);
|
|
13100
|
+
removedOversizedValue = true;
|
|
13213
13101
|
}
|
|
13214
|
-
const shouldReplace =
|
|
13102
|
+
const shouldReplace = removedOversizedValue
|
|
13103
|
+
? true
|
|
13104
|
+
: (options?.replace ?? replaceOption ?? !Boolean(currentHashValue));
|
|
13215
13105
|
// Tanstack Router's `navigate({ hash })` expects the fragment body
|
|
13216
13106
|
// *without* the leading `#` — the router adds the delimiter itself
|
|
13217
13107
|
// when serialising the URL. Forwarding the `#`-prefixed value from
|
|
@@ -15135,4 +15025,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
|
|
|
15135
15025
|
*/
|
|
15136
15026
|
setupLibraryTranslations();
|
|
15137
15027
|
|
|
15138
|
-
export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton,
|
|
15028
|
+
export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, LabeledValue, LabeledValueList, List, ListItem, MAX_HASH_LENGTH, MAX_URL_LENGTH, MenuContent, MenuDivider, MenuItem, MenuTree, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SidebarContentLayout, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHashParamSync, useHold, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMenuSearchField, useMenuTree, useMergeRefs, useModifierKey, useOptionalPopoverContext, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
|
|
@@ -0,0 +1,234 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.copyableTextToTextCopyButton = void 0;
|
|
37
|
+
const ts = __importStar(require("typescript"));
|
|
38
|
+
const jsx_utils_1 = require("../utils/jsx-utils");
|
|
39
|
+
const PACKAGE_NAME = "@trackunit/react-components";
|
|
40
|
+
const COMPONENT_NAME = "CopyableText";
|
|
41
|
+
/** Attribute initializer rendered back to source, e.g. `"abc"` or `{value}`. */
|
|
42
|
+
const attributeText = (attr) => {
|
|
43
|
+
const initializer = attr.initializer;
|
|
44
|
+
if (initializer === undefined)
|
|
45
|
+
return null;
|
|
46
|
+
return initializer.getText();
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The expression the copied text resolves to: `text="abc"` yields `"abc"`,
|
|
50
|
+
* `text={value}` yields `value`. Used both as the CopyButton value and as the
|
|
51
|
+
* rendered children, so it must be a side-effect-free expression.
|
|
52
|
+
*/
|
|
53
|
+
const textExpression = (attr) => {
|
|
54
|
+
const initializer = attr.initializer;
|
|
55
|
+
if (initializer === undefined)
|
|
56
|
+
return null;
|
|
57
|
+
if (ts.isStringLiteral(initializer))
|
|
58
|
+
return initializer.getText();
|
|
59
|
+
if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {
|
|
60
|
+
return initializer.expression.getText();
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
};
|
|
64
|
+
const renderReplacement = ({ textExpr, size, copyLabelAttr, copiedLabelAttr, classNameLiteral, styleAttr, testIdAttr, textAlias, copyButtonAlias, }) => {
|
|
65
|
+
const wrapperClasses = ["flex", "min-w-0", "max-w-full", "items-center", "gap-0.5", classNameLiteral]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(" ");
|
|
68
|
+
const wrapperAttrs = [
|
|
69
|
+
`className="${wrapperClasses}"`,
|
|
70
|
+
styleAttr !== null ? `style=${styleAttr}` : null,
|
|
71
|
+
testIdAttr !== null ? `data-testid=${testIdAttr}` : null,
|
|
72
|
+
]
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join(" ");
|
|
75
|
+
const textProps = [`className="min-w-0 truncate"`, size === "xs" ? `size="small"` : null, `type="span"`]
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join(" ");
|
|
78
|
+
const buttonProps = [
|
|
79
|
+
`className="shrink-0"`,
|
|
80
|
+
copiedLabelAttr !== null ? `copiedLabel=${copiedLabelAttr}` : null,
|
|
81
|
+
copyLabelAttr !== null ? `copyLabel=${copyLabelAttr}` : null,
|
|
82
|
+
size === "xs" ? `size="extraSmall"` : null,
|
|
83
|
+
`value={${textExpr}}`,
|
|
84
|
+
]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.join(" ");
|
|
87
|
+
return `<span ${wrapperAttrs}><${textAlias} ${textProps}>{${textExpr}}</${textAlias}><${copyButtonAlias} ${buttonProps} /></span>`;
|
|
88
|
+
};
|
|
89
|
+
/** Removes `name` (possibly aliased) from the package's named import list. */
|
|
90
|
+
const removeNamedImport = (content, packageName, name) => {
|
|
91
|
+
const sourceFile = (0, jsx_utils_1.parseTsx)(content, "source.tsx");
|
|
92
|
+
for (const stmt of sourceFile.statements) {
|
|
93
|
+
if (!ts.isImportDeclaration(stmt))
|
|
94
|
+
continue;
|
|
95
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
96
|
+
if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName)
|
|
97
|
+
continue;
|
|
98
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
99
|
+
if (namedBindings === undefined || !ts.isNamedImports(namedBindings))
|
|
100
|
+
continue;
|
|
101
|
+
const remaining = namedBindings.elements.filter(el => (el.propertyName?.text ?? el.name.text) !== name);
|
|
102
|
+
if (remaining.length === namedBindings.elements.length)
|
|
103
|
+
continue;
|
|
104
|
+
if (remaining.length === 0) {
|
|
105
|
+
// The import only carried this component; drop the whole statement.
|
|
106
|
+
const start = stmt.getFullStart();
|
|
107
|
+
const end = stmt.getEnd();
|
|
108
|
+
const trailing = content.slice(end).startsWith("\n") ? end + 1 : end;
|
|
109
|
+
return `${content.slice(0, start)}${content.slice(trailing)}`;
|
|
110
|
+
}
|
|
111
|
+
const elementsText = remaining.map(el => el.getText()).join(", ");
|
|
112
|
+
return `${content.slice(0, namedBindings.getStart())}{ ${elementsText} }${content.slice(namedBindings.getEnd())}`;
|
|
113
|
+
}
|
|
114
|
+
return content;
|
|
115
|
+
};
|
|
116
|
+
const ensureNamedImport = (content, packageName, namesToAdd) => {
|
|
117
|
+
const sourceFile = (0, jsx_utils_1.parseTsx)(content, "source.tsx");
|
|
118
|
+
const aliases = (0, jsx_utils_1.getImportedAliases)(sourceFile, packageName) ?? {};
|
|
119
|
+
const missing = namesToAdd.filter(name => !Object.values(aliases).includes(name));
|
|
120
|
+
if (missing.length === 0)
|
|
121
|
+
return content;
|
|
122
|
+
for (const stmt of sourceFile.statements) {
|
|
123
|
+
if (!ts.isImportDeclaration(stmt))
|
|
124
|
+
continue;
|
|
125
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
126
|
+
if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName)
|
|
127
|
+
continue;
|
|
128
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
129
|
+
if (namedBindings === undefined || !ts.isNamedImports(namedBindings))
|
|
130
|
+
continue;
|
|
131
|
+
const elementsText = namedBindings.elements.map(el => el.getText()).join(", ");
|
|
132
|
+
const merged = [elementsText, ...missing].join(", ");
|
|
133
|
+
return `${content.slice(0, namedBindings.getStart())}{ ${merged} }${content.slice(namedBindings.getEnd())}`;
|
|
134
|
+
}
|
|
135
|
+
const importLine = `import { ${missing.join(", ")} } from "${packageName}";\n`;
|
|
136
|
+
return `${importLine}${content}`;
|
|
137
|
+
};
|
|
138
|
+
let manualReviewCount = 0;
|
|
139
|
+
const transformCopyableTextUsage = (filePath, content) => {
|
|
140
|
+
const sourceFile = (0, jsx_utils_1.parseTsx)(content, filePath);
|
|
141
|
+
const aliases = (0, jsx_utils_1.getImportedAliases)(sourceFile, PACKAGE_NAME);
|
|
142
|
+
if (aliases === null)
|
|
143
|
+
return null;
|
|
144
|
+
const copyableTextAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];
|
|
145
|
+
if (copyableTextAlias === undefined)
|
|
146
|
+
return null;
|
|
147
|
+
// A referenced CopyableTextProps type cannot be rewritten mechanically;
|
|
148
|
+
// leave the whole file for a human so it isn't half-migrated.
|
|
149
|
+
if (Object.values(aliases).includes("CopyableTextProps")) {
|
|
150
|
+
manualReviewCount += 1;
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const matches = (0, jsx_utils_1.findJsxElements)(sourceFile, [copyableTextAlias]);
|
|
154
|
+
if (matches.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
const edits = [];
|
|
157
|
+
for (const { openingElement, element } of matches) {
|
|
158
|
+
if ((0, jsx_utils_1.hasSpreadAttribute)(openingElement) || (0, jsx_utils_1.findJsxAttribute)(openingElement, "ref") !== null) {
|
|
159
|
+
manualReviewCount += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const textAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "text");
|
|
163
|
+
const textExpr = textAttr !== null ? textExpression(textAttr) : null;
|
|
164
|
+
if (textExpr === null) {
|
|
165
|
+
manualReviewCount += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const sizeAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "size");
|
|
169
|
+
const sizeInitializer = sizeAttr?.initializer;
|
|
170
|
+
const size = sizeInitializer !== undefined && sizeInitializer.getText().includes("xs") ? "xs" : "sm";
|
|
171
|
+
const classNameAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "className");
|
|
172
|
+
let classNameLiteral = null;
|
|
173
|
+
if (classNameAttr !== null) {
|
|
174
|
+
const initializer = classNameAttr.initializer;
|
|
175
|
+
if (initializer !== undefined && ts.isStringLiteral(initializer)) {
|
|
176
|
+
classNameLiteral = initializer.text;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Dynamic classNames can't be merged into the wrapper's string safely.
|
|
180
|
+
manualReviewCount += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const copyLabelAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "copyLabel");
|
|
185
|
+
const copiedLabelAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "copiedLabel");
|
|
186
|
+
const styleAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "style");
|
|
187
|
+
const testIdAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "data-testid");
|
|
188
|
+
const replacement = renderReplacement({
|
|
189
|
+
textExpr,
|
|
190
|
+
size,
|
|
191
|
+
copyLabelAttr: copyLabelAttr !== null ? attributeText(copyLabelAttr) : null,
|
|
192
|
+
copiedLabelAttr: copiedLabelAttr !== null ? attributeText(copiedLabelAttr) : null,
|
|
193
|
+
classNameLiteral,
|
|
194
|
+
styleAttr: styleAttr !== null ? attributeText(styleAttr) : null,
|
|
195
|
+
testIdAttr: testIdAttr !== null ? attributeText(testIdAttr) : null,
|
|
196
|
+
textAlias: "Text",
|
|
197
|
+
copyButtonAlias: "CopyButton",
|
|
198
|
+
});
|
|
199
|
+
edits.push({ start: element.getStart(), end: element.getEnd(), replacement });
|
|
200
|
+
}
|
|
201
|
+
if (edits.length === 0)
|
|
202
|
+
return null;
|
|
203
|
+
edits.sort((a, b) => b.start - a.start);
|
|
204
|
+
let updated = content;
|
|
205
|
+
for (const { start, end, replacement } of edits) {
|
|
206
|
+
updated = `${updated.slice(0, start)}${replacement}${updated.slice(end)}`;
|
|
207
|
+
}
|
|
208
|
+
// Only drop the import when every usage in the file was rewritten.
|
|
209
|
+
const remaining = (0, jsx_utils_1.findJsxElements)((0, jsx_utils_1.parseTsx)(updated, filePath), [copyableTextAlias]);
|
|
210
|
+
if (remaining.length === 0) {
|
|
211
|
+
updated = removeNamedImport(updated, PACKAGE_NAME, COMPONENT_NAME);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
manualReviewCount += 1;
|
|
215
|
+
}
|
|
216
|
+
updated = ensureNamedImport(updated, PACKAGE_NAME, ["Text", "CopyButton"]);
|
|
217
|
+
return updated;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Rewrites `<CopyableText text={x} …/>` (removed in v3) into the plain
|
|
221
|
+
* value/copy pair: a `Text` next to a `CopyButton` that copies the same
|
|
222
|
+
* expression. `size="xs"` maps to `Text size="small"` + `CopyButton
|
|
223
|
+
* size="extraSmall"`; `copyLabel`/`copiedLabel` carry over (CopyButton also
|
|
224
|
+
* ships localized defaults). Usages with spreads, refs, dynamic classNames,
|
|
225
|
+
* or a referenced `CopyableTextProps` type are left for manual review.
|
|
226
|
+
*/
|
|
227
|
+
const copyableTextToTextCopyButton = (tree) => {
|
|
228
|
+
manualReviewCount = 0;
|
|
229
|
+
const touched = (0, jsx_utils_1.visitTsxFiles)(tree, COMPONENT_NAME, transformCopyableTextUsage);
|
|
230
|
+
(0, jsx_utils_1.logSummary)("copyabletext-to-text-copybutton", touched, manualReviewCount);
|
|
231
|
+
};
|
|
232
|
+
exports.copyableTextToTextCopyButton = copyableTextToTextCopyButton;
|
|
233
|
+
exports.default = exports.copyableTextToTextCopyButton;
|
|
234
|
+
//# sourceMappingURL=copyabletext-to-text-copybutton.js.map
|
package/migrations.json
CHANGED
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"version": "3.0.0",
|
|
45
45
|
"description": "Rename MenuList/MenuListProps to MenuContent/MenuContentProps (import specifiers, JSX tags, and type references).",
|
|
46
46
|
"implementation": "./migrations/v3-0-0/menulist-rename-to-menucontent"
|
|
47
|
+
},
|
|
48
|
+
"v3-0-0-copyabletext-to-text-copybutton": {
|
|
49
|
+
"version": "3.0.0",
|
|
50
|
+
"description": "Replace the removed CopyableText with the Text + CopyButton pair (the displayed value stays plain text; only the button copies).",
|
|
51
|
+
"implementation": "./migrations/v3-0-0/copyabletext-to-text-copybutton"
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -14,7 +14,7 @@
|
|
|
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.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.15.36",
|
|
18
18
|
"@trackunit/css-class-variance-utilities": "2.0.15",
|
|
19
19
|
"@trackunit/shared-utils": "1.16.50",
|
|
20
20
|
"@trackunit/ui-icons": "1.14.44",
|
|
@@ -49,8 +49,8 @@ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLBut
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
|
|
52
|
-
* showing brief tooltip feedback.
|
|
53
|
-
*
|
|
52
|
+
* showing brief tooltip feedback. The copied value is not itself the clickable/displayed
|
|
53
|
+
* element, so the button can be placed next to plain (non-clickable) text.
|
|
54
54
|
*
|
|
55
55
|
* ### When to use
|
|
56
56
|
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
@@ -63,8 +63,8 @@ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLBut
|
|
|
63
63
|
* one copy control; pick icon-only or labelled mode instead.
|
|
64
64
|
*
|
|
65
65
|
* ### Empty values
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* CopyButton renders nothing when `value` is empty, so consumers never present a copy control
|
|
67
|
+
* that has no meaningful result.
|
|
68
68
|
*
|
|
69
69
|
* @example Icon button next to plain text
|
|
70
70
|
* ```tsx
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActivityColors,
|
|
1
|
+
import { ActivityColors, CriticalityColors, GeneralColors, IntentColors, RentalStatusColors, SitesColors, TrackunitBrandingColors, UtilizationColors } from "@trackunit/ui-design-tokens";
|
|
2
2
|
import { IconName } from "@trackunit/ui-icons";
|
|
3
3
|
import { MouseEventHandler, ReactElement } from "react";
|
|
4
4
|
import { AriaProps } from "../../common/AriaProps";
|
|
@@ -6,7 +6,7 @@ import { CommonProps } from "../../common/CommonProps";
|
|
|
6
6
|
import { Refable } from "../../common/Refable";
|
|
7
7
|
import { Size } from "../../common/Size";
|
|
8
8
|
import type { Styleable } from "../../common/Styleable";
|
|
9
|
-
export type IconColors = IntentColors |
|
|
9
|
+
export type IconColors = IntentColors | TrackunitBrandingColors | GeneralColors | CriticalityColors | ActivityColors | UtilizationColors | SitesColors | RentalStatusColors;
|
|
10
10
|
export declare const iconPalette: {
|
|
11
11
|
ON_RENT: {
|
|
12
12
|
readonly 100: "219 234 254";
|
|
@@ -163,7 +163,7 @@ export declare const iconPalette: {
|
|
|
163
163
|
readonly 600: "220 38 38";
|
|
164
164
|
readonly 700: "185 28 28";
|
|
165
165
|
};
|
|
166
|
-
|
|
166
|
+
TRACKUNIT_BRANDING: {
|
|
167
167
|
readonly DEFAULT: "249 66 58";
|
|
168
168
|
};
|
|
169
169
|
PRIMARY: {
|
|
@@ -245,7 +245,7 @@ export declare const iconPalette: {
|
|
|
245
245
|
readonly 900: "127 29 29";
|
|
246
246
|
};
|
|
247
247
|
};
|
|
248
|
-
export declare const iconColorNames: ("black" | "white" | "active" | "primary" | "
|
|
248
|
+
export declare const iconColorNames: ("black" | "white" | "active" | "primary" | "trackunit_branding" | "neutral" | "info" | "success" | "warning" | "danger" | "good" | "low" | "critical" | "working" | "idle" | "moving" | "excessive_usage" | "stopped" | "unknown" | "unused" | "utilized" | "heavily_utilized" | "unknown_utilization" | "site_area" | "site_classic_poi" | "site_classic_zone" | "site_depot" | "site_work_place" | "site_construction_site" | "site_unknown" | "on_rent" | "returned" | "available" | "pickup_ready" | "transfer" | "in_repair" | "other_rental_status")[];
|
|
249
249
|
type IconPropsSmall = {
|
|
250
250
|
size?: "small";
|
|
251
251
|
type?: "solid";
|
|
@@ -3,7 +3,7 @@ export declare const cvaIcon: import("cva").CVAComponent<Omit<{
|
|
|
3
3
|
variants: {
|
|
4
4
|
color: {
|
|
5
5
|
primary: string;
|
|
6
|
-
|
|
6
|
+
trackunit_branding: string;
|
|
7
7
|
neutral: string;
|
|
8
8
|
info: string;
|
|
9
9
|
success: string;
|
|
@@ -72,7 +72,7 @@ export declare const cvaIcon: import("cva").CVAComponent<Omit<{
|
|
|
72
72
|
variants: {
|
|
73
73
|
color: {
|
|
74
74
|
primary: string;
|
|
75
|
-
|
|
75
|
+
trackunit_branding: string;
|
|
76
76
|
neutral: string;
|
|
77
77
|
info: string;
|
|
78
78
|
success: string;
|
|
@@ -127,7 +127,7 @@ export declare const cvaIcon: import("cva").CVAComponent<Omit<{
|
|
|
127
127
|
}, {
|
|
128
128
|
color: {
|
|
129
129
|
primary: string;
|
|
130
|
-
|
|
130
|
+
trackunit_branding: string;
|
|
131
131
|
neutral: string;
|
|
132
132
|
info: string;
|
|
133
133
|
success: string;
|
|
@@ -4,7 +4,7 @@ import { Refable } from "../../../common/Refable";
|
|
|
4
4
|
import { Icon, IconProps } from "../../Icon/Icon";
|
|
5
5
|
import { ButtonCommonProps } from "../shared/ButtonProps";
|
|
6
6
|
import { IconButtonSize } from "../shared/IconButtonSize";
|
|
7
|
-
interface IconButtonBaseProps extends MappedOmit<ButtonCommonProps, "ariaLabel" | "size" | "title"
|
|
7
|
+
interface IconButtonBaseProps extends MappedOmit<ButtonCommonProps, "ariaLabel" | "size" | "title">, Refable<HTMLButtonElement>, Refable<HTMLButtonElement> {
|
|
8
8
|
/**
|
|
9
9
|
* The icon to display. Must be an `<Icon />` element.
|
|
10
10
|
*/
|
|
@@ -21,8 +21,6 @@ interface IconButtonBaseProps extends MappedOmit<ButtonCommonProps, "ariaLabel"
|
|
|
21
21
|
* single text line.
|
|
22
22
|
*/
|
|
23
23
|
size?: IconButtonSize;
|
|
24
|
-
/** The button appearance, including the AI icon-only treatment. */
|
|
25
|
-
variant?: ButtonCommonProps["variant"] | "ai";
|
|
26
24
|
}
|
|
27
25
|
type IconButtonAccessibleNameProps = {
|
|
28
26
|
/**
|
|
@@ -336,10 +336,6 @@ export declare const cvaButtonPrefixSuffix: import("cva").CVAComponent<Omit<{
|
|
|
336
336
|
export declare const cvaIconButton: import("cva").CVAComponent<Omit<{
|
|
337
337
|
base: never[];
|
|
338
338
|
variants: {
|
|
339
|
-
ai: {
|
|
340
|
-
true: string[];
|
|
341
|
-
false: never[];
|
|
342
|
-
};
|
|
343
339
|
size: {
|
|
344
340
|
/**
|
|
345
341
|
* Sized to match a single text line (20px) so the button fits inside
|
|
@@ -359,10 +355,6 @@ export declare const cvaIconButton: import("cva").CVAComponent<Omit<{
|
|
|
359
355
|
};
|
|
360
356
|
}, "defaultVariants"> & {
|
|
361
357
|
variants: {
|
|
362
|
-
ai: {
|
|
363
|
-
true: string[];
|
|
364
|
-
false: never[];
|
|
365
|
-
};
|
|
366
358
|
size: {
|
|
367
359
|
/**
|
|
368
360
|
* Sized to match a single text line (20px) so the button fits inside
|
|
@@ -381,10 +373,6 @@ export declare const cvaIconButton: import("cva").CVAComponent<Omit<{
|
|
|
381
373
|
size: "medium";
|
|
382
374
|
};
|
|
383
375
|
}, {
|
|
384
|
-
ai: {
|
|
385
|
-
true: string[];
|
|
386
|
-
false: never[];
|
|
387
|
-
};
|
|
388
376
|
size: {
|
|
389
377
|
/**
|
|
390
378
|
* Sized to match a single text line (20px) so the button fits inside
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default maximum length of the resulting URL fragment after adding one
|
|
3
|
-
* persisted entry.
|
|
4
|
-
*
|
|
3
|
+
* persisted entry. Oversized new entries are skipped; oversized replacements
|
|
4
|
+
* remove the stale entry so localStorage remains authoritative.
|
|
5
5
|
*
|
|
6
6
|
* The fragment is never sent to the server, so it is not subject to the
|
|
7
7
|
* AWS WAF managed-rule `SizeRestrictions_QUERYSTRING` cap that applies to
|
|
@@ -37,8 +37,8 @@ type UseHashParamSyncReturn = {
|
|
|
37
37
|
*
|
|
38
38
|
* Behaviour mirrors `useSearchParamSync`, with two differences:
|
|
39
39
|
* - Reads from and writes to `location.hash` instead of `location.search`.
|
|
40
|
-
* -
|
|
41
|
-
* AWS-WAF-driven search-param cap.
|
|
40
|
+
* - Oversized replacements remove stale values using
|
|
41
|
+
* {@link MAX_HASH_LENGTH}, rather than the AWS-WAF-driven search-param cap.
|
|
42
42
|
*
|
|
43
43
|
* The fragment is treated as a sequence of `&`-separated tokens via the
|
|
44
44
|
* pure utilities in `./hashFragment`. Foreign tokens (bare anchors, other
|
package/src/index.d.ts
CHANGED
|
@@ -22,7 +22,6 @@ export * from "./components/Card/CardHeader";
|
|
|
22
22
|
export * from "./components/Clickable/Clickable.variants";
|
|
23
23
|
export * from "./components/Collapse/Collapse";
|
|
24
24
|
export * from "./components/CompletionStatusIndicator/CompletionStatusIndicator";
|
|
25
|
-
export * from "./components/CopyableText/CopyableText";
|
|
26
25
|
export * from "./components/CopyButton/CopyButton";
|
|
27
26
|
export * from "./components/DetailsList/DetailsList";
|
|
28
27
|
export * from "./components/EmptyState/EmptyState";
|
|
@@ -71,8 +70,8 @@ export * from "./components/PageHeader/PageHeader.variants";
|
|
|
71
70
|
export * from "./components/PageHeader/types";
|
|
72
71
|
export * from "./components/Pagination/Pagination";
|
|
73
72
|
export * from "./components/Polygon/Polygon";
|
|
74
|
-
export type { MenuTreeCloseEvent, MenuTreeOpenEvent, UseMenuTreeType } from "./components/Popover/MenuTree";
|
|
75
73
|
export { MenuTree, useMenuTree } from "./components/Popover/MenuTree";
|
|
74
|
+
export type { MenuTreeCloseEvent, MenuTreeOpenEvent, UseMenuTreeType } from "./components/Popover/MenuTree";
|
|
76
75
|
export * from "./components/Popover/Popover";
|
|
77
76
|
export * from "./components/Popover/PopoverContent";
|
|
78
77
|
export * from "./components/Popover/PopoverTitle";
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { type ReactElement } from "react";
|
|
2
|
-
import { CommonProps } from "../../common/CommonProps";
|
|
3
|
-
import type { Styleable } from "../../common/Styleable";
|
|
4
|
-
import { Refable } from "../../common/Refable";
|
|
5
|
-
export interface CopyableTextProps extends CommonProps, Styleable, Refable<HTMLButtonElement> {
|
|
6
|
-
/**
|
|
7
|
-
* The text displayed in the UI and copied to clipboard on click.
|
|
8
|
-
*/
|
|
9
|
-
text: string;
|
|
10
|
-
/**
|
|
11
|
-
* When true, renders a copy icon (Square2Stack) next to the text.
|
|
12
|
-
* The text is automatically truncated with an ellipsis.
|
|
13
|
-
*
|
|
14
|
-
* @default true
|
|
15
|
-
*/
|
|
16
|
-
withIcon?: boolean;
|
|
17
|
-
/**
|
|
18
|
-
* Controls the text size of the component.
|
|
19
|
-
* - `sm` (default): standard text size
|
|
20
|
-
* - `xs`: smaller text size
|
|
21
|
-
*
|
|
22
|
-
* @default "sm"
|
|
23
|
-
*/
|
|
24
|
-
size?: "sm" | "xs";
|
|
25
|
-
/**
|
|
26
|
-
* Accessible label prefix for screen readers. Rendered as `aria-label="{copyLabel} {text}"`.
|
|
27
|
-
*
|
|
28
|
-
* @default "Copy"
|
|
29
|
-
*/
|
|
30
|
-
copyLabel?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Label shown in the tooltip after a successful copy.
|
|
33
|
-
*
|
|
34
|
-
* @default "Copied!"
|
|
35
|
-
*/
|
|
36
|
-
copiedLabel?: string;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* CopyableText displays a text value that the user can click to copy to the clipboard.
|
|
40
|
-
* It shows a brief animation and tooltip feedback on copy. What you see is what gets copied.
|
|
41
|
-
*
|
|
42
|
-
* ### When to use
|
|
43
|
-
* Use CopyableText for identifiers, serial numbers, URLs, or any value the user may want to copy (e.g., asset IDs, error codes).
|
|
44
|
-
*
|
|
45
|
-
* ### When not to use
|
|
46
|
-
* - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
|
|
47
|
-
* - If the value is rendered as plain (non-clickable) text and only a dedicated icon should copy it
|
|
48
|
-
* (e.g. inside a row or cell that should stay clickable), use `CopyButton` instead:
|
|
49
|
-
* ```tsx
|
|
50
|
-
* <div className="flex items-center gap-0.5">
|
|
51
|
-
* <Text type="span">{value}</Text>
|
|
52
|
-
* <CopyButton value={value} />
|
|
53
|
-
* </div>
|
|
54
|
-
* ```
|
|
55
|
-
* - Only use `useCopyToClipboard` directly for genuinely custom copy behavior that neither `CopyableText` nor `CopyButton` supports.
|
|
56
|
-
*
|
|
57
|
-
* @example Copyable serial number
|
|
58
|
-
* ```tsx
|
|
59
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
60
|
-
*
|
|
61
|
-
* const AssetSerial = () => (
|
|
62
|
-
* <CopyableText text="SN-2024-00142" data-testid="serial-number" />
|
|
63
|
-
* );
|
|
64
|
-
* ```
|
|
65
|
-
* @example Copyable text with small size
|
|
66
|
-
* ```tsx
|
|
67
|
-
* import { CopyableText } from "@trackunit/react-components";
|
|
68
|
-
*
|
|
69
|
-
* const CopyableId = () => (
|
|
70
|
-
* <CopyableText text="ABC-123" size="xs" />
|
|
71
|
-
* );
|
|
72
|
-
* ```
|
|
73
|
-
* @param {CopyableTextProps} props - The props for the CopyableText component
|
|
74
|
-
* @returns {ReactElement} CopyableText component
|
|
75
|
-
*/
|
|
76
|
-
export declare const CopyableText: ({ text, withIcon, size, copyLabel, copiedLabel, "data-testid": dataTestId, className, style, ref, }: CopyableTextProps) => ReactElement | null;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export declare const cvaCopyableText: import("cva").CVAComponent<Omit<{
|
|
2
|
-
base: string[];
|
|
3
|
-
variants: {
|
|
4
|
-
animating: {
|
|
5
|
-
false: string;
|
|
6
|
-
true: string;
|
|
7
|
-
};
|
|
8
|
-
size: {
|
|
9
|
-
sm: string;
|
|
10
|
-
xs: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
defaultVariants: {
|
|
14
|
-
animating: false;
|
|
15
|
-
size: "sm";
|
|
16
|
-
};
|
|
17
|
-
}, "defaultVariants"> & {
|
|
18
|
-
variants: {
|
|
19
|
-
animating: {
|
|
20
|
-
false: string;
|
|
21
|
-
true: string;
|
|
22
|
-
};
|
|
23
|
-
size: {
|
|
24
|
-
sm: string;
|
|
25
|
-
xs: string;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
defaultVariants: Omit<{}, "size" | "animating"> & {
|
|
29
|
-
animating: false;
|
|
30
|
-
size: "sm";
|
|
31
|
-
};
|
|
32
|
-
}, {
|
|
33
|
-
animating: {
|
|
34
|
-
false: string;
|
|
35
|
-
true: string;
|
|
36
|
-
};
|
|
37
|
-
size: {
|
|
38
|
-
sm: string;
|
|
39
|
-
xs: string;
|
|
40
|
-
};
|
|
41
|
-
}>;
|