@nadicodeai/ui 0.18.0 → 0.18.2

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,3 +1,44 @@
1
+ /**
2
+ * The copy affordance behind every "copy" button in the package.
3
+ *
4
+ * The decision half and the timer half are exported as plain functions, with the
5
+ * hook a thin binding over them (the shape `use-confirm-action` established), so
6
+ * both are testable in the package's node-only default suite instead of needing a
7
+ * DOM render harness.
8
+ */
9
+ export type CopyAttempt =
10
+ /** No clipboard to write to — an insecure origin, typically. */
11
+ {
12
+ kind: "unavailable";
13
+ error: Error;
14
+ }
15
+ /** Already showing "copied" and the caller asked not to repeat. */
16
+ | {
17
+ kind: "skipped";
18
+ } | {
19
+ kind: "copied";
20
+ } | {
21
+ kind: "failed";
22
+ error: Error;
23
+ };
24
+ /** Attempts the write and classifies the result; performs no state or timer work. */
25
+ export declare function attemptCopy(input: {
26
+ text: string;
27
+ isCopied: boolean;
28
+ skipWhileCopied: boolean;
29
+ writeText: ((text: string) => Promise<void>) | undefined;
30
+ }): Promise<CopyAttempt>;
31
+ export type ResetTimers = {
32
+ clear: (handle: number) => void;
33
+ set: (onReset: () => void, timeout: number) => number;
34
+ };
35
+ /**
36
+ * Starts the "copied" reset window, dropping whichever reset was already
37
+ * pending. Without the clear, an earlier press's timer keeps its own schedule and
38
+ * fires part-way through this press's window, so the feedback disappears early.
39
+ * Returns the new handle for the caller to hold.
40
+ */
41
+ export declare function restartResetTimer(timers: ResetTimers, pendingHandle: number, onReset: () => void, timeout: number): number;
1
42
  export declare function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied, }: {
2
43
  text: string;
3
44
  onCopy?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,eAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;EA+BA"}
1
+ {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW;AACrB,gEAAgE;AAC9D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE;AACvC,mEAAmE;GACjE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAErC,qFAAqF;AACrF,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CAC1D,GAAG,OAAO,CAAC,WAAW,CAAC,CAavB;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACvD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,EAAE,MAAM,GACd,MAAM,CAGR;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,eAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;EAyCA"}
@@ -1,24 +1,52 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
+ /** Attempts the write and classifies the result; performs no state or timer work. */
3
+ export async function attemptCopy(input) {
4
+ if (!input.writeText) {
5
+ return { kind: "unavailable", error: new Error("Clipboard API not available") };
6
+ }
7
+ if (input.skipWhileCopied && input.isCopied) {
8
+ return { kind: "skipped" };
9
+ }
10
+ try {
11
+ await input.writeText(input.text);
12
+ return { kind: "copied" };
13
+ }
14
+ catch (error) {
15
+ return { kind: "failed", error: error };
16
+ }
17
+ }
18
+ /**
19
+ * Starts the "copied" reset window, dropping whichever reset was already
20
+ * pending. Without the clear, an earlier press's timer keeps its own schedule and
21
+ * fires part-way through this press's window, so the feedback disappears early.
22
+ * Returns the new handle for the caller to hold.
23
+ */
24
+ export function restartResetTimer(timers, pendingHandle, onReset, timeout) {
25
+ timers.clear(pendingHandle);
26
+ return timers.set(onReset, timeout);
27
+ }
2
28
  export function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied = false, }) {
3
29
  const [isCopied, setIsCopied] = useState(false);
4
30
  const timeoutRef = useRef(0);
5
31
  const copyToClipboard = useCallback(async () => {
6
- if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
7
- onError?.(new Error("Clipboard API not available"));
32
+ const attempt = await attemptCopy({
33
+ text,
34
+ isCopied,
35
+ skipWhileCopied,
36
+ writeText: typeof window === "undefined"
37
+ ? undefined
38
+ : navigator?.clipboard?.writeText?.bind(navigator.clipboard),
39
+ });
40
+ if (attempt.kind === "unavailable" || attempt.kind === "failed") {
41
+ onError?.(attempt.error);
8
42
  return;
9
43
  }
10
- if (skipWhileCopied && isCopied) {
44
+ if (attempt.kind === "skipped") {
11
45
  return;
12
46
  }
13
- try {
14
- await navigator.clipboard.writeText(text);
15
- setIsCopied(true);
16
- onCopy?.();
17
- timeoutRef.current = window.setTimeout(() => setIsCopied(false), timeout);
18
- }
19
- catch (error) {
20
- onError?.(error);
21
- }
47
+ setIsCopied(true);
48
+ onCopy?.();
49
+ timeoutRef.current = restartResetTimer({ clear: window.clearTimeout.bind(window), set: window.setTimeout.bind(window) }, timeoutRef.current, () => setIsCopied(false), timeout);
22
50
  }, [isCopied, onCopy, onError, skipWhileCopied, text, timeout]);
23
51
  useEffect(() => () => {
24
52
  window.clearTimeout(timeoutRef.current);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/ui",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "@base-ui/react": "^1.6.0",
66
- "@nadicodeai/design-system": "0.18.0",
66
+ "@nadicodeai/design-system": "0.18.2",
67
67
  "@rive-app/react-webgl2": "^4.29.1",
68
68
  "@streamdown/cjk": "^1.0.3",
69
69
  "@streamdown/code": "^1.1.1",
@@ -102,19 +102,19 @@
102
102
  "tailwindcss": "^4.3.1"
103
103
  },
104
104
  "devDependencies": {
105
- "@testing-library/react": "^16.3.2",
106
105
  "@tailwindcss/postcss": "^4.3.1",
106
+ "@testing-library/react": "^16.3.2",
107
107
  "@types/react": "^19",
108
108
  "@types/react-dom": "^19",
109
109
  "axe-core": "^4.12.0",
110
110
  "eslint": "^9.39.4",
111
111
  "jsdom": "^26.1.0",
112
+ "postcss": "^8.5.15",
112
113
  "react": "19.2.7",
113
114
  "react-dom": "19.2.7",
114
115
  "shadcn": "4.13.0",
115
116
  "tailwindcss": "^4.3.1",
116
117
  "typescript": "^5",
117
- "typescript-eslint": "^8.46.0",
118
118
  "vitest": "^4.1.6"
119
119
  }
120
120
  }