@lerianstudio/sindarian-ui 1.2.0-beta.10 → 1.2.0-beta.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/badge/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAKjE,QAAA,MAAM,aAAa;;8EA8BlB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GACnD,YAAY,CAAC,OAAO,aAAa,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE5D,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,qBAUnE;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/badge/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAKjE,QAAA,MAAM,aAAa;;8EA+BlB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GACnD,YAAY,CAAC,OAAO,aAAa,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE5D,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,UAAU,qBAUnE;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA"}
@@ -10,8 +10,8 @@ const badgeVariants = (0, class_variance_authority_1.cva)('inline-flex items-cen
10
10
  variants: {
11
11
  variant: {
12
12
  default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
13
- active: 'bg-system-success-surface text-system-success-h1a border-none py-[2px] px-3',
14
- inactive: 'bg-muted text-foreground border-none py-[2px] px-3',
13
+ active: 'bg-system-success-surface text-system-success-h1a border-system-success-border py-[2px] px-3',
14
+ inactive: 'bg-muted text-foreground border-muted-foreground/40 py-[2px] px-3',
15
15
  secondary: 'border-transparent bg-muted-foreground text-white dark:text-black',
16
16
  destructive: 'border-transparent bg-red-500 text-primary-foreground hover:bg-red-500/80',
17
17
  error: 'border-system-error-border bg-system-error-surface text-system-error-text px-[10px] py-1',
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=copy-field.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-field.test.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/copy-field/copy-field.test.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ require("@testing-library/jest-dom");
5
+ const react_1 = require("@testing-library/react");
6
+ const _1 = require(".");
7
+ const mockToast = jest.fn();
8
+ jest.mock('@/hooks/use-toast', () => ({
9
+ useToast: () => ({ toast: mockToast })
10
+ }));
11
+ const FALLBACK_COPY_LABEL = 'Copy not available — text selected, press Ctrl/Cmd+C';
12
+ const writeText = jest.fn().mockResolvedValue(undefined);
13
+ const readText = jest.fn().mockResolvedValue('');
14
+ const setClipboard = (value) => {
15
+ Object.defineProperty(navigator, 'clipboard', {
16
+ value,
17
+ configurable: true,
18
+ writable: true
19
+ });
20
+ };
21
+ beforeEach(() => {
22
+ mockToast.mockClear();
23
+ writeText.mockClear();
24
+ writeText.mockResolvedValue(undefined);
25
+ readText.mockClear();
26
+ readText.mockResolvedValue('');
27
+ setClipboard({ writeText, readText });
28
+ });
29
+ describe('CopyField', () => {
30
+ it('renders the value in a read-only input', () => {
31
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret" }));
32
+ const input = react_1.screen.getByLabelText('Secret');
33
+ expect(input).toHaveValue('SECRET123');
34
+ expect(input).toHaveAttribute('readonly');
35
+ });
36
+ it('associates the visible label with the input via htmlFor/id', () => {
37
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", label: "API Key" }));
38
+ expect(react_1.screen.getByLabelText('API Key')).toBeInTheDocument();
39
+ });
40
+ it('copies the value to the clipboard when the copy button is clicked', async () => {
41
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret" }));
42
+ await (0, react_1.act)(async () => {
43
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy secret/i }));
44
+ });
45
+ expect(writeText).toHaveBeenCalledWith('SECRET123');
46
+ });
47
+ it('fires a success toast with the provided onCopyLabel after copying', async () => {
48
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", onCopyLabel: "Secret copied!" }));
49
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy secret/i }));
50
+ await (0, react_1.waitFor)(() => expect(mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: 'success', title: 'Secret copied!' })));
51
+ });
52
+ it('uses the default copy label when onCopyLabel is not provided', async () => {
53
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc" }));
54
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy/i }));
55
+ await (0, react_1.waitFor)(() => expect(mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: 'Copied to clipboard!' })));
56
+ });
57
+ it('gives the copy button an accessible name even without a label', () => {
58
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc" }));
59
+ expect(react_1.screen.getByRole('button', { name: /copy/i })).toBeInTheDocument();
60
+ });
61
+ it('overrides the input aria-label via valueLabel when no visible label', () => {
62
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", valueLabel: "Valor para copiar" }));
63
+ expect(react_1.screen.getByLabelText('Valor para copiar')).toBeInTheDocument();
64
+ });
65
+ describe('masked', () => {
66
+ it('obscures the displayed value while keeping the real value copyable', async () => {
67
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "TOTPSECRET", label: "Secret", masked: true }));
68
+ const input = react_1.screen.getByLabelText('Secret');
69
+ expect(input).toHaveAttribute('type', 'password');
70
+ expect(input).toHaveValue('TOTPSECRET');
71
+ await (0, react_1.act)(async () => {
72
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy secret/i }));
73
+ });
74
+ expect(writeText).toHaveBeenCalledWith('TOTPSECRET');
75
+ });
76
+ it('reveals and then hides the value with the reveal toggle (aria-pressed)', () => {
77
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "s3cr3t", label: "Secret", masked: true }));
78
+ const input = react_1.screen.getByLabelText('Secret');
79
+ expect(input).toHaveAttribute('type', 'password');
80
+ const toggle = react_1.screen.getByRole('button', { name: /show value/i });
81
+ expect(toggle).toHaveAttribute('aria-pressed', 'false');
82
+ react_1.fireEvent.click(toggle);
83
+ expect(input).toHaveAttribute('type', 'text');
84
+ const hideToggle = react_1.screen.getByRole('button', { name: /hide value/i });
85
+ expect(hideToggle).toHaveAttribute('aria-pressed', 'true');
86
+ react_1.fireEvent.click(hideToggle);
87
+ expect(input).toHaveAttribute('type', 'password');
88
+ expect(react_1.screen.getByRole('button', { name: /show value/i })).toHaveAttribute('aria-pressed', 'false');
89
+ });
90
+ it('supports localized reveal/hide labels', () => {
91
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "s3cr3t", label: "Secret", masked: true, revealLabel: "Mostrar valor", hideLabel: "Ocultar valor" }));
92
+ const toggle = react_1.screen.getByRole('button', { name: 'Mostrar valor' });
93
+ react_1.fireEvent.click(toggle);
94
+ expect(react_1.screen.getByRole('button', { name: 'Ocultar valor' })).toBeInTheDocument();
95
+ });
96
+ it('does not render a reveal toggle when not masked', () => {
97
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", label: "Token" }));
98
+ expect(react_1.screen.queryByRole('button', { name: /show value/i })).not.toBeInTheDocument();
99
+ });
100
+ });
101
+ describe('clipboard unavailable fallback', () => {
102
+ it('selects the input text and toasts the fallback label when clipboard API is absent', async () => {
103
+ setClipboard(undefined);
104
+ const selectSpy = jest.spyOn(HTMLInputElement.prototype, 'select');
105
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", label: "Token" }));
106
+ expect(() => react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy token/i }))).not.toThrow();
107
+ expect(selectSpy).toHaveBeenCalled();
108
+ await (0, react_1.waitFor)(() => expect(mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: FALLBACK_COPY_LABEL })));
109
+ selectSpy.mockRestore();
110
+ });
111
+ it('fires the fallback toast and NOT the success toast when writeText rejects', async () => {
112
+ writeText.mockRejectedValueOnce(new Error('permission denied'));
113
+ const selectSpy = jest.spyOn(HTMLInputElement.prototype, 'select');
114
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", label: "Token" }));
115
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy token/i }));
116
+ await (0, react_1.waitFor)(() => expect(selectSpy).toHaveBeenCalled());
117
+ await (0, react_1.waitFor)(() => expect(mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: FALLBACK_COPY_LABEL })));
118
+ expect(mockToast).not.toHaveBeenCalledWith(expect.objectContaining({ variant: 'success' }));
119
+ selectSpy.mockRestore();
120
+ });
121
+ it('makes a masked field textual (selectable) before the fallback selection when clipboard is absent', async () => {
122
+ setClipboard(undefined);
123
+ const selectSpy = jest.spyOn(HTMLInputElement.prototype, 'select');
124
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "TOTPSECRET", label: "Secret", masked: true }));
125
+ const input = react_1.screen.getByLabelText('Secret');
126
+ expect(input).toHaveAttribute('type', 'password');
127
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy secret/i }));
128
+ // The field must be text (selectable) at/after the fallback, and it must
129
+ // still hold the real secret so Ctrl/Cmd+C copies the true value.
130
+ expect(input).toHaveAttribute('type', 'text');
131
+ expect(input).toHaveValue('TOTPSECRET');
132
+ expect(selectSpy).toHaveBeenCalled();
133
+ selectSpy.mockRestore();
134
+ });
135
+ it('makes a masked field textual before the fallback selection when writeText rejects', async () => {
136
+ writeText.mockRejectedValueOnce(new Error('permission denied'));
137
+ const selectSpy = jest.spyOn(HTMLInputElement.prototype, 'select');
138
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "TOTPSECRET", label: "Secret", masked: true }));
139
+ const input = react_1.screen.getByLabelText('Secret');
140
+ expect(input).toHaveAttribute('type', 'password');
141
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name: /copy secret/i }));
142
+ await (0, react_1.waitFor)(() => expect(selectSpy).toHaveBeenCalled());
143
+ expect(input).toHaveAttribute('type', 'text');
144
+ expect(input).toHaveValue('TOTPSECRET');
145
+ selectSpy.mockRestore();
146
+ });
147
+ });
148
+ describe('clearClipboardAfter', () => {
149
+ const CLEAR_AFTER = 30_000;
150
+ beforeEach(() => {
151
+ jest.useFakeTimers();
152
+ });
153
+ afterEach(() => {
154
+ jest.runOnlyPendingTimers();
155
+ jest.useRealTimers();
156
+ });
157
+ /** Runs the pending clear timer and drains its await chain. */
158
+ const runClearTimer = async () => {
159
+ await (0, react_1.act)(async () => {
160
+ jest.advanceTimersByTime(CLEAR_AFTER);
161
+ });
162
+ // readText -> comparison -> writeText: two extra microtask turns.
163
+ await (0, react_1.act)(async () => {
164
+ await Promise.resolve();
165
+ await Promise.resolve();
166
+ });
167
+ };
168
+ const copy = async (name) => {
169
+ await (0, react_1.act)(async () => {
170
+ react_1.fireEvent.click(react_1.screen.getByRole('button', { name }));
171
+ });
172
+ };
173
+ it('wipes the clipboard after the delay when it still holds the copied value', async () => {
174
+ readText.mockResolvedValue('SECRET123');
175
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
176
+ await copy(/copy secret/i);
177
+ expect(writeText).toHaveBeenCalledWith('SECRET123');
178
+ // Nothing cleared before the delay elapses.
179
+ expect(writeText).toHaveBeenCalledTimes(1);
180
+ await runClearTimer();
181
+ expect(readText).toHaveBeenCalled();
182
+ expect(writeText).toHaveBeenLastCalledWith('');
183
+ });
184
+ it('leaves the clipboard untouched when it now holds an unrelated value', async () => {
185
+ readText.mockResolvedValue('something the user copied afterwards');
186
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
187
+ await copy(/copy secret/i);
188
+ await runClearTimer();
189
+ expect(readText).toHaveBeenCalled();
190
+ expect(writeText).toHaveBeenCalledTimes(1);
191
+ expect(writeText).toHaveBeenLastCalledWith('SECRET123');
192
+ });
193
+ it('clears against the value copied at click time, not a later prop value', async () => {
194
+ readText.mockResolvedValue('FIRST');
195
+ const { rerender } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "FIRST", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
196
+ await copy(/copy secret/i);
197
+ rerender((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECOND", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
198
+ await runClearTimer();
199
+ expect(writeText).toHaveBeenLastCalledWith('');
200
+ });
201
+ it('does not clear when the clipboard cannot be read back', async () => {
202
+ setClipboard({ writeText });
203
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
204
+ await copy(/copy secret/i);
205
+ await runClearTimer();
206
+ expect(writeText).toHaveBeenCalledTimes(1);
207
+ expect(writeText).toHaveBeenLastCalledWith('SECRET123');
208
+ });
209
+ it('does not clear when readText rejects', async () => {
210
+ readText.mockRejectedValue(new Error('permission denied'));
211
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
212
+ await copy(/copy secret/i);
213
+ await runClearTimer();
214
+ expect(writeText).toHaveBeenCalledTimes(1);
215
+ });
216
+ it('does not schedule a clear when the copy itself failed', async () => {
217
+ writeText.mockRejectedValue(new Error('permission denied'));
218
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
219
+ await copy(/copy secret/i);
220
+ await runClearTimer();
221
+ expect(readText).not.toHaveBeenCalled();
222
+ });
223
+ it('never clears when the prop is omitted or non-positive', async () => {
224
+ const { unmount } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret" }));
225
+ await copy(/copy secret/i);
226
+ await runClearTimer();
227
+ expect(readText).not.toHaveBeenCalled();
228
+ unmount();
229
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: 0 }));
230
+ await copy(/copy secret/i);
231
+ await runClearTimer();
232
+ expect(readText).not.toHaveBeenCalled();
233
+ });
234
+ it('still clears after the field unmounts before the delay elapses', async () => {
235
+ readText.mockResolvedValue('SECRET123');
236
+ const { unmount } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "SECRET123", label: "Secret", clearClipboardAfter: CLEAR_AFTER }));
237
+ await copy(/copy secret/i);
238
+ unmount();
239
+ await runClearTimer();
240
+ expect(writeText).toHaveBeenLastCalledWith('');
241
+ });
242
+ });
243
+ describe('copied-indicator timer', () => {
244
+ beforeEach(() => {
245
+ jest.useFakeTimers();
246
+ });
247
+ afterEach(() => {
248
+ jest.runOnlyPendingTimers();
249
+ jest.useRealTimers();
250
+ });
251
+ it('does not throw on rapid double-copy or unmount before the timeout', async () => {
252
+ const { unmount } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.CopyField, { value: "abc", label: "Token" }));
253
+ const button = react_1.screen.getByRole('button', { name: /copy token/i });
254
+ await (0, react_1.act)(async () => {
255
+ react_1.fireEvent.click(button);
256
+ });
257
+ await (0, react_1.act)(async () => {
258
+ react_1.fireEvent.click(button);
259
+ });
260
+ // Unmount before the copied-icon timeout fires; the cleanup effect must
261
+ // clear the pending timer so no setState-after-unmount occurs.
262
+ expect(() => {
263
+ unmount();
264
+ jest.advanceTimersByTime(2000);
265
+ }).not.toThrow();
266
+ });
267
+ });
268
+ });
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ export type CopyFieldProps = {
3
+ /**
4
+ * The string shown and copied verbatim (e.g. a TOTP manual-entry secret or a
5
+ * single recovery code). Copy always uses this raw value, even while masked.
6
+ */
7
+ value: string;
8
+ /**
9
+ * Optional visible label rendered above the field and associated with the
10
+ * input via `htmlFor`/`id`.
11
+ */
12
+ label?: string;
13
+ /**
14
+ * When true the displayed value is obscured (native password dots) while the
15
+ * underlying `value` stays copyable. A reveal toggle is offered.
16
+ * @defaultValue false
17
+ */
18
+ masked?: boolean;
19
+ /**
20
+ * i18n message used as the success toast text. Falls back to a sensible
21
+ * default when omitted.
22
+ */
23
+ onCopyLabel?: string;
24
+ /**
25
+ * Accessible name for the reveal toggle when the value is hidden. Override
26
+ * for localization. Defaults to English.
27
+ * @defaultValue 'Show value'
28
+ */
29
+ revealLabel?: string;
30
+ /**
31
+ * Accessible name for the reveal toggle when the value is shown. Override
32
+ * for localization. Defaults to English.
33
+ * @defaultValue 'Hide value'
34
+ */
35
+ hideLabel?: string;
36
+ /**
37
+ * Accessible name for the input when no visible `label` is provided. Override
38
+ * for localization. Defaults to English.
39
+ * @defaultValue 'Value to copy'
40
+ */
41
+ valueLabel?: string;
42
+ /**
43
+ * Milliseconds after a *successful* copy before the clipboard is wiped, for
44
+ * sensitive values that shouldn't linger there (TOTP secrets, recovery
45
+ * codes). Omit — or pass a non-positive number — to never clear.
46
+ *
47
+ * The clear is best-effort and never destructive: before wiping, the
48
+ * clipboard is read back and left untouched unless it still holds exactly
49
+ * the value this field wrote, so a copy the user made in the meantime
50
+ * survives. Browsers without `clipboard.readText` (Firefox, Safari) or that
51
+ * deny the read can't be verified, so nothing is cleared there.
52
+ */
53
+ clearClipboardAfter?: number;
54
+ };
55
+ export declare function CopyField({ value, label, masked, onCopyLabel, revealLabel, hideLabel, valueLabel, clearClipboardAfter }: CopyFieldProps): React.JSX.Element;
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,qBAuJhB"}
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.CopyField = CopyField;
38
+ const jsx_runtime_1 = require("react/jsx-runtime");
39
+ const React = __importStar(require("react"));
40
+ const lucide_react_1 = require("lucide-react");
41
+ const icon_button_1 = require("../../../components/ui/icon-button");
42
+ const use_toast_1 = require("../../../hooks/use-toast");
43
+ const DEFAULT_COPY_LABEL = 'Copied to clipboard!';
44
+ const FALLBACK_COPY_LABEL = 'Copy not available — text selected, press Ctrl/Cmd+C';
45
+ const COPIED_ICON_TIMEOUT = 1500;
46
+ const isClipboardAvailable = () => typeof navigator !== 'undefined' &&
47
+ typeof navigator.clipboard?.writeText === 'function';
48
+ const isClipboardReadable = () => typeof navigator !== 'undefined' &&
49
+ typeof navigator.clipboard?.readText === 'function';
50
+ /**
51
+ * Wipes the clipboard, but only if it still holds `copiedValue` — the user may
52
+ * have copied something else in the meantime, and clobbering that would be
53
+ * worse than leaving the secret behind. Unverifiable (no `readText`, permission
54
+ * denied, unfocused document) means "leave it alone".
55
+ */
56
+ const clearClipboardIfUnchanged = async (copiedValue) => {
57
+ if (!isClipboardReadable()) {
58
+ return;
59
+ }
60
+ try {
61
+ if ((await navigator.clipboard.readText()) !== copiedValue) {
62
+ return;
63
+ }
64
+ await navigator.clipboard.writeText('');
65
+ }
66
+ catch {
67
+ // Read or write refused; the clipboard stays as-is.
68
+ }
69
+ };
70
+ function CopyField({ value, label, masked = false, onCopyLabel, revealLabel = 'Show value', hideLabel = 'Hide value', valueLabel = 'Value to copy', clearClipboardAfter }) {
71
+ const { toast } = (0, use_toast_1.useToast)();
72
+ const generatedId = React.useId();
73
+ const inputId = `${generatedId}-copy-field`;
74
+ const inputRef = React.useRef(null);
75
+ const copiedTimerRef = React.useRef(null);
76
+ const clearTimerRef = React.useRef(null);
77
+ const [revealed, setRevealed] = React.useState(false);
78
+ const [copied, setCopied] = React.useState(false);
79
+ const showAsText = !masked || revealed;
80
+ const copyAccessibleName = label ? `Copy ${label}` : 'Copy value';
81
+ React.useEffect(() => {
82
+ return () => {
83
+ if (copiedTimerRef.current) {
84
+ clearTimeout(copiedTimerRef.current);
85
+ }
86
+ // The clipboard-clear timer is deliberately *not* cancelled here: it
87
+ // touches no state (so it can't warn about an unmounted update) and the
88
+ // secret must still leave the clipboard when the dialog holding this
89
+ // field closes before the timeout elapses.
90
+ };
91
+ }, []);
92
+ const selectFieldText = () => {
93
+ const input = inputRef.current;
94
+ if (!input) {
95
+ return;
96
+ }
97
+ // Reveal so the user can copy the selection manually. Flip the DOM `type`
98
+ // imperatively *before* select(): `setRevealed` is batched, so a plain
99
+ // state update would leave the input as type="password" at select() time,
100
+ // and React's subsequent type -> "text" re-render clears the selection —
101
+ // the fallback would then select nothing. We still push the state update so
102
+ // the re-render stays consistent with the DOM we just mutated.
103
+ if (masked) {
104
+ input.type = 'text';
105
+ setRevealed(true);
106
+ }
107
+ input.focus();
108
+ input.select();
109
+ };
110
+ const handleFallback = () => {
111
+ selectFieldText();
112
+ toast({ title: FALLBACK_COPY_LABEL });
113
+ };
114
+ const handleCopy = async () => {
115
+ if (!isClipboardAvailable()) {
116
+ handleFallback();
117
+ return;
118
+ }
119
+ try {
120
+ await navigator.clipboard.writeText(value);
121
+ toast({ variant: 'success', title: onCopyLabel ?? DEFAULT_COPY_LABEL });
122
+ setCopied(true);
123
+ if (copiedTimerRef.current) {
124
+ clearTimeout(copiedTimerRef.current);
125
+ }
126
+ copiedTimerRef.current = setTimeout(() => setCopied(false), COPIED_ICON_TIMEOUT);
127
+ // Only a confirmed write earns a clear — the fallback path never wrote to
128
+ // the clipboard, so it has nothing of ours to wipe. `value` is captured
129
+ // per copy so a later prop change can't make us clear the wrong string.
130
+ if (clearTimerRef.current) {
131
+ clearTimeout(clearTimerRef.current);
132
+ }
133
+ if (clearClipboardAfter !== undefined && clearClipboardAfter > 0) {
134
+ const copiedValue = value;
135
+ clearTimerRef.current = setTimeout(() => {
136
+ void clearClipboardIfUnchanged(copiedValue);
137
+ }, clearClipboardAfter);
138
+ }
139
+ }
140
+ catch {
141
+ handleFallback();
142
+ }
143
+ };
144
+ return ((0, jsx_runtime_1.jsxs)("div", { "data-slot": "copy-field", className: "flex w-full flex-col gap-2", children: [label ? ((0, jsx_runtime_1.jsx)("label", { htmlFor: inputId, className: "text-muted-foreground text-sm font-semibold", children: label })) : null, (0, jsx_runtime_1.jsxs)("div", { "data-slot": "input-wrapper", className: "border-input-border focus-within:border-ring flex h-10 w-full cursor-text items-center gap-1 rounded-md border pr-1 pl-4 transition-[color,box-shadow]", children: [(0, jsx_runtime_1.jsx)("input", { ref: inputRef, id: inputId, "data-slot": "input", "data-testid": "copy-field-input", type: showAsText ? 'text' : 'password', value: value, readOnly: true, "aria-label": label ? undefined : valueLabel,
145
+ // `min-w-0` lets this flex item shrink below its content width so a
146
+ // long value (e.g. a UUID recovery code) scrolls inside the field
147
+ // instead of overflowing its container. No `font-mono`: the value
148
+ // inherits the app sans font (Inter) so it matches every other field.
149
+ 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-pressed": revealed, "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, {}) })] })] }));
150
+ }
@@ -1,33 +1,33 @@
1
1
  @theme inline {
2
- --spacing-icon-button-p: calc(var(--spacing) * 2)
2
+ --spacing-icon-button-p: calc(var(--spacing) * 2);
3
3
  }
