@lerianstudio/sindarian-ui 2.0.0-beta.12 → 2.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/copy-field/index.d.ts +25 -1
- package/dist/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/components/ui/copy-field/index.js +12 -7
- package/dist/esm/components/ui/copy-field/index.d.ts +25 -1
- package/dist/esm/components/ui/copy-field/index.d.ts.map +1 -1
- package/dist/esm/components/ui/copy-field/index.js +12 -7
- package/package.json +1 -1
|
@@ -39,6 +39,30 @@ export type CopyFieldProps = {
|
|
|
39
39
|
* @defaultValue 'Value to copy'
|
|
40
40
|
*/
|
|
41
41
|
valueLabel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Accessible name for the copy button. Override for localization. Defaults to
|
|
44
|
+
* English, derived from `label` when there is one.
|
|
45
|
+
* @defaultValue `Copy ${label}`, or 'Copy value' without a label
|
|
46
|
+
*/
|
|
47
|
+
copyLabel?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Toast text on the fallback path, when the clipboard API is unavailable or
|
|
50
|
+
* refuses the write and the value is selected for a manual Ctrl/Cmd+C
|
|
51
|
+
* instead. Override for localization. Defaults to English.
|
|
52
|
+
* @defaultValue 'Copy not available — text selected, press Ctrl/Cmd+C'
|
|
53
|
+
*/
|
|
54
|
+
fallbackLabel?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Renders the value in the monospace face. For identifiers, tokens and
|
|
57
|
+
* recovery codes, where character shapes must be told apart.
|
|
58
|
+
* @defaultValue false
|
|
59
|
+
*/
|
|
60
|
+
mono?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* One-line hint rendered under the field and announced with the input via
|
|
63
|
+
* `aria-describedby`. Omit for no element and no attribute.
|
|
64
|
+
*/
|
|
65
|
+
description?: React.ReactNode;
|
|
42
66
|
/**
|
|
43
67
|
* Milliseconds after a *successful* copy before the clipboard is wiped, for
|
|
44
68
|
* sensitive values that shouldn't linger there (TOTP secrets, recovery
|
|
@@ -52,5 +76,5 @@ export type CopyFieldProps = {
|
|
|
52
76
|
*/
|
|
53
77
|
clearClipboardAfter?: number;
|
|
54
78
|
};
|
|
55
|
-
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
79
|
+
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, copyLabel, fallbackLabel, mono, description, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
56
80
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAmCD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,KAAK,EACL,MAAc,EACd,WAAW,EACX,WAA0B,EAC1B,SAAwB,EACxB,UAA4B,EAC5B,SAAS,EACT,aAAa,EACb,IAAY,EACZ,WAAW,EACX,mBAAmB,EACpB,EAAE,cAAc,qBAqLhB"}
|
|
@@ -40,6 +40,7 @@ const React = __importStar(require("react"));
|
|
|
40
40
|
const lucide_react_1 = require("lucide-react");
|
|
41
41
|
const icon_button_1 = require("../../../components/ui/icon-button");
|
|
42
42
|
const use_toast_1 = require("../../../hooks/use-toast");
|
|
43
|
+
const utils_1 = require("../../../lib/utils");
|
|
43
44
|
const DEFAULT_COPY_LABEL = 'Copied to clipboard!';
|
|
44
45
|
const FALLBACK_COPY_LABEL = 'Copy not available — text selected, press Ctrl/Cmd+C';
|
|
45
46
|
const COPIED_ICON_TIMEOUT = 1500;
|
|
@@ -67,17 +68,18 @@ const clearClipboardIfUnchanged = async (copiedValue) => {
|
|
|
67
68
|
// Read or write refused; the clipboard stays as-is.
|
|
68
69
|
}
|
|
69
70
|
};
|
|
70
|
-
function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', clearClipboardAfter }) {
|
|
71
|
+
function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', copyLabel, fallbackLabel, mono = false, description, clearClipboardAfter }) {
|
|
71
72
|
const { toast } = (0, use_toast_1.useToast)();
|
|
72
73
|
const generatedId = React.useId();
|
|
73
74
|
const inputId = `${generatedId}-copy-field`;
|
|
75
|
+
const descriptionId = `${inputId}-description`;
|
|
74
76
|
const inputRef = React.useRef(null);
|
|
75
77
|
const copiedTimerRef = React.useRef(null);
|
|
76
78
|
const clearTimerRef = React.useRef(null);
|
|
77
79
|
const [revealed, setRevealed] = React.useState(false);
|
|
78
80
|
const [copied, setCopied] = React.useState(false);
|
|
79
81
|
const showAsText = !masked || revealed;
|
|
80
|
-
const copyAccessibleName = label ? `Copy ${label}` : 'Copy value';
|
|
82
|
+
const copyAccessibleName = copyLabel ?? (label ? `Copy ${label}` : 'Copy value');
|
|
81
83
|
React.useEffect(() => {
|
|
82
84
|
return () => {
|
|
83
85
|
if (copiedTimerRef.current) {
|
|
@@ -109,7 +111,7 @@ function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'S
|
|
|
109
111
|
};
|
|
110
112
|
const handleFallback = () => {
|
|
111
113
|
selectFieldText();
|
|
112
|
-
toast({ title: FALLBACK_COPY_LABEL });
|
|
114
|
+
toast({ title: fallbackLabel ?? FALLBACK_COPY_LABEL });
|
|
113
115
|
};
|
|
114
116
|
const handleCopy = async () => {
|
|
115
117
|
if (!isClipboardAvailable()) {
|
|
@@ -146,10 +148,13 @@ function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'S
|
|
|
146
148
|
// codes. While masked it renders as type="password", which invites
|
|
147
149
|
// browsers and password managers to autofill it or offer to SAVE its
|
|
148
150
|
// value, and spellcheck can ship the text to a remote service.
|
|
149
|
-
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel,
|
|
151
|
+
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel, "aria-describedby": description ? descriptionId : undefined,
|
|
150
152
|
// `min-w-0` lets this flex item shrink below its content width so a
|
|
151
153
|
// long value (e.g. a UUID recovery code) scrolls inside the field
|
|
152
|
-
// instead of overflowing its container.
|
|
153
|
-
//
|
|
154
|
-
|
|
154
|
+
// instead of overflowing its container. Sans by default, inheriting
|
|
155
|
+
// the app font (Inter) so the field matches every other input;
|
|
156
|
+
// `mono` opts identifiers, tokens and recovery codes into the
|
|
157
|
+
// monospace face, where telling 0 from O decides whether the value
|
|
158
|
+
// read back is the value.
|
|
159
|
+
className: (0, utils_1.cn)('text-input-foreground h-full min-w-0 flex-1 cursor-text border-none bg-transparent text-sm outline-none select-text focus:ring-0 focus:ring-offset-0', mono && 'font-mono') }), masked ? ((0, jsx_runtime_1.jsx)(icon_button_1.IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": revealed ? hideLabel : revealLabel, onClick: () => setRevealed((prev) => !prev), children: revealed ? (0, jsx_runtime_1.jsx)(lucide_react_1.EyeOff, {}) : (0, jsx_runtime_1.jsx)(lucide_react_1.Eye, {}) })) : null, (0, jsx_runtime_1.jsx)(icon_button_1.IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": copyAccessibleName, onClick: handleCopy, children: copied ? (0, jsx_runtime_1.jsx)(lucide_react_1.Check, {}) : (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, {}) })] }), description ? ((0, jsx_runtime_1.jsx)("p", { "data-slot": "copy-field-description", id: descriptionId, className: "text-muted-foreground text-xs", children: description })) : null] }));
|
|
155
160
|
}
|
|
@@ -39,6 +39,30 @@ export type CopyFieldProps = {
|
|
|
39
39
|
* @defaultValue 'Value to copy'
|
|
40
40
|
*/
|
|
41
41
|
valueLabel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Accessible name for the copy button. Override for localization. Defaults to
|
|
44
|
+
* English, derived from `label` when there is one.
|
|
45
|
+
* @defaultValue `Copy ${label}`, or 'Copy value' without a label
|
|
46
|
+
*/
|
|
47
|
+
copyLabel?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Toast text on the fallback path, when the clipboard API is unavailable or
|
|
50
|
+
* refuses the write and the value is selected for a manual Ctrl/Cmd+C
|
|
51
|
+
* instead. Override for localization. Defaults to English.
|
|
52
|
+
* @defaultValue 'Copy not available — text selected, press Ctrl/Cmd+C'
|
|
53
|
+
*/
|
|
54
|
+
fallbackLabel?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Renders the value in the monospace face. For identifiers, tokens and
|
|
57
|
+
* recovery codes, where character shapes must be told apart.
|
|
58
|
+
* @defaultValue false
|
|
59
|
+
*/
|
|
60
|
+
mono?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* One-line hint rendered under the field and announced with the input via
|
|
63
|
+
* `aria-describedby`. Omit for no element and no attribute.
|
|
64
|
+
*/
|
|
65
|
+
description?: React.ReactNode;
|
|
42
66
|
/**
|
|
43
67
|
* Milliseconds after a *successful* copy before the clipboard is wiped, for
|
|
44
68
|
* sensitive values that shouldn't linger there (TOTP secrets, recovery
|
|
@@ -52,5 +76,5 @@ export type CopyFieldProps = {
|
|
|
52
76
|
*/
|
|
53
77
|
clearClipboardAfter?: number;
|
|
54
78
|
};
|
|
55
|
-
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
79
|
+
export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, copyLabel, fallbackLabel, mono, description, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
|
|
56
80
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/copy-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAmCD,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,KAAK,EACL,MAAc,EACd,WAAW,EACX,WAA0B,EAC1B,SAAwB,EACxB,UAA4B,EAC5B,SAAS,EACT,aAAa,EACb,IAAY,EACZ,WAAW,EACX,mBAAmB,EACpB,EAAE,cAAc,qBAqLhB"}
|
|
@@ -4,6 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { Check, Copy, Eye, EyeOff } from 'lucide-react';
|
|
5
5
|
import { IconButton } from '../../../components/ui/icon-button/index.js';
|
|
6
6
|
import { useToast } from '../../../hooks/use-toast.js';
|
|
7
|
+
import { cn } from '../../../lib/utils.js';
|
|
7
8
|
const DEFAULT_COPY_LABEL = 'Copied to clipboard!';
|
|
8
9
|
const FALLBACK_COPY_LABEL = 'Copy not available — text selected, press Ctrl/Cmd+C';
|
|
9
10
|
const COPIED_ICON_TIMEOUT = 1500;
|
|
@@ -31,17 +32,18 @@ const clearClipboardIfUnchanged = async (copiedValue) => {
|
|
|
31
32
|
// Read or write refused; the clipboard stays as-is.
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
export function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', clearClipboardAfter }) {
|
|
35
|
+
export function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', copyLabel, fallbackLabel, mono = false, description, clearClipboardAfter }) {
|
|
35
36
|
const { toast } = useToast();
|
|
36
37
|
const generatedId = React.useId();
|
|
37
38
|
const inputId = `${generatedId}-copy-field`;
|
|
39
|
+
const descriptionId = `${inputId}-description`;
|
|
38
40
|
const inputRef = React.useRef(null);
|
|
39
41
|
const copiedTimerRef = React.useRef(null);
|
|
40
42
|
const clearTimerRef = React.useRef(null);
|
|
41
43
|
const [revealed, setRevealed] = React.useState(false);
|
|
42
44
|
const [copied, setCopied] = React.useState(false);
|
|
43
45
|
const showAsText = !masked || revealed;
|
|
44
|
-
const copyAccessibleName = label ? `Copy ${label}` : 'Copy value';
|
|
46
|
+
const copyAccessibleName = copyLabel ?? (label ? `Copy ${label}` : 'Copy value');
|
|
45
47
|
React.useEffect(() => {
|
|
46
48
|
return () => {
|
|
47
49
|
if (copiedTimerRef.current) {
|
|
@@ -73,7 +75,7 @@ export function CopyField({ value, label, masked = false, onCopyLabel, revealLab
|
|
|
73
75
|
};
|
|
74
76
|
const handleFallback = () => {
|
|
75
77
|
selectFieldText();
|
|
76
|
-
toast({ title: FALLBACK_COPY_LABEL });
|
|
78
|
+
toast({ title: fallbackLabel ?? FALLBACK_COPY_LABEL });
|
|
77
79
|
};
|
|
78
80
|
const handleCopy = async () => {
|
|
79
81
|
if (!isClipboardAvailable()) {
|
|
@@ -110,10 +112,13 @@ export function CopyField({ value, label, masked = false, onCopyLabel, revealLab
|
|
|
110
112
|
// codes. While masked it renders as type="password", which invites
|
|
111
113
|
// browsers and password managers to autofill it or offer to SAVE its
|
|
112
114
|
// value, and spellcheck can ship the text to a remote service.
|
|
113
|
-
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel,
|
|
115
|
+
autoComplete: "off", spellCheck: false, "aria-label": label ? undefined : valueLabel, "aria-describedby": description ? descriptionId : undefined,
|
|
114
116
|
// `min-w-0` lets this flex item shrink below its content width so a
|
|
115
117
|
// long value (e.g. a UUID recovery code) scrolls inside the field
|
|
116
|
-
// instead of overflowing its container.
|
|
117
|
-
//
|
|
118
|
-
|
|
118
|
+
// instead of overflowing its container. Sans by default, inheriting
|
|
119
|
+
// the app font (Inter) so the field matches every other input;
|
|
120
|
+
// `mono` opts identifiers, tokens and recovery codes into the
|
|
121
|
+
// monospace face, where telling 0 from O decides whether the value
|
|
122
|
+
// read back is the value.
|
|
123
|
+
className: cn('text-input-foreground h-full min-w-0 flex-1 cursor-text border-none bg-transparent text-sm outline-none select-text focus:ring-0 focus:ring-offset-0', mono && 'font-mono') }), masked ? (_jsx(IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": revealed ? hideLabel : revealLabel, onClick: () => setRevealed((prev) => !prev), children: revealed ? _jsx(EyeOff, {}) : _jsx(Eye, {}) })) : null, _jsx(IconButton, { type: "button", variant: "outline", size: "small", rounded: true, "aria-label": copyAccessibleName, onClick: handleCopy, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) })] }), description ? (_jsx("p", { "data-slot": "copy-field-description", id: descriptionId, className: "text-muted-foreground text-xs", children: description })) : null] }));
|
|
119
124
|
}
|