@trackunit/react-components 2.9.4 → 2.10.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 CHANGED
@@ -3979,7 +3979,15 @@ const Content = ({ text, withIcon, }) => {
3979
3979
  *
3980
3980
  * ### When not to use
3981
3981
  * - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
3982
- * - If you need a custom-looking copy button (different display text than copied value), use `useCopyToClipboard` directly.
3982
+ * - If the value is rendered as plain (non-clickable) text and only a dedicated icon should copy it
3983
+ * (e.g. inside a row or cell that should stay clickable), use `CopyButton` instead:
3984
+ * ```tsx
3985
+ * <div className="flex items-center gap-0.5">
3986
+ * <Text type="span">{value}</Text>
3987
+ * <CopyButton value={value} />
3988
+ * </div>
3989
+ * ```
3990
+ * - Only use `useCopyToClipboard` directly for genuinely custom copy behavior that neither `CopyableText` nor `CopyButton` supports.
3983
3991
  *
3984
3992
  * @example Copyable serial number
3985
3993
  * ```tsx
@@ -4028,6 +4036,52 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
4028
4036
  }, onMouseEnter: onMouseEnter, onMouseLeave: handleMouseLeave, ref: ref, style: style, type: "button", children: jsxRuntime.jsx(Content, { text: text, withIcon: withIcon }) }) }));
4029
4037
  };
4030
4038
 
4039
+ /**
4040
+ * CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
4041
+ * showing brief tooltip feedback. Unlike CopyableText, the copied value is not itself the
4042
+ * clickable/displayed element, so it can be placed next to plain (non-clickable) text.
4043
+ *
4044
+ * ### When to use
4045
+ * Use CopyButton when the copyable value is displayed as plain text (e.g. in a table cell or
4046
+ * row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
4047
+ *
4048
+ * ### When not to use
4049
+ * If the displayed text itself should be the click target, use `CopyableText` instead.
4050
+ *
4051
+ * ### Empty values
4052
+ * Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
4053
+ * present a copy control that has no meaningful result.
4054
+ *
4055
+ * @example Icon button next to plain text
4056
+ * ```tsx
4057
+ * import { CopyButton, Text } from "@trackunit/react-components";
4058
+ *
4059
+ * const ShortCode = ({ code }: { code: string }) => (
4060
+ * <div className="flex items-center gap-0.5">
4061
+ * <Text type="span">{code}</Text>
4062
+ * <CopyButton value={code} />
4063
+ * </div>
4064
+ * );
4065
+ * ```
4066
+ * @param {CopyButtonProps} props - The props for the CopyButton component
4067
+ * @returns {ReactElement} CopyButton component
4068
+ */
4069
+ const CopyButton = ({ value, copyLabel = "Copy", copiedLabel = "Copied!", size = "small", className, style, "data-testid": dataTestId, ref, }) => {
4070
+ const [showCopied, setShowCopied] = react.useState(false);
4071
+ const [, copyToClipboard] = useCopyToClipboard();
4072
+ if (!value) {
4073
+ return null;
4074
+ }
4075
+ const handleCopy = (e) => {
4076
+ e.stopPropagation();
4077
+ void copyToClipboard(value)
4078
+ .then(() => setShowCopied(true))
4079
+ .catch(() => undefined);
4080
+ };
4081
+ return (jsxRuntime.jsx(Tooltip, { delayed: !showCopied, label: showCopied ? copiedLabel : copyLabel, placement: "top", children: jsxRuntime.jsx(Button, { ariaLabel: copyLabel, className: cvaIconButton({ size, className }), "data-testid": dataTestId, onBlur: () => setShowCopied(false), onClick: handleCopy, onMouseLeave: () => setShowCopied(false), prefix: jsxRuntime.jsx(Icon, { ariaHidden: true, name: "Square2Stack", size: size === "large" ? "medium" : "small" }), ref: ref, size: size, square: true, style: style, variant: "ghost-neutral" }) }));
4082
+ };
4083
+ CopyButton.displayName = "CopyButton";
4084
+
4031
4085
  const cvaDetailsList = cssClassVarianceUtilities.cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-neutral-600", "font-medium", "pt-0"], {
4032
4086
  variants: {
4033
4087
  hasLink: {
@@ -14286,6 +14340,7 @@ exports.CardFooter = CardFooter;
14286
14340
  exports.CardHeader = CardHeader;
14287
14341
  exports.Collapse = Collapse;
14288
14342
  exports.CompletionStatusIndicator = CompletionStatusIndicator;
14343
+ exports.CopyButton = CopyButton;
14289
14344
  exports.CopyableText = CopyableText;
14290
14345
  exports.DEFAULT_SKELETON_PREFERENCE_CARD_PROPS = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS;
14291
14346
  exports.DetailsList = DetailsList;
package/index.esm.js CHANGED
@@ -3978,7 +3978,15 @@ const Content = ({ text, withIcon, }) => {
3978
3978
  *
3979
3979
  * ### When not to use
3980
3980
  * - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
3981
- * - If you need a custom-looking copy button (different display text than copied value), use `useCopyToClipboard` directly.
3981
+ * - If the value is rendered as plain (non-clickable) text and only a dedicated icon should copy it
3982
+ * (e.g. inside a row or cell that should stay clickable), use `CopyButton` instead:
3983
+ * ```tsx
3984
+ * <div className="flex items-center gap-0.5">
3985
+ * <Text type="span">{value}</Text>
3986
+ * <CopyButton value={value} />
3987
+ * </div>
3988
+ * ```
3989
+ * - Only use `useCopyToClipboard` directly for genuinely custom copy behavior that neither `CopyableText` nor `CopyButton` supports.
3982
3990
  *
3983
3991
  * @example Copyable serial number
3984
3992
  * ```tsx
@@ -4027,6 +4035,52 @@ const CopyableText = ({ text, withIcon = true, size = "sm", copyLabel = "Copy",
4027
4035
  }, onMouseEnter: onMouseEnter, onMouseLeave: handleMouseLeave, ref: ref, style: style, type: "button", children: jsx(Content, { text: text, withIcon: withIcon }) }) }));