4
4
 
5
5
  @layer components {
6
- .icon-button-base {
7
- @apply p-icon-button-p rounded-md size-10 [&>*]:size-6;
8
- }
6
+ .icon-button-base {
7
+ @apply p-icon-button-p size-10 rounded-md [&>*]:size-6 [&>*]:shrink-0;
8
+ }
9
9
 
10
- .icon-button-read-only {
11
- @apply data-[read-only=true]:border data-[read-only=true]:border-button-border;
12
- }
10
+ .icon-button-read-only {
11
+ @apply data-[read-only=true]:border-button-border data-[read-only=true]:border;
12
+ }
13
13
 
14
- .icon-button-disabled {
15
- @apply disabled:border disabled:border-button-border;
16
- }
14
+ .icon-button-disabled {
15
+ @apply disabled:border-button-border disabled:border;
16
+ }
17
17
 
18
- .icon-button-small {
19
- @apply size-8 [&>*]:size-4;
20
- }
18
+ .icon-button-small {
19
+ @apply size-8 [&>*]:size-4;
20
+ }
21
21
 
22
- .icon-button-rounded {
23
- @apply rounded-full;
24
- }
22
+ .icon-button-rounded {
23
+ @apply rounded-full;
24
+ }
25
25
 
26
- /* Overwrite for outline style */
27
- .button-outline.icon-button-disabled {
28
- @apply disabled:border-transparent
29
- }
30
- .button-outline.icon-button-read-only {
31
- @apply data-[read-only=true]:border-transparent
32
- }
33
- }
26
+ /* Overwrite for outline style */
27
+ .button-outline.icon-button-disabled {
28
+ @apply disabled:border-transparent;
29
+ }
30
+ .button-outline.icon-button-read-only {
31
+ @apply data-[read-only=true]:border-transparent;
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ export type QRCodeProps = {
2
+ /**
3
+ * The `otpauth://totp/...` URI to encode. An empty or whitespace-only value
4
+ * renders nothing (never a broken QR).
5
+ */
6
+ value: string;
7
+ /**
8
+ * Rendered square edge in px. Drives both width and height of the SVG.
9
+ * @defaultValue 160
10
+ */
11
+ size?: number;
12
+ /**
13
+ * Optional image URL rendered in the center of the code (e.g. a brand logo).
14
+ * When set, the error-correction level is raised to `H` so the code stays
15
+ * scannable despite the occluded modules, and the modules behind the image
16
+ * are excavated to the background color.
17
+ */
18
+ logoSrc?: string;
19
+ /**
20
+ * Accessible name for the QR image. Falls back to a generic label when
21
+ * omitted. Consumers should pass an i18n string.
22
+ */
23
+ 'aria-label'?: string;
24
+ };
25
+ export declare function QRCode({ value, size, logoSrc, 'aria-label': ariaLabel }: QRCodeProps): import("react").JSX.Element | null;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/qr-code/index.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,WAAW,GAAG;IACxB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAUD,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,IAAmB,EACnB,OAAO,EACP,YAAY,EAAE,SAA8B,EAC7C,EAAE,WAAW,sCA2Bb"}
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.QRCode = QRCode;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const qrcode_react_1 = require("qrcode.react");
7
+ const DEFAULT_SIZE = 160;
8
+ const DEFAULT_ARIA_LABEL = 'QR code';
9
+ /**
10
+ * Center image edge as a fraction of the code size. Kept small so error
11
+ * correction can recover the excavated modules.
12
+ */
13
+ const LOGO_SIZE_RATIO = 0.22;
14
+ function QRCode({ value, size = DEFAULT_SIZE, logoSrc, 'aria-label': ariaLabel = DEFAULT_ARIA_LABEL }) {
15
+ if (!value || value.trim() === '') {
16
+ return null;
17
+ }
18
+ const logoSize = Math.round(size * LOGO_SIZE_RATIO);
19
+ return ((0, jsx_runtime_1.jsx)(qrcode_react_1.QRCodeSVG, { "data-slot": "qr-code", value: value, size: size, role: "img", "aria-label": ariaLabel, level: logoSrc ? 'H' : 'L', imageSettings: logoSrc
20
+ ? {
21
+ src: logoSrc,
22
+ height: logoSize,
23
+ width: logoSize,
24
+ excavate: true
25
+ }
26
+ : undefined }));
27
+ }
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=qr-code.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr-code.test.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/qr-code/qr-code.test.tsx"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ require("@testing-library/jest-dom");
5
+ const react_1 = require("@testing-library/react");
6
+ const _1 = require(".");
7
+ const SAMPLE = 'otpauth://totp/Lerian:user@acme?secret=JBSWY3DPEHPK3PXP&issuer=Lerian';
8
+ describe('QRCode', () => {
9
+ it('renders an svg when value is present', () => {
10
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE }));
11
+ const svg = container.querySelector('svg');
12
+ expect(svg).toBeInTheDocument();
13
+ });
14
+ it('exposes the QR as an image to assistive tech', () => {
15
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE }));
16
+ expect(react_1.screen.getByRole('img')).toBeInTheDocument();
17
+ });
18
+ it('renders nothing when value is an empty string', () => {
19
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: "" }));
20
+ expect(container).toBeEmptyDOMElement();
21
+ expect(react_1.screen.queryByRole('img')).not.toBeInTheDocument();
22
+ });
23
+ it('renders nothing when value is whitespace only', () => {
24
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: " " }));
25
+ expect(container).toBeEmptyDOMElement();
26
+ expect(react_1.screen.queryByRole('img')).not.toBeInTheDocument();
27
+ });
28
+ it('applies the provided aria-label', () => {
29
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE, "aria-label": "Scan this QR code" }));
30
+ expect(react_1.screen.getByRole('img', { name: 'Scan this QR code' })).toBeInTheDocument();
31
+ });
32
+ it('falls back to a generic accessible name when aria-label is omitted', () => {
33
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE }));
34
+ const svg = react_1.screen.getByRole('img');
35
+ expect(svg).toHaveAccessibleName();
36
+ expect(svg.getAttribute('aria-label')).toBeTruthy();
37
+ });
38
+ it('applies the provided size to width and height', () => {
39
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE, size: 200 }));
40
+ const svg = container.querySelector('svg');
41
+ expect(svg).toHaveAttribute('width', '200');
42
+ expect(svg).toHaveAttribute('height', '200');
43
+ });
44
+ it('defaults width and height to 160 when size is omitted', () => {
45
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE }));
46
+ const svg = container.querySelector('svg');
47
+ expect(svg).toHaveAttribute('width', '160');
48
+ expect(svg).toHaveAttribute('height', '160');
49
+ });
50
+ it('embeds the center logo when logoSrc is provided', () => {
51
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE, logoSrc: "/logo.svg" }));
52
+ const image = container.querySelector('image');
53
+ expect(image).toBeInTheDocument();
54
+ expect(image).toHaveAttribute('href', '/logo.svg');
55
+ });
56
+ it('renders no embedded image when logoSrc is omitted', () => {
57
+ const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(_1.QRCode, { value: SAMPLE }));
58
+ expect(container.querySelector('image')).not.toBeInTheDocument();
59
+ });
60
+ });
@@ -7,6 +7,6 @@ type SidebarItemIconButtonProps = React.ComponentProps<typeof Link> & {
7
7
  inactive?: boolean;
8
8
  disabled?: boolean;
9
9
  };
