cliphook 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 satishjaiswal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # cliphook 🪝
2
+
3
+ A lightweight, developer-friendly React hook + component for easy clipboard management with built-in feedback states and no-conflict naming.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/cliphook.svg?style=flat-square)](https://www.npmjs.com/package/cliphook)
6
+ [![npm downloads](https://img.shields.io/npm/dm/cliphook.svg?style=flat-square)](https://www.npmjs.com/package/cliphook)
7
+ [![license](https://img.shields.io/npm/l/cliphook.svg?style=flat-square)](https://www.npmjs.com/package/cliphook)
8
+
9
+ ## ✨ Why cliphook?
10
+
11
+ - **Zero Dependencies**: Keeps your bundle size ultra-small.
12
+ - **Robust Fallback**: Uses `navigator.clipboard` with an automatic fallback to `document.execCommand('copy')` for older browsers.
13
+ - **No-Conflict Naming**: Exports as both `useClipboard` and its alias `useCliphook` to avoid naming collisions with other hooks.
14
+ - **Headless Component**: Provide ultimate UI flexibility with the `CopyToClipboard` component.
15
+ - **Bonus `useLocalStorage`**: Includes a type-safe, SSR-safe hook with cross-tab synchronization.
16
+
17
+ ## 🚀 Installation
18
+
19
+ ```bash
20
+ npm install cliphook
21
+ # or
22
+ yarn add cliphook
23
+ # or
24
+ pnpm add cliphook
25
+ ```
26
+
27
+ ## 📋 Quick Start
28
+
29
+ ### 1. Hook Usage (`useCliphook`)
30
+
31
+ The easiest way to copy text inside your components.
32
+
33
+ ```tsx
34
+ import { useCliphook } from 'cliphook';
35
+
36
+ function MyComponent() {
37
+ const { copyText, isCopied } = useCliphook({ timeout: 2000 });
38
+
39
+ return (
40
+ <button onClick={() => copyText('Hello World!')}>
41
+ {isCopied ? '✅ Copied' : '📋 Copy to Clipboard'}
42
+ </button>
43
+ );
44
+ }
45
+ ```
46
+
47
+ ### 2. Component Usage (`CopyToClipboard`)
48
+
49
+ A headless wrapper that makes it easy to add copy functionality to any custom UI.
50
+
51
+ ```tsx
52
+ import { CopyToClipboard } from 'cliphook';
53
+
54
+ function MyButton() {
55
+ return (
56
+ <CopyToClipboard text="Some important text">
57
+ {({ copy, isCopied }) => (
58
+ <button onClick={copy}>
59
+ {isCopied ? 'Done!' : 'Copy'}
60
+ </button>
61
+ )}
62
+ </CopyToClipboard>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ### 3. Bonus Hook (`useLocalStorage`)
68
+
69
+ Persist state automatically with cross-tab sync and SSR safety.
70
+
71
+ ```tsx
72
+ import { useLocalStorage } from 'cliphook';
73
+
74
+ function ThemeToggle() {
75
+ const [theme, setTheme] = useLocalStorage('theme', 'light');
76
+
77
+ return (
78
+ <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
79
+ Mode: {theme}
80
+ </button>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## 📖 For More Detailed Examples
86
+ Check out the **[EXAMPLES.md](./EXAMPLES.md)** file for advanced usage, types, and troubleshooting.
87
+
88
+ ## 📄 License
89
+ MIT © satishjaiswal
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+
3
+ interface ClipboardOptions {
4
+ /**
5
+ * Reset 'isCopied' state after this duration (ms).
6
+ * @default 2000
7
+ */
8
+ timeout?: number;
9
+ }
10
+ interface UseClipboardReturn {
11
+ /**
12
+ * Function to copy text to clipboard.
13
+ * @param text The string to copy.
14
+ */
15
+ copyText: (text: string) => Promise<void>;
16
+ /**
17
+ * Whether the last copy operation was successful.
18
+ */
19
+ isCopied: boolean;
20
+ /**
21
+ * Error object if the last copy operation failed.
22
+ */
23
+ error: Error | null;
24
+ /**
25
+ * Number of successful copy operations in this session.
26
+ */
27
+ copyCount: number;
28
+ /**
29
+ * Resets the 'isCopied' and 'error' state manually.
30
+ */
31
+ reset: () => void;
32
+ }
33
+ /**
34
+ * useClipboard is a React hook that makes copying text to the clipboard effortless.
35
+ *
36
+ * @alias useCliphook
37
+ */
38
+ declare function useClipboard(options?: ClipboardOptions): UseClipboardReturn;
39
+ /**
40
+ * Alias for useClipboard to avoid naming conflicts.
41
+ */
42
+ declare const useCliphook: typeof useClipboard;
43
+
44
+ interface CopyToClipboardProps extends ClipboardOptions {
45
+ /**
46
+ * The text to copy to the clipboard.
47
+ */
48
+ text: string;
49
+ /**
50
+ * A function that returns a React element.
51
+ * Receives the clipboard state (copy, isCopied, error, etc.) as an argument.
52
+ */
53
+ children: (props: UseClipboardReturn & {
54
+ copy: () => void;
55
+ }) => React.ReactNode;
56
+ /**
57
+ * Callback when copy is successful.
58
+ */
59
+ onCopy?: (text: string) => void;
60
+ /**
61
+ * Callback when copy fails.
62
+ */
63
+ onError?: (error: Error) => void;
64
+ }
65
+ /**
66
+ * CopyToClipboard is a headless component that makes copying text to the clipboard effortless.
67
+ *
68
+ * Example usage:
69
+ * ```tsx
70
+ * <CopyToClipboard text="Hello World">
71
+ * {({ copy, isCopied }) => (
72
+ * <button onClick={copy}>{isCopied ? 'Done!' : 'Copy'}</button>
73
+ * )}
74
+ * </CopyToClipboard>
75
+ * ```
76
+ */
77
+ declare const CopyToClipboard: React.FC<CopyToClipboardProps>;
78
+
79
+ /**
80
+ * useLocalStorage is a React hook that makes persisting state to localStorage effortless.
81
+ * Includes cross-tab synchronization and type safety.
82
+ * SSR safe: Does not cause hydration errors.
83
+ */
84
+ declare function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void, () => void];
85
+
86
+ export { type ClipboardOptions, CopyToClipboard, type CopyToClipboardProps, type UseClipboardReturn, useClipboard, useCliphook, useLocalStorage };
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+
3
+ interface ClipboardOptions {
4
+ /**
5
+ * Reset 'isCopied' state after this duration (ms).
6
+ * @default 2000
7
+ */
8
+ timeout?: number;
9
+ }
10
+ interface UseClipboardReturn {
11
+ /**
12
+ * Function to copy text to clipboard.
13
+ * @param text The string to copy.
14
+ */
15
+ copyText: (text: string) => Promise<void>;
16
+ /**
17
+ * Whether the last copy operation was successful.
18
+ */
19
+ isCopied: boolean;
20
+ /**
21
+ * Error object if the last copy operation failed.
22
+ */
23
+ error: Error | null;
24
+ /**
25
+ * Number of successful copy operations in this session.
26
+ */
27
+ copyCount: number;
28
+ /**
29
+ * Resets the 'isCopied' and 'error' state manually.
30
+ */
31
+ reset: () => void;
32
+ }
33
+ /**
34
+ * useClipboard is a React hook that makes copying text to the clipboard effortless.
35
+ *
36
+ * @alias useCliphook
37
+ */
38
+ declare function useClipboard(options?: ClipboardOptions): UseClipboardReturn;
39
+ /**
40
+ * Alias for useClipboard to avoid naming conflicts.
41
+ */
42
+ declare const useCliphook: typeof useClipboard;
43
+
44
+ interface CopyToClipboardProps extends ClipboardOptions {
45
+ /**
46
+ * The text to copy to the clipboard.
47
+ */
48
+ text: string;
49
+ /**
50
+ * A function that returns a React element.
51
+ * Receives the clipboard state (copy, isCopied, error, etc.) as an argument.
52
+ */
53
+ children: (props: UseClipboardReturn & {
54
+ copy: () => void;
55
+ }) => React.ReactNode;
56
+ /**
57
+ * Callback when copy is successful.
58
+ */
59
+ onCopy?: (text: string) => void;
60
+ /**
61
+ * Callback when copy fails.
62
+ */
63
+ onError?: (error: Error) => void;
64
+ }
65
+ /**
66
+ * CopyToClipboard is a headless component that makes copying text to the clipboard effortless.
67
+ *
68
+ * Example usage:
69
+ * ```tsx
70
+ * <CopyToClipboard text="Hello World">
71
+ * {({ copy, isCopied }) => (
72
+ * <button onClick={copy}>{isCopied ? 'Done!' : 'Copy'}</button>
73
+ * )}
74
+ * </CopyToClipboard>
75
+ * ```
76
+ */
77
+ declare const CopyToClipboard: React.FC<CopyToClipboardProps>;
78
+
79
+ /**
80
+ * useLocalStorage is a React hook that makes persisting state to localStorage effortless.
81
+ * Includes cross-tab synchronization and type safety.
82
+ * SSR safe: Does not cause hydration errors.
83
+ */
84
+ declare function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void, () => void];
85
+
86
+ export { type ClipboardOptions, CopyToClipboard, type CopyToClipboardProps, type UseClipboardReturn, useClipboard, useCliphook, useLocalStorage };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var y=Object.create;var m=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty;var h=(e,o)=>{for(var r in o)m(e,r,{get:o[r],enumerable:!0})},g=(e,o,r,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let c of E(o))!S.call(e,c)&&c!==r&&m(e,c,{get:()=>o[c],enumerable:!(n=T(o,c))||n.enumerable});return e};var R=(e,o,r)=>(r=e!=null?y(x(e)):{},g(o||!e||!e.__esModule?m(r,"default",{value:e,enumerable:!0}):r,e)),O=e=>g(m({},"__esModule",{value:!0}),e);var N={};h(N,{CopyToClipboard:()=>I,useClipboard:()=>w,useCliphook:()=>F,useLocalStorage:()=>U});module.exports=O(N);var i=require("react");function L(e){let o=document.createElement("textarea");o.value=e,o.style.position="fixed",o.style.left="-9999px",o.style.top="0",document.body.appendChild(o),o.focus(),o.select();try{let r=document.execCommand("copy");return document.body.removeChild(o),r}catch{return document.body.removeChild(o),!1}}function w(e={}){let{timeout:o=2e3}=e,[r,n]=(0,i.useState)(!1),[c,l]=(0,i.useState)(null),[p,u]=(0,i.useState)(0),s=(0,i.useRef)(null),t=(0,i.useCallback)(()=>{n(!1),l(null),s.current&&clearTimeout(s.current)},[]),d=(0,i.useCallback)(async b=>{s.current&&clearTimeout(s.current);try{if(navigator?.clipboard?.writeText)await navigator.clipboard.writeText(b);else if(!L(b))throw new Error("Fallback clipboard copy failed");n(!0),l(null),u(f=>f+1),o>0&&(s.current=setTimeout(()=>{n(!1)},o))}catch(f){n(!1),l(f instanceof Error?f:new Error("Failed to copy to clipboard"))}},[o]);return(0,i.useEffect)(()=>()=>{s.current&&clearTimeout(s.current)},[]),{copyText:d,isCopied:r,error:c,copyCount:p,reset:t}}var F=w;var v=R(require("react"));var C=require("react/jsx-runtime"),I=({text:e,children:o,onCopy:r,onError:n,...c})=>{let{copyText:l,...p}=w(c),u=v.default.useCallback(async()=>{try{await l(e),r&&r(e)}catch(s){n&&s instanceof Error&&n(s)}},[l,e,r,n]);return(0,C.jsx)(C.Fragment,{children:o({...p,copyText:l,copy:u})})};var a=require("react");function U(e,o){let r=(0,a.useCallback)(()=>{if(typeof window>"u")return o;try{let t=window.localStorage.getItem(e);return t?JSON.parse(t):o}catch(t){return console.warn(`Error reading localStorage key "${e}":`,t),o}},[o,e]),[n,c]=(0,a.useState)(r),l=(0,a.useCallback)(t=>{try{let d=t instanceof Function?t(n):t;c(d),typeof window<"u"&&(window.localStorage.setItem(e,JSON.stringify(d)),window.dispatchEvent(new Event("local-storage")))}catch(d){console.warn(`Error setting localStorage key "${e}":`,d)}},[e,n]),p=(0,a.useCallback)(()=>{try{typeof window<"u"&&(window.localStorage.removeItem(e),c(o),window.dispatchEvent(new Event("local-storage")))}catch(t){console.warn(`Error removing localStorage key "${e}":`,t)}},[e,o]),u=(0,a.useCallback)(t=>{t.key&&t.key!==e||c(r())},[e,r]);(0,a.useEffect)(()=>(window.addEventListener("storage",u),window.addEventListener("local-storage",u),()=>{window.removeEventListener("storage",u),window.removeEventListener("local-storage",u)}),[u]);let s=(0,a.useRef)(!0);return(0,a.useEffect)(()=>{if(s.current){s.current=!1;let t=r();t!==n&&c(t)}},[r,n]),[n,l,p]}0&&(module.exports={CopyToClipboard,useClipboard,useCliphook,useLocalStorage});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{useState as f,useCallback as C,useRef as g,useEffect as v}from"react";function y(o){let e=document.createElement("textarea");e.value=o,e.style.position="fixed",e.style.left="-9999px",e.style.top="0",document.body.appendChild(e),e.focus(),e.select();try{let t=document.execCommand("copy");return document.body.removeChild(e),t}catch{return document.body.removeChild(e),!1}}function m(o={}){let{timeout:e=2e3}=o,[t,n]=f(!1),[i,s]=f(null),[u,a]=f(0),c=g(null),r=C(()=>{n(!1),s(null),c.current&&clearTimeout(c.current)},[]),l=C(async w=>{c.current&&clearTimeout(c.current);try{if(navigator?.clipboard?.writeText)await navigator.clipboard.writeText(w);else if(!y(w))throw new Error("Fallback clipboard copy failed");n(!0),s(null),a(d=>d+1),e>0&&(c.current=setTimeout(()=>{n(!1)},e))}catch(d){n(!1),s(d instanceof Error?d:new Error("Failed to copy to clipboard"))}},[e]);return v(()=>()=>{c.current&&clearTimeout(c.current)},[]),{copyText:l,isCopied:t,error:i,copyCount:u,reset:r}}var O=m;import T from"react";import{Fragment as E,jsx as x}from"react/jsx-runtime";var P=({text:o,children:e,onCopy:t,onError:n,...i})=>{let{copyText:s,...u}=m(i),a=T.useCallback(async()=>{try{await s(o),t&&t(o)}catch(c){n&&c instanceof Error&&n(c)}},[s,o,t,n]);return x(E,{children:e({...u,copyText:s,copy:a})})};import{useState as S,useCallback as p,useEffect as b,useRef as h}from"react";function A(o,e){let t=p(()=>{if(typeof window>"u")return e;try{let r=window.localStorage.getItem(o);return r?JSON.parse(r):e}catch(r){return console.warn(`Error reading localStorage key "${o}":`,r),e}},[e,o]),[n,i]=S(t),s=p(r=>{try{let l=r instanceof Function?r(n):r;i(l),typeof window<"u"&&(window.localStorage.setItem(o,JSON.stringify(l)),window.dispatchEvent(new Event("local-storage")))}catch(l){console.warn(`Error setting localStorage key "${o}":`,l)}},[o,n]),u=p(()=>{try{typeof window<"u"&&(window.localStorage.removeItem(o),i(e),window.dispatchEvent(new Event("local-storage")))}catch(r){console.warn(`Error removing localStorage key "${o}":`,r)}},[o,e]),a=p(r=>{r.key&&r.key!==o||i(t())},[o,t]);b(()=>(window.addEventListener("storage",a),window.addEventListener("local-storage",a),()=>{window.removeEventListener("storage",a),window.removeEventListener("local-storage",a)}),[a]);let c=h(!0);return b(()=>{if(c.current){c.current=!1;let r=t();r!==n&&i(r)}},[t,n]),[n,s,u]}export{P as CopyToClipboard,m as useClipboard,O as useCliphook,A as useLocalStorage};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "cliphook",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight, developer-friendly React hook + component for easy clipboard management with built-in feedback states.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --minify",
13
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
14
+ "lint": "tsc"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "hook",
19
+ "clipboard",
20
+ "copy",
21
+ "copy-to-clipboard",
22
+ "useClipboard",
23
+ "useCliphook"
24
+ ],
25
+ "author": "satishjaiswal",
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "react": ">=16.8.0",
29
+ "react-dom": ">=16.8.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^18.0.0",
33
+ "@types/react-dom": "^18.0.0",
34
+ "react": "^18.0.0",
35
+ "react-dom": "^18.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0"
38
+ }
39
+ }