4028
4036
  };
4029
4037
 
4038
+ /**
4039
+ * CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
4040
+ * showing brief tooltip feedback. Unlike CopyableText, the copied value is not itself the
4041
+ * clickable/displayed element, so it can be placed next to plain (non-clickable) text.
4042
+ *
4043
+ * ### When to use
4044
+ * Use CopyButton when the copyable value is displayed as plain text (e.g. in a table cell or
4045
+ * row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
4046
+ *
4047
+ * ### When not to use
4048
+ * If the displayed text itself should be the click target, use `CopyableText` instead.
4049
+ *
4050
+ * ### Empty values
4051
+ * Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
4052
+ * present a copy control that has no meaningful result.
4053
+ *
4054
+ * @example Icon button next to plain text
4055
+ * ```tsx
4056
+ * import { CopyButton, Text } from "@trackunit/react-components";
4057
+ *
4058
+ * const ShortCode = ({ code }: { code: string }) => (
4059
+ * <div className="flex items-center gap-0.5">
4060
+ * <Text type="span">{code}</Text>
4061
+ * <CopyButton value={code} />
4062
+ * </div>
4063
+ * );
4064
+ * ```
4065
+ * @param {CopyButtonProps} props - The props for the CopyButton component
4066
+ * @returns {ReactElement} CopyButton component
4067
+ */
4068
+ const CopyButton = ({ value, copyLabel = "Copy", copiedLabel = "Copied!", size = "small", className, style, "data-testid": dataTestId, ref, }) => {
4069
+ const [showCopied, setShowCopied] = useState(false);
4070
+ const [, copyToClipboard] = useCopyToClipboard();
4071
+ if (!value) {
4072
+ return null;
4073
+ }
4074
+ const handleCopy = (e) => {
4075
+ e.stopPropagation();
4076
+ void copyToClipboard(value)
4077
+ .then(() => setShowCopied(true))
4078
+ .catch(() => undefined);
4079
+ };
4080
+ return (jsx(Tooltip, { delayed: !showCopied, label: showCopied ? copiedLabel : copyLabel, placement: "top", children: jsx(Button, { ariaLabel: copyLabel, className: cvaIconButton({ size, className }), "data-testid": dataTestId, onBlur: () => setShowCopied(false), onClick: handleCopy, onMouseLeave: () => setShowCopied(false), prefix: jsx(Icon, { ariaHidden: true, name: "Square2Stack", size: size === "large" ? "medium" : "small" }), ref: ref, size: size, square: true, style: style, variant: "ghost-neutral" }) }));
4081
+ };
4082
+ CopyButton.displayName = "CopyButton";
4083
+
4030
4084
  const cvaDetailsList = cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-neutral-600", "font-medium", "pt-0"], {