10
- export declare const SidebarItemIconButton: ({ title, icon, href, active, inactive, disabled, ...props }: SidebarItemIconButtonProps) => React.JSX.Element;
10
+ export declare const SidebarItemIconButton: ({ className, title, icon, href, active, inactive, disabled, ...props }: SidebarItemIconButtonProps) => React.JSX.Element;
11
11
  export {};
12
12
  //# sourceMappingURL=sidebar-item-icon-button.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-item-icon-button.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-item-icon-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,IAAI,MAAM,WAAW,CAAA;AAW5B,KAAK,0BAA0B,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,GAAG;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,6DAQnC,0BAA0B,sBAoC5B,CAAA"}
1
+ {"version":3,"file":"sidebar-item-icon-button.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/sidebar/sidebar-item-icon-button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,IAAI,MAAM,WAAW,CAAA;AAW5B,KAAK,0BAA0B,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,GAAG;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,wEASnC,0BAA0B,sBAqC5B,CAAA"}
@@ -11,10 +11,10 @@ const button_1 = require("../../ui/button");
11
11
  const tooltip_1 = require("../../ui/tooltip");
12
12
  const utils_1 = require("../../../lib/utils");
13
13
  const icon_button_1 = require("../icon-button");
14
- const SidebarItemIconButton = ({ title, icon, href, active, inactive, disabled, ...props }) => {
14
+ const SidebarItemIconButton = ({ className, title, icon, href, active, inactive, disabled, ...props }) => {
15
15
  const sharedClassName = (0, utils_1.cn)((0, button_1.buttonVariants)({
16
16
  variant: active ? 'tertiary' : 'outline'
17
- }), (0, icon_button_1.iconButtonVariants)(), inactive && 'hover:border-transparent', disabled && 'cursor-default opacity-30');
17
+ }), (0, icon_button_1.iconButtonVariants)(), inactive && 'hover:border-transparent', disabled && 'cursor-default opacity-30', className);
18
18
  return ((0, jsx_runtime_1.jsx)(tooltip_1.TooltipProvider, { children: (0, jsx_runtime_1.jsxs)(tooltip_1.Tooltip, { delayDuration: 0, children: [(0, jsx_runtime_1.jsx)(tooltip_1.TooltipTrigger, { asChild: true, children: disabled || inactive ? ((0, jsx_runtime_1.jsx)("div", { "data-slot": "sidebar-item-icon-button", className: sharedClassName, children: icon })) : ((0, jsx_runtime_1.jsx)(link_1.default, { "data-slot": "sidebar-item-icon-button", href: href, className: sharedClassName, ...props, children: icon })) }), (0, jsx_runtime_1.jsx)(tooltip_1.TooltipContent, { side: "right", children: title })] }) }));
