@trackunit/react-components 2.10.30-alpha-fa4af587a69.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +58 -9
- package/index.esm.js +58 -9
- package/package.json +6 -6
- package/src/components/CopyButton/CopyButton.d.ts +35 -12
- package/src/components/buttons/IconButton/IconButton.d.ts +5 -3
- package/src/components/buttons/shared/Button.variants.d.ts +2 -2
- package/src/components/buttons/shared/ButtonProps.d.ts +1 -1
- package/src/components/buttons/shared/IconButtonSize.d.ts +16 -0
- package/src/index.d.ts +1 -0
package/index.cjs.js
CHANGED
|
@@ -27,6 +27,8 @@ var dequal = require('dequal');
|
|
|
27
27
|
var defaultTranslations = {
|
|
28
28
|
"breadcrumb.backButton": "back",
|
|
29
29
|
"breadcrumb.expandButton": "expand",
|
|
30
|
+
"copyButton.copied": "Copied!",
|
|
31
|
+
"copyButton.copy": "Copy",
|
|
30
32
|
"moreMenu.buttonLabel": "Show more"
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -1753,7 +1755,6 @@ const cvaButtonSpinner = cssClassVarianceUtilities.cvaMerge([], {
|
|
|
1753
1755
|
const cvaButtonPrefixSuffix = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"], {
|
|
1754
1756
|
variants: {
|
|
1755
1757
|
size: {
|
|
1756
|
-
extraSmall: ["h-3"],
|
|
1757
1758
|
small: ["h-4"],
|
|
1758
1759
|
medium: ["h-4"],
|
|
1759
1760
|
large: ["h-5"],
|
|
@@ -1823,6 +1824,14 @@ const cvaButtonPrefixSuffix = cssClassVarianceUtilities.cvaMerge(["flex", "items
|
|
|
1823
1824
|
const cvaIconButton = cssClassVarianceUtilities.cvaMerge([], {
|
|
1824
1825
|
variants: {
|
|
1825
1826
|
size: {
|
|
1827
|
+
/**
|
|
1828
|
+
* Sized to match a single text line (20px) so the button fits inside
|
|
1829
|
+
* fixed-height contexts like table rows without clipping its hover state.
|
|
1830
|
+
*
|
|
1831
|
+
* `min-h-5` is what makes this an icon-only step: it overrides the
|
|
1832
|
+
* text-height floor `cvaButton` applies for every size on its own scale.
|
|
1833
|
+
*/
|
|
1834
|
+
extraSmall: ["p-0.5", "w-5", "h-5", "min-h-5"],
|
|
1826
1835
|
small: ["p-1.5", "w-7", "h-7"],
|
|
1827
1836
|
medium: ["p-2", "w-8", "h-8"],
|
|
1828
1837
|
large: ["p-2.5", "w-10", "h-10"],
|
|
@@ -2525,6 +2534,16 @@ function useMergeRefs(refs) {
|
|
|
2525
2534
|
}, [refEffect]);
|
|
2526
2535
|
}
|
|
2527
2536
|
|
|
2537
|
+
/**
|
|
2538
|
+
* Maps an icon-only size onto the text-button scale that `Button` understands.
|
|
2539
|
+
*
|
|
2540
|
+
* `extraSmall` has no text-button equivalent — its 20px box comes entirely from
|
|
2541
|
+
* `cvaIconButton`, which overrides padding, width and height. `small` is the step
|
|
2542
|
+
* whose remaining classes (`gap-1`, `text-xs`) already match, so it is what the
|
|
2543
|
+
* icon-only components hand to `Button`.
|
|
2544
|
+
*/
|
|
2545
|
+
const toButtonSize = (size) => (size === "extraSmall" ? "small" : size);
|
|
2546
|
+
|
|
2528
2547
|
/**
|
|
2529
2548
|
* Buttons are clickable elements that are used to trigger actions. They communicate calls to action to the user and allow users to interact with pages in a variety of ways. The Icon Button is a version of the standard Button component without the text label.
|
|
2530
2549
|
*
|
|
@@ -2569,7 +2588,7 @@ function useMergeRefs(refs) {
|
|
|
2569
2588
|
*/
|
|
2570
2589
|
const IconButton = ({ icon, size = "medium", square = true, loading = false, disabled = false, className, title, ariaLabel, "data-testid": dataTestId, ref, ...rest }) => {
|
|
2571
2590
|
const accessibleLabel = ariaLabel ?? title;
|
|
2572
|
-
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: size, square: square, title: undefined }) }));
|
|
2591
|
+
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 }) }));
|
|
2573
2592
|
};
|
|
2574
2593
|
IconButton.displayName = "IconButton";
|
|
2575
2594
|
|
|
@@ -4042,11 +4061,14 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4042
4061
|
* clickable/displayed element, so it can be placed next to plain (non-clickable) text.
|
|
4043
4062
|
*
|
|
4044
4063
|
* ### When to use
|
|
4045
|
-
*
|
|
4046
|
-
*
|
|
4064
|
+
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
4065
|
+
* row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
|
|
4066
|
+
* - Labelled (`label` set): copying is the primary action of a panel or section — e.g. an invite
|
|
4067
|
+
* link — and deserves a full button that is itself the click target.
|
|
4047
4068
|
*
|
|
4048
4069
|
* ### When not to use
|
|
4049
|
-
*
|
|
4070
|
+
* Do not hand-roll copy affordances (custom icons, click-to-copy text) — this component is the
|
|
4071
|
+
* one copy control; pick icon-only or labelled mode instead.
|
|
4050
4072
|
*
|
|
4051
4073
|
* ### Empty values
|
|
4052
4074
|
* Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
|
|
@@ -4066,21 +4088,48 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4066
4088
|
* @param {CopyButtonProps} props - The props for the CopyButton component
|
|
4067
4089
|
* @returns {ReactElement} CopyButton component
|
|
4068
4090
|
*/
|
|
4069
|
-
const CopyButton = ({ value, copyLabel
|
|
4091
|
+
const CopyButton = ({ value, copyLabel, copiedLabel, size = "small", label, variant = "ghost-neutral", className, style, "data-testid": dataTestId, onCopy, ref, }) => {
|
|
4092
|
+
const [t] = useTranslation();
|
|
4070
4093
|
const [showCopied, setShowCopied] = react.useState(false);
|
|
4071
4094
|
const [, copyToClipboard] = useCopyToClipboard();
|
|
4095
|
+
const resetTimerRef = react.useRef(undefined);
|
|
4096
|
+
react.useEffect(() => () => clearTimeout(resetTimerRef.current), []);
|
|
4097
|
+
// Localized defaults, so call sites don't each re-declare "Copy"/"Copied!"
|
|
4098
|
+
// in their own translation files; the props stay as context-specific overrides.
|
|
4099
|
+
const resolvedCopyLabel = copyLabel ?? t("copyButton.copy");
|
|
4100
|
+
const resolvedCopiedLabel = copiedLabel ?? t("copyButton.copied");
|
|
4072
4101
|
if (!value) {
|
|
4073
4102
|
return null;
|
|
4074
4103
|
}
|
|
4075
4104
|
const handleCopy = (e) => {
|
|
4076
4105
|
e.stopPropagation();
|
|
4077
4106
|
void copyToClipboard(value)
|
|
4078
|
-
|
|
4079
|
-
.
|
|
4107
|
+
// Rejections (both clipboard mechanisms failed) are intentionally
|
|
4108
|
+
// swallowed; the icon simply doesn't flip. A throwing onCopy is a
|
|
4109
|
+
// consumer bug and surfaces as an unhandled rejection instead.
|
|
4110
|
+
.then(() => {
|
|
4111
|
+
// Flip the icon to a checkmark for a moment, then revert. The timer
|
|
4112
|
+
// owns the copied state so the confirmation survives the pointer
|
|
4113
|
+
// leaving the button, and runs even if onCopy below throws.
|
|
4114
|
+
setShowCopied(true);
|
|
4115
|
+
clearTimeout(resetTimerRef.current);
|
|
4116
|
+
resetTimerRef.current = setTimeout(() => setShowCopied(false), COPIED_FEEDBACK_DURATION_MS);
|
|
4117
|
+
onCopy?.();
|
|
4118
|
+
}, () => undefined);
|
|
4080
4119
|
};
|
|
4081
|
-
|
|
4120
|
+
const icon = (jsxRuntime.jsx(Icon, { ariaHidden: true, name: showCopied ? "Check" : "Square2Stack", size: size === "large" ? "medium" : "small" }));
|
|
4121
|
+
if (label) {
|
|
4122
|
+
// Labelled mode: a regular (non-square) button whose prefix icon carries
|
|
4123
|
+
// the copied confirmation. `extraSmall` is icon-only, so it collapses to small.
|
|
4124
|
+
// The visible label stays the accessible name (WCAG 2.5.3 Label in Name);
|
|
4125
|
+
// since the copied feedback is otherwise visual-only, it is announced
|
|
4126
|
+
// through the polite live region rendered next to the button.
|
|
4127
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Tooltip, { delayed: !showCopied, label: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, placement: "top", children: jsxRuntime.jsx(Button, { className: className, "data-testid": dataTestId, onClick: handleCopy, prefix: icon, ref: ref, size: toButtonSize(size), style: style, variant: variant, children: label }) }), jsxRuntime.jsx("span", { className: "sr-only", role: "status", children: showCopied ? resolvedCopiedLabel : undefined })] }));
|
|
4128
|
+
}
|
|
4129
|
+
return (jsxRuntime.jsx(Tooltip, { delayed: !showCopied, label: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, placement: "top", children: jsxRuntime.jsx(Button, { ariaLabel: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, className: cvaIconButton({ size, className }), "data-testid": dataTestId, onClick: handleCopy, prefix: icon, ref: ref, size: toButtonSize(size), square: true, style: style, variant: variant }) }));
|
|
4082
4130
|
};
|
|
4083
4131
|
CopyButton.displayName = "CopyButton";
|
|
4132
|
+
const COPIED_FEEDBACK_DURATION_MS = 2000;
|
|
4084
4133
|
|
|
4085
4134
|
const cvaDetailsList = cssClassVarianceUtilities.cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-neutral-600", "font-medium", "pt-0"], {
|
|
4086
4135
|
variants: {
|
package/index.esm.js
CHANGED
|
@@ -26,6 +26,8 @@ import { dequal } from 'dequal';
|
|
|
26
26
|
var defaultTranslations = {
|
|
27
27
|
"breadcrumb.backButton": "back",
|
|
28
28
|
"breadcrumb.expandButton": "expand",
|
|
29
|
+
"copyButton.copied": "Copied!",
|
|
30
|
+
"copyButton.copy": "Copy",
|
|
29
31
|
"moreMenu.buttonLabel": "Show more"
|
|
30
32
|
};
|
|
31
33
|
|
|
@@ -1752,7 +1754,6 @@ const cvaButtonSpinner = cvaMerge([], {
|
|
|
1752
1754
|
const cvaButtonPrefixSuffix = cvaMerge(["flex", "items-center"], {
|
|
1753
1755
|
variants: {
|
|
1754
1756
|
size: {
|
|
1755
|
-
extraSmall: ["h-3"],
|
|
1756
1757
|
small: ["h-4"],
|
|
1757
1758
|
medium: ["h-4"],
|
|
1758
1759
|
large: ["h-5"],
|
|
@@ -1822,6 +1823,14 @@ const cvaButtonPrefixSuffix = cvaMerge(["flex", "items-center"], {
|
|
|
1822
1823
|
const cvaIconButton = cvaMerge([], {
|
|
1823
1824
|
variants: {
|
|
1824
1825
|
size: {
|
|
1826
|
+
/**
|
|
1827
|
+
* Sized to match a single text line (20px) so the button fits inside
|
|
1828
|
+
* fixed-height contexts like table rows without clipping its hover state.
|
|
1829
|
+
*
|
|
1830
|
+
* `min-h-5` is what makes this an icon-only step: it overrides the
|
|
1831
|
+
* text-height floor `cvaButton` applies for every size on its own scale.
|
|
1832
|
+
*/
|
|
1833
|
+
extraSmall: ["p-0.5", "w-5", "h-5", "min-h-5"],
|
|
1825
1834
|
small: ["p-1.5", "w-7", "h-7"],
|
|
1826
1835
|
medium: ["p-2", "w-8", "h-8"],
|
|
1827
1836
|
large: ["p-2.5", "w-10", "h-10"],
|
|
@@ -2524,6 +2533,16 @@ function useMergeRefs(refs) {
|
|
|
2524
2533
|
}, [refEffect]);
|
|
2525
2534
|
}
|
|
2526
2535
|
|
|
2536
|
+
/**
|
|
2537
|
+
* Maps an icon-only size onto the text-button scale that `Button` understands.
|
|
2538
|
+
*
|
|
2539
|
+
* `extraSmall` has no text-button equivalent — its 20px box comes entirely from
|
|
2540
|
+
* `cvaIconButton`, which overrides padding, width and height. `small` is the step
|
|
2541
|
+
* whose remaining classes (`gap-1`, `text-xs`) already match, so it is what the
|
|
2542
|
+
* icon-only components hand to `Button`.
|
|
2543
|
+
*/
|
|
2544
|
+
const toButtonSize = (size) => (size === "extraSmall" ? "small" : size);
|
|
2545
|
+
|
|
2527
2546
|
/**
|
|
2528
2547
|
* Buttons are clickable elements that are used to trigger actions. They communicate calls to action to the user and allow users to interact with pages in a variety of ways. The Icon Button is a version of the standard Button component without the text label.
|
|
2529
2548
|
*
|
|
@@ -2568,7 +2587,7 @@ function useMergeRefs(refs) {
|
|
|
2568
2587
|
*/
|
|
2569
2588
|
const IconButton = ({ icon, size = "medium", square = true, loading = false, disabled = false, className, title, ariaLabel, "data-testid": dataTestId, ref, ...rest }) => {
|
|
2570
2589
|
const accessibleLabel = ariaLabel ?? title;
|
|
2571
|
-
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: size, square: square, title: undefined }) }));
|
|
2590
|
+
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 }) }));
|
|
2572
2591
|
};
|
|
2573
2592
|
IconButton.displayName = "IconButton";
|
|
2574
2593
|
|
|
@@ -4041,11 +4060,14 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4041
4060
|
* clickable/displayed element, so it can be placed next to plain (non-clickable) text.
|
|
4042
4061
|
*
|
|
4043
4062
|
* ### When to use
|
|
4044
|
-
*
|
|
4045
|
-
*
|
|
4063
|
+
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
4064
|
+
* row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
|
|
4065
|
+
* - Labelled (`label` set): copying is the primary action of a panel or section — e.g. an invite
|
|
4066
|
+
* link — and deserves a full button that is itself the click target.
|
|
4046
4067
|
*
|
|
4047
4068
|
* ### When not to use
|
|
4048
|
-
*
|
|
4069
|
+
* Do not hand-roll copy affordances (custom icons, click-to-copy text) — this component is the
|
|
4070
|
+
* one copy control; pick icon-only or labelled mode instead.
|
|
4049
4071
|
*
|
|
4050
4072
|
* ### Empty values
|
|
4051
4073
|
* Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
|
|
@@ -4065,21 +4087,48 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
|
|
|
4065
4087
|
* @param {CopyButtonProps} props - The props for the CopyButton component
|
|
4066
4088
|
* @returns {ReactElement} CopyButton component
|
|
4067
4089
|
*/
|
|
4068
|
-
const CopyButton = ({ value, copyLabel
|
|
4090
|
+
const CopyButton = ({ value, copyLabel, copiedLabel, size = "small", label, variant = "ghost-neutral", className, style, "data-testid": dataTestId, onCopy, ref, }) => {
|
|
4091
|
+
const [t] = useTranslation();
|
|
4069
4092
|
const [showCopied, setShowCopied] = useState(false);
|
|
4070
4093
|
const [, copyToClipboard] = useCopyToClipboard();
|
|
4094
|
+
const resetTimerRef = useRef(undefined);
|
|
4095
|
+
useEffect(() => () => clearTimeout(resetTimerRef.current), []);
|
|
4096
|
+
// Localized defaults, so call sites don't each re-declare "Copy"/"Copied!"
|
|
4097
|
+
// in their own translation files; the props stay as context-specific overrides.
|
|
4098
|
+
const resolvedCopyLabel = copyLabel ?? t("copyButton.copy");
|
|
4099
|
+
const resolvedCopiedLabel = copiedLabel ?? t("copyButton.copied");
|
|
4071
4100
|
if (!value) {
|
|
4072
4101
|
return null;
|
|
4073
4102
|
}
|
|
4074
4103
|
const handleCopy = (e) => {
|
|
4075
4104
|
e.stopPropagation();
|
|
4076
4105
|
void copyToClipboard(value)
|
|
4077
|
-
|
|
4078
|
-
.
|
|
4106
|
+
// Rejections (both clipboard mechanisms failed) are intentionally
|
|
4107
|
+
// swallowed; the icon simply doesn't flip. A throwing onCopy is a
|
|
4108
|
+
// consumer bug and surfaces as an unhandled rejection instead.
|
|
4109
|
+
.then(() => {
|
|
4110
|
+
// Flip the icon to a checkmark for a moment, then revert. The timer
|
|
4111
|
+
// owns the copied state so the confirmation survives the pointer
|
|
4112
|
+
// leaving the button, and runs even if onCopy below throws.
|
|
4113
|
+
setShowCopied(true);
|
|
4114
|
+
clearTimeout(resetTimerRef.current);
|
|
4115
|
+
resetTimerRef.current = setTimeout(() => setShowCopied(false), COPIED_FEEDBACK_DURATION_MS);
|
|
4116
|
+
onCopy?.();
|
|
4117
|
+
}, () => undefined);
|
|
4079
4118
|
};
|
|
4080
|
-
|
|
4119
|
+
const icon = (jsx(Icon, { ariaHidden: true, name: showCopied ? "Check" : "Square2Stack", size: size === "large" ? "medium" : "small" }));
|
|
4120
|
+
if (label) {
|
|
4121
|
+
// Labelled mode: a regular (non-square) button whose prefix icon carries
|
|
4122
|
+
// the copied confirmation. `extraSmall` is icon-only, so it collapses to small.
|
|
4123
|
+
// The visible label stays the accessible name (WCAG 2.5.3 Label in Name);
|
|
4124
|
+
// since the copied feedback is otherwise visual-only, it is announced
|
|
4125
|
+
// through the polite live region rendered next to the button.
|
|
4126
|
+
return (jsxs(Fragment, { children: [jsx(Tooltip, { delayed: !showCopied, label: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, placement: "top", children: jsx(Button, { className: className, "data-testid": dataTestId, onClick: handleCopy, prefix: icon, ref: ref, size: toButtonSize(size), style: style, variant: variant, children: label }) }), jsx("span", { className: "sr-only", role: "status", children: showCopied ? resolvedCopiedLabel : undefined })] }));
|
|
4127
|
+
}
|
|
4128
|
+
return (jsx(Tooltip, { delayed: !showCopied, label: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, placement: "top", children: jsx(Button, { ariaLabel: showCopied ? resolvedCopiedLabel : resolvedCopyLabel, className: cvaIconButton({ size, className }), "data-testid": dataTestId, onClick: handleCopy, prefix: icon, ref: ref, size: toButtonSize(size), square: true, style: style, variant: variant }) }));
|
|
4081
4129
|
};
|
|
4082
4130
|
CopyButton.displayName = "CopyButton";
|
|
4131
|
+
const COPIED_FEEDBACK_DURATION_MS = 2000;
|
|
4083
4132
|
|
|
4084
4133
|
const cvaDetailsList = cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-neutral-600", "font-medium", "pt-0"], {
|
|
4085
4134
|
variants: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
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": "1.14.
|
|
19
|
-
"@trackunit/shared-utils": "1.16.
|
|
20
|
-
"@trackunit/ui-icons": "1.14.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.15.13",
|
|
18
|
+
"@trackunit/css-class-variance-utilities": "1.14.23",
|
|
19
|
+
"@trackunit/shared-utils": "1.16.26",
|
|
20
|
+
"@trackunit/ui-icons": "1.14.22",
|
|
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.34
|
|
26
|
+
"@trackunit/i18n-library-translation": "2.4.34"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"react": "^19.0.0",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { CommonProps } from "../../common/CommonProps";
|
|
3
3
|
import { Refable } from "../../common/Refable";
|
|
4
|
-
import { Size } from "../../common/Size";
|
|
5
4
|
import type { Styleable } from "../../common/Styleable";
|
|
5
|
+
import { ButtonVariant } from "../buttons/shared/ButtonProps";
|
|
6
|
+
import { IconButtonSize } from "../buttons/shared/IconButtonSize";
|
|
6
7
|
export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLButtonElement> {
|
|
7
8
|
/**
|
|
8
9
|
* The value copied to the clipboard when the button is clicked.
|
|
@@ -10,22 +11,41 @@ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLBut
|
|
|
10
11
|
value: string;
|
|
11
12
|
/**
|
|
12
13
|
* Accessible label for the button, and tooltip text shown before copying.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Defaults to the library's localized "Copy"; pass a value only when the
|
|
15
|
+
* context needs a more specific label (e.g. "Copy account ID").
|
|
15
16
|
*/
|
|
16
17
|
copyLabel?: string;
|
|
17
18
|
/**
|
|
18
|
-
* Tooltip text shown briefly after a successful copy.
|
|
19
|
-
*
|
|
20
|
-
* @default "Copied!"
|
|
19
|
+
* Tooltip text shown briefly after a successful copy. Defaults to the
|
|
20
|
+
* library's localized "Copied!".
|
|
21
21
|
*/
|
|
22
22
|
copiedLabel?: string;
|
|
23
23
|
/**
|
|
24
|
-
* The size of the icon button.
|
|
24
|
+
* The size of the icon button. Use `extraSmall` inside fixed-height contexts
|
|
25
|
+
* such as table rows, where the button must fit a single text line.
|
|
25
26
|
*
|
|
26
27
|
* @default "small"
|
|
27
28
|
*/
|
|
28
|
-
size?:
|
|
29
|
+
size?: IconButtonSize;
|
|
30
|
+
/**
|
|
31
|
+
* Optional visible label. When set, the button renders as a regular
|
|
32
|
+
* labelled button instead of an icon-only control — use this where copying
|
|
33
|
+
* is the primary action of a panel (e.g. invite links). The icon still
|
|
34
|
+
* flips to a checkmark while the copied confirmation is active.
|
|
35
|
+
*/
|
|
36
|
+
label?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The button's variant.
|
|
39
|
+
*
|
|
40
|
+
* @default "ghost-neutral"
|
|
41
|
+
*/
|
|
42
|
+
variant?: ButtonVariant;
|
|
43
|
+
/**
|
|
44
|
+
* Called after the value has been written to the clipboard — via the
|
|
45
|
+
* Clipboard API or the `execCommand("copy")` fallback. Only skipped when
|
|
46
|
+
* both mechanisms fail.
|
|
47
|
+
*/
|
|
48
|
+
onCopy?: () => void;
|
|
29
49
|
}
|
|
30
50
|
/**
|
|
31
51
|
* CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
|
|
@@ -33,11 +53,14 @@ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLBut
|
|
|
33
53
|
* clickable/displayed element, so it can be placed next to plain (non-clickable) text.
|
|
34
54
|
*
|
|
35
55
|
* ### When to use
|
|
36
|
-
*
|
|
37
|
-
*
|
|
56
|
+
* - Icon-only (default): the copyable value is displayed as plain text (e.g. in a table cell or
|
|
57
|
+
* row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
|
|
58
|
+
* - Labelled (`label` set): copying is the primary action of a panel or section — e.g. an invite
|
|
59
|
+
* link — and deserves a full button that is itself the click target.
|
|
38
60
|
*
|
|
39
61
|
* ### When not to use
|
|
40
|
-
*
|
|
62
|
+
* Do not hand-roll copy affordances (custom icons, click-to-copy text) — this component is the
|
|
63
|
+
* one copy control; pick icon-only or labelled mode instead.
|
|
41
64
|
*
|
|
42
65
|
* ### Empty values
|
|
43
66
|
* Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
|
|
@@ -58,6 +81,6 @@ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLBut
|
|
|
58
81
|
* @returns {ReactElement} CopyButton component
|
|
59
82
|
*/
|
|
60
83
|
export declare const CopyButton: {
|
|
61
|
-
({ value, copyLabel, copiedLabel, size, className, style, "data-testid": dataTestId, ref, }: CopyButtonProps): ReactElement | null;
|
|
84
|
+
({ value, copyLabel, copiedLabel, size, label, variant, className, style, "data-testid": dataTestId, onCopy, ref, }: CopyButtonProps): ReactElement | null;
|
|
62
85
|
displayName: string;
|
|
63
86
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MappedOmit } from "@trackunit/shared-utils";
|
|
2
2
|
import { ComponentType, JSX, ReactElement } from "react";
|
|
3
3
|
import { Refable } from "../../../common/Refable";
|
|
4
|
-
import { Size } from "../../../common/Size";
|
|
5
4
|
import { Icon, IconProps } from "../../Icon/Icon";
|
|
6
5
|
import { ButtonCommonProps } from "../shared/ButtonProps";
|
|
6
|
+
import { IconButtonSize } from "../shared/IconButtonSize";
|
|
7
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.
|
|
@@ -16,9 +16,11 @@ interface IconButtonBaseProps extends MappedOmit<ButtonCommonProps, "ariaLabel"
|
|
|
16
16
|
*/
|
|
17
17
|
renderAs?: keyof JSX.IntrinsicElements | ComponentType<any>;
|
|
18
18
|
/**
|
|
19
|
-
* The size of the button. Defaults to "medium".
|
|
19
|
+
* The size of the button. Defaults to "medium". Use `extraSmall` inside
|
|
20
|
+
* fixed-height contexts such as table rows, where the button must fit a
|
|
21
|
+
* single text line.
|
|
20
22
|
*/
|
|
21
|
-
size?:
|
|
23
|
+
size?: IconButtonSize;
|
|
22
24
|
}
|
|
23
25
|
type IconButtonAccessibleNameProps = {
|
|
24
26
|
/**
|
|
@@ -11,10 +11,10 @@ export declare const cvaButtonSpinner: (props?: ({
|
|
|
11
11
|
variant?: "filled" | "primary" | "primary-danger" | "secondary" | "secondary-danger" | "ghost" | "ghost-neutral" | null | undefined;
|
|
12
12
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
13
|
export declare const cvaButtonPrefixSuffix: (props?: ({
|
|
14
|
-
size?: "small" | "medium" | "large" |
|
|
14
|
+
size?: "small" | "medium" | "large" | null | undefined;
|
|
15
15
|
variant?: "filled" | "primary" | "primary-danger" | "secondary" | "secondary-danger" | "ghost" | "ghost-neutral" | null | undefined;
|
|
16
16
|
disabled?: boolean | null | undefined;
|
|
17
17
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
18
18
|
export declare const cvaIconButton: (props?: ({
|
|
19
|
-
size?: "small" | "medium" | "large" | null | undefined;
|
|
19
|
+
size?: "small" | "medium" | "large" | "extraSmall" | null | undefined;
|
|
20
20
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Size } from "../../../common/Size";
|
|
2
|
+
/**
|
|
3
|
+
* Size scale for icon-only buttons. Extends the regular button scale with
|
|
4
|
+
* `extraSmall` (20px), which matches a single text line so the button fits inside
|
|
5
|
+
* fixed-height contexts such as table rows without clipping.
|
|
6
|
+
*/
|
|
7
|
+
export type IconButtonSize = Size | "extraSmall";
|
|
8
|
+
/**
|
|
9
|
+
* Maps an icon-only size onto the text-button scale that `Button` understands.
|
|
10
|
+
*
|
|
11
|
+
* `extraSmall` has no text-button equivalent — its 20px box comes entirely from
|
|
12
|
+
* `cvaIconButton`, which overrides padding, width and height. `small` is the step
|
|
13
|
+
* whose remaining classes (`gap-1`, `text-xs`) already match, so it is what the
|
|
14
|
+
* icon-only components hand to `Button`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const toButtonSize: (size: IconButtonSize) => Size;
|
package/src/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./components/buttons/Button/Button";
|
|
|
13
13
|
export * from "./components/buttons/IconButton/IconButton";
|
|
14
14
|
export * from "./components/buttons/shared/Button.variants";
|
|
15
15
|
export * from "./components/buttons/shared/ButtonProps";
|
|
16
|
+
export type { IconButtonSize } from "./components/buttons/shared/IconButtonSize";
|
|
16
17
|
export * from "./components/buttons/StarButton/StarButton";
|
|
17
18
|
export * from "./components/Card/Card";
|
|
18
19
|
export * from "./components/Card/CardBody";
|