4031
4085
  variants: {
4032
4086
  hasLink: {
@@ -14271,4 +14325,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
14271
14325
  */
14272
14326
  setupLibraryTranslations();
14273
14327
 
14274
- export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, 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, 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, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, 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 };
14328
+ export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyButton, CopyableText, 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, 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, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "2.9.4",
3
+ "version": "2.10.1",
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.3",
18
- "@trackunit/css-class-variance-utilities": "1.14.14",
19
- "@trackunit/shared-utils": "1.16.17",
20
- "@trackunit/ui-icons": "1.14.13",
17
+ "@trackunit/ui-design-tokens": "1.15.4",
18
+ "@trackunit/css-class-variance-utilities": "1.14.15",
19
+ "@trackunit/shared-utils": "1.16.18",
20
+ "@trackunit/ui-icons": "1.14.14",
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.10"
26
+ "@trackunit/i18n-library-translation": "2.4.11"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^19.0.0",
@@ -0,0 +1,63 @@
1
+ import { type ReactElement } from "react";
2
+ import { CommonProps } from "../../common/CommonProps";
3
+ import { Refable } from "../../common/Refable";
4
+ import { Size } from "../../common/Size";
5
+ import type { Styleable } from "../../common/Styleable";
6
+ export interface CopyButtonProps extends CommonProps, Styleable, Refable<HTMLButtonElement> {
7
+ /**
8
+ * The value copied to the clipboard when the button is clicked.
9
+ */
10
+ value: string;
11
+ /**
12
+ * Accessible label for the button, and tooltip text shown before copying.
13
+ *
14
+ * @default "Copy"
15
+ */
16
+ copyLabel?: string;
17
+ /**
18
+ * Tooltip text shown briefly after a successful copy.
19
+ *
20
+ * @default "Copied!"
21
+ */
22
+ copiedLabel?: string;
23
+ /**
24
+ * The size of the icon button.
25
+ *
26
+ * @default "small"
27
+ */
28
+ size?: Size;
29
+ }
30
+ /**
31
+ * CopyButton is a standalone icon button that copies a value to the clipboard when clicked,
32
+ * showing brief tooltip feedback. Unlike CopyableText, the copied value is not itself the
33
+ * clickable/displayed element, so it can be placed next to plain (non-clickable) text.
34
+ *
35
+ * ### When to use
36
+ * Use CopyButton when the copyable value is displayed as plain text (e.g. in a table cell or
37
+ * row) and only a dedicated icon should trigger the copy, keeping the rest of the row clickable.
38
+ *
39
+ * ### When not to use
40
+ * If the displayed text itself should be the click target, use `CopyableText` instead.
41
+ *
42
+ * ### Empty values
43
+ * Like `CopyableText`, CopyButton renders nothing when `value` is empty, so consumers never
44
+ * present a copy control that has no meaningful result.
45
+ *
46
+ * @example Icon button next to plain text
47
+ * ```tsx
48
+ * import { CopyButton, Text } from "@trackunit/react-components";
49
+ *
50
+ * const ShortCode = ({ code }: { code: string }) => (
51
+ * <div className="flex items-center gap-0.5">
52
+ * <Text type="span">{code}</Text>
53
+ * <CopyButton value={code} />
54
+ * </div>
55
+ * );
56
+ * ```
57
+ * @param {CopyButtonProps} props - The props for the CopyButton component
58
+ * @returns {ReactElement} CopyButton component
59
+ */
60
+ export declare const CopyButton: {
61
+ ({ value, copyLabel, copiedLabel, size, className, style, "data-testid": dataTestId, ref, }: CopyButtonProps): ReactElement | null;
62
+ displayName: string;
63
+ };
@@ -44,7 +44,15 @@ export interface CopyableTextProps extends CommonProps, Styleable, Refable<HTMLB
44
44
  *
45
45
  * ### When not to use
46
46
  * - Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
47
- * - If you need a custom-looking copy button (different display text than copied value), use `useCopyToClipboard` directly.
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.
48
56
  *
49
57
  * @example Copyable serial number
50
58
  * ```tsx
@@ -1,4 +1,4 @@
1
- import { MouseEventHandler } from "react";
1
+ import { FocusEventHandler, MouseEventHandler } from "react";
2
2
  import type { AriaProps } from "../../../common/AriaProps";
3
3
  import { CommonProps } from "../../../common/CommonProps";
4
4
  import { Size } from "../../../common/Size";
@@ -18,6 +18,10 @@ export interface ButtonCommonProps extends CommonProps, Styleable, AriaProps {
18
18
  * A MouseLeave handler function
19
19
  */
20
20
  onMouseLeave?: MouseEventHandler<HTMLButtonElement>;
21
+ /**
22
+ * A Blur handler function
23
+ */
24
+ onBlur?: FocusEventHandler<HTMLButtonElement>;
21
25
  /**
22
26
  * Whether the button is loading.
23
27
  */
package/src/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export * from "./components/Clickable/Clickable.variants";
22
22
  export * from "./components/Collapse/Collapse";
23
23
  export * from "./components/CompletionStatusIndicator/CompletionStatusIndicator";
24
24
  export * from "./components/CopyableText/CopyableText";
25
+ export * from "./components/CopyButton/CopyButton";
25
26
  export * from "./components/DetailsList/DetailsList";
26
27
  export * from "./components/EmptyState/EmptyState";
27
28
  export * from "./components/EmptyState/EmptyState.variants";