19
19
  };
20
20
  exports.SidebarItemIconButton = SidebarItemIconButton;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export * from './components/ui/card';
11
11
  export * from './components/ui/checkbox';
12
12
  export * from './components/ui/collapsible';
13
13
  export * from './components/ui/command';
14
+ export * from './components/ui/copy-field';
14
15
  export * from './components/ui/dialog';
15
16
  export * from './components/ui/dropdown-menu';
16
17
  export * from './components/ui/input';
@@ -22,6 +23,7 @@ export * from './components/ui/paper';
22
23
  export * from './components/ui/paper-collapsible';
23
24
  export * from './components/ui/popover';
24
25
  export * from './components/ui/progress';
26
+ export * from './components/ui/qr-code';
25
27
  export * from './components/ui/select';
26
28
  export * from './components/ui/separator';
27
29
  export * from './components/ui/sheet';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,uBAAuB,CAAA;AACrC,cAAc,mCAAmC,CAAA;AACjD,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AACtC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AAGpC,OAAO,EAAE,UAAU,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,cAAc,8CAA8C,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,uBAAuB,CAAA;AACrC,cAAc,mCAAmC,CAAA;AACjD,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mBAAmB,CAAA;AACjC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AAGpC,OAAO,EAAE,UAAU,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,cAAc,8CAA8C,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ __exportStar(require("./components/ui/card"), exports);
