@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.
@@ -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;AAM9B,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;;;;;;;;;;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,mBAAmB,EACpB,EAAE,cAAc,qBAkKhB"}
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. No `font-mono`: the value
153
- // inherits the app sans font (Inter) so it matches every other field.
154
- className: "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" }), 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, {}) })] })] }));
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;AAM9B,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;;;;;;;;;;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,mBAAmB,EACpB,EAAE,cAAc,qBAkKhB"}
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. No `font-mono`: the value
117
- // inherits the app sans font (Inter) so it matches every other field.
118
- className: "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" }), 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, {}) })] })] }));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerianstudio/sindarian-ui",
3
- "version": "2.0.0-beta.12",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "Sindarian UI - A UI library for Midaz Console",
5
5
  "license": "ISC",
6
6
  "author": {