30
30
  __exportStar(require("./components/ui/checkbox"), exports);
31
31
  __exportStar(require("./components/ui/collapsible"), exports);
32
32
  __exportStar(require("./components/ui/command"), exports);
33
+ __exportStar(require("./components/ui/copy-field"), exports);
33
34
  __exportStar(require("./components/ui/dialog"), exports);
34
35
  __exportStar(require("./components/ui/dropdown-menu"), exports);
35
36
  __exportStar(require("./components/ui/input"), exports);
@@ -41,6 +42,7 @@ __exportStar(require("./components/ui/paper"), exports);
41
42
  __exportStar(require("./components/ui/paper-collapsible"), exports);
42
43
  __exportStar(require("./components/ui/popover"), exports);
43
44
  __exportStar(require("./components/ui/progress"), exports);
45
+ __exportStar(require("./components/ui/qr-code"), exports);
44
46
  __exportStar(require("./components/ui/select"), exports);
45
47
  __exportStar(require("./components/ui/separator"), exports);
46
48
  __exportStar(require("./components/ui/sheet"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerianstudio/sindarian-ui",
3
- "version": "1.2.0-beta.10",
3
+ "version": "1.2.0-beta.11",
4
4
  "description": "Sindarian UI - A UI library for Midaz Console",
5
5
  "license": "ISC",
6
6
  "author": {
@@ -53,6 +53,7 @@
53
53
  "dayjs": "^1.11.21",
54
54
  "input-otp": "^1.4.2",
55
55
  "postcss": "^8.5.16",
56
+ "qrcode.react": "^4.2.0",
56
57
  "sonner": "^2.0.7",
57
58
  "tailwind-merge": "^3.6.0",
58
59
  "tailwindcss": "^4.3.2",