@usefy/use-copy-to-clipboard 0.0.8 → 0.0.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.
- package/README.md +498 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-copy-to-clipboard</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A robust React hook for copying text to clipboard with fallback support</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-copy-to-clipboard">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-copy-to-clipboard.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-copy-to-clipboard">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-copy-to-clipboard.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-copy-to-clipboard">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-copy-to-clipboard?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-copy-to-clipboard.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
|
|
38
|
+
`@usefy/use-copy-to-clipboard` provides a simple way to copy text to the clipboard using the modern Clipboard API with automatic fallback for older browsers. Features include auto-reset timeout, success/error callbacks, and copy state tracking.
|
|
39
|
+
|
|
40
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
41
|
+
|
|
42
|
+
### Why use-copy-to-clipboard?
|
|
43
|
+
|
|
44
|
+
- **Zero Dependencies** — Pure React implementation with no external dependencies
|
|
45
|
+
- **TypeScript First** — Full type safety with exported interfaces
|
|
46
|
+
- **Modern + Fallback** — Uses Clipboard API with automatic `execCommand` fallback
|
|
47
|
+
- **Auto Reset** — Copied state automatically resets after configurable timeout
|
|
48
|
+
- **Callbacks** — `onSuccess` and `onError` callbacks for custom handling
|
|
49
|
+
- **Async/Await** — Returns promise with boolean success indicator
|
|
50
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
51
|
+
- **Stable References** — Memoized copy function for optimal performance
|
|
52
|
+
- **Well Tested** — Comprehensive test coverage with Vitest
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# npm
|
|
60
|
+
npm install @usefy/use-copy-to-clipboard
|
|
61
|
+
|
|
62
|
+
# yarn
|
|
63
|
+
yarn add @usefy/use-copy-to-clipboard
|
|
64
|
+
|
|
65
|
+
# pnpm
|
|
66
|
+
pnpm add @usefy/use-copy-to-clipboard
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Peer Dependencies
|
|
70
|
+
|
|
71
|
+
This package requires React 18 or 19:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
87
|
+
|
|
88
|
+
function CopyButton() {
|
|
89
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<button onClick={() => copy('Hello World!')}>
|
|
93
|
+
{copiedText ? 'Copied!' : 'Copy'}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## API Reference
|
|
102
|
+
|
|
103
|
+
### `useCopyToClipboard(options?)`
|
|
104
|
+
|
|
105
|
+
A hook that provides clipboard copy functionality with state tracking.
|
|
106
|
+
|
|
107
|
+
#### Parameters
|
|
108
|
+
|
|
109
|
+
| Parameter | Type | Description |
|
|
110
|
+
|-----------|------|-------------|
|
|
111
|
+
| `options` | `UseCopyToClipboardOptions` | Configuration options |
|
|
112
|
+
|
|
113
|
+
#### Options
|
|
114
|
+
|
|
115
|
+
| Option | Type | Default | Description |
|
|
116
|
+
|--------|------|---------|-------------|
|
|
117
|
+
| `timeout` | `number` | `2000` | Time in ms before `copiedText` resets to null. Set to `0` to disable. |
|
|
118
|
+
| `onSuccess` | `(text: string) => void` | — | Callback called when copy succeeds |
|
|
119
|
+
| `onError` | `(error: Error) => void` | — | Callback called when copy fails |
|
|
120
|
+
|
|
121
|
+
#### Returns `[copiedText, copy]`
|
|
122
|
+
|
|
123
|
+
| Index | Type | Description |
|
|
124
|
+
|-------|------|-------------|
|
|
125
|
+
| `[0]` | `string \| null` | The last successfully copied text, or `null` |
|
|
126
|
+
| `[1]` | `(text: string) => Promise<boolean>` | Async function to copy text |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Examples
|
|
131
|
+
|
|
132
|
+
### Basic Copy Button
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
136
|
+
|
|
137
|
+
function CopyButton({ text }: { text: string }) {
|
|
138
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<button onClick={() => copy(text)}>
|
|
142
|
+
{copiedText === text ? 'Copied!' : 'Copy to Clipboard'}
|
|
143
|
+
</button>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Copy with Visual Feedback
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
152
|
+
|
|
153
|
+
function CopyWithIcon({ text }: { text: string }) {
|
|
154
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
155
|
+
const isCopied = copiedText === text;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => copy(text)}
|
|
160
|
+
className={isCopied ? 'copied' : ''}
|
|
161
|
+
>
|
|
162
|
+
{isCopied ? (
|
|
163
|
+
<CheckIcon className="icon" />
|
|
164
|
+
) : (
|
|
165
|
+
<CopyIcon className="icon" />
|
|
166
|
+
)}
|
|
167
|
+
{isCopied ? 'Copied!' : 'Copy'}
|
|
168
|
+
</button>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Code Block with Copy
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
177
|
+
|
|
178
|
+
function CodeBlock({ code, language }: { code: string; language: string }) {
|
|
179
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="code-block">
|
|
183
|
+
<div className="code-header">
|
|
184
|
+
<span>{language}</span>
|
|
185
|
+
<button onClick={() => copy(code)}>
|
|
186
|
+
{copiedText === code ? 'Copied!' : 'Copy Code'}
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
<pre>
|
|
190
|
+
<code>{code}</code>
|
|
191
|
+
</pre>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Custom Timeout
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
201
|
+
|
|
202
|
+
function LongFeedbackCopy() {
|
|
203
|
+
// Show "Copied!" for 5 seconds
|
|
204
|
+
const [copiedText, copy] = useCopyToClipboard({ timeout: 5000 });
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<button onClick={() => copy('Long feedback!')}>
|
|
208
|
+
{copiedText ? 'Copied!' : 'Copy'}
|
|
209
|
+
</button>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Persistent Copied State
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
218
|
+
|
|
219
|
+
function PersistentCopy() {
|
|
220
|
+
// Never auto-reset the copied state
|
|
221
|
+
const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<div>
|
|
225
|
+
<button onClick={() => copy('Persistent!')}>
|
|
226
|
+
{copiedText ? 'Copied!' : 'Copy'}
|
|
227
|
+
</button>
|
|
228
|
+
{copiedText && <span>Copied text: {copiedText}</span>}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### With Callbacks
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
238
|
+
import { toast } from 'your-toast-library';
|
|
239
|
+
|
|
240
|
+
function CopyWithToast({ text }: { text: string }) {
|
|
241
|
+
const [, copy] = useCopyToClipboard({
|
|
242
|
+
onSuccess: (copiedText) => {
|
|
243
|
+
toast.success(`Copied: ${copiedText}`);
|
|
244
|
+
},
|
|
245
|
+
onError: (error) => {
|
|
246
|
+
toast.error(`Failed to copy: ${error.message}`);
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return <button onClick={() => copy(text)}>Copy</button>;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Async Handling
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
258
|
+
|
|
259
|
+
function AsyncCopy({ text }: { text: string }) {
|
|
260
|
+
const [, copy] = useCopyToClipboard();
|
|
261
|
+
const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
|
262
|
+
|
|
263
|
+
const handleCopy = async () => {
|
|
264
|
+
const success = await copy(text);
|
|
265
|
+
setStatus(success ? 'success' : 'error');
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div>
|
|
270
|
+
<button onClick={handleCopy}>Copy</button>
|
|
271
|
+
{status === 'success' && <span className="success">Copied successfully!</span>}
|
|
272
|
+
{status === 'error' && <span className="error">Failed to copy</span>}
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Share URL Button
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
282
|
+
|
|
283
|
+
function ShareButton() {
|
|
284
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
285
|
+
|
|
286
|
+
const handleShare = () => {
|
|
287
|
+
copy(window.location.href);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<button onClick={handleShare}>
|
|
292
|
+
{copiedText ? 'Link Copied!' : 'Share Link'}
|
|
293
|
+
</button>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Copy Multiple Items
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
302
|
+
|
|
303
|
+
function CopyList({ items }: { items: string[] }) {
|
|
304
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<ul>
|
|
308
|
+
{items.map((item) => (
|
|
309
|
+
<li key={item}>
|
|
310
|
+
<span>{item}</span>
|
|
311
|
+
<button onClick={() => copy(item)}>
|
|
312
|
+
{copiedText === item ? 'Copied!' : 'Copy'}
|
|
313
|
+
</button>
|
|
314
|
+
</li>
|
|
315
|
+
))}
|
|
316
|
+
</ul>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### API Key Display
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
import { useCopyToClipboard } from '@usefy/use-copy-to-clipboard';
|
|
325
|
+
|
|
326
|
+
function ApiKeyDisplay({ apiKey }: { apiKey: string }) {
|
|
327
|
+
const [copiedText, copy] = useCopyToClipboard();
|
|
328
|
+
|
|
329
|
+
const maskedKey = `${apiKey.slice(0, 4)}${'*'.repeat(20)}${apiKey.slice(-4)}`;
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<div className="api-key">
|
|
333
|
+
<code>{maskedKey}</code>
|
|
334
|
+
<button onClick={() => copy(apiKey)}>
|
|
335
|
+
{copiedText === apiKey ? 'Copied!' : 'Copy Key'}
|
|
336
|
+
</button>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## TypeScript
|
|
345
|
+
|
|
346
|
+
This hook is written in TypeScript with exported types.
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
import {
|
|
350
|
+
useCopyToClipboard,
|
|
351
|
+
type UseCopyToClipboardOptions,
|
|
352
|
+
type UseCopyToClipboardReturn,
|
|
353
|
+
type CopyFn,
|
|
354
|
+
} from '@usefy/use-copy-to-clipboard';
|
|
355
|
+
|
|
356
|
+
// Return type
|
|
357
|
+
const [copiedText, copy]: UseCopyToClipboardReturn = useCopyToClipboard();
|
|
358
|
+
|
|
359
|
+
// copiedText: string | null
|
|
360
|
+
// copy: (text: string) => Promise<boolean>
|
|
361
|
+
|
|
362
|
+
// Options type
|
|
363
|
+
const options: UseCopyToClipboardOptions = {
|
|
364
|
+
timeout: 3000,
|
|
365
|
+
onSuccess: (text) => console.log('Copied:', text),
|
|
366
|
+
onError: (error) => console.error('Error:', error),
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Browser Support
|
|
373
|
+
|
|
374
|
+
This hook uses the modern [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API) when available, with automatic fallback to `document.execCommand('copy')` for older browsers.
|
|
375
|
+
|
|
376
|
+
| Browser | Clipboard API | Fallback |
|
|
377
|
+
|---------|---------------|----------|
|
|
378
|
+
| Chrome 66+ | Yes | - |
|
|
379
|
+
| Firefox 63+ | Yes | - |
|
|
380
|
+
| Safari 13.1+ | Yes | - |
|
|
381
|
+
| Edge 79+ | Yes | - |
|
|
382
|
+
| IE 11 | No | Yes |
|
|
383
|
+
| Older browsers | No | Yes |
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Testing
|
|
388
|
+
|
|
389
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
390
|
+
|
|
391
|
+
### Test Coverage
|
|
392
|
+
|
|
393
|
+
| Category | Tests | Coverage |
|
|
394
|
+
|----------|-------|----------|
|
|
395
|
+
| Initialization | 3 | 100% |
|
|
396
|
+
| Copy Functionality | 5 | 100% |
|
|
397
|
+
| Copy Failure | 3 | 100% |
|
|
398
|
+
| Timeout/Auto-Reset | 4 | 100% |
|
|
399
|
+
| Callbacks | 4 | 100% |
|
|
400
|
+
| Function Stability | 3 | 100% |
|
|
401
|
+
| Cleanup | 2 | 100% |
|
|
402
|
+
| Multiple Instances | 2 | 100% |
|
|
403
|
+
| Fallback Behavior | 2 | 100% |
|
|
404
|
+
| Edge Cases | 4 | 100% |
|
|
405
|
+
| **Total** | **32** | **87.87%** |
|
|
406
|
+
|
|
407
|
+
### Test Categories
|
|
408
|
+
|
|
409
|
+
<details>
|
|
410
|
+
<summary><strong>Copy Functionality Tests</strong></summary>
|
|
411
|
+
|
|
412
|
+
- Copy text successfully using Clipboard API
|
|
413
|
+
- Update copiedText after successful copy
|
|
414
|
+
- Return true on successful copy
|
|
415
|
+
- Handle empty string
|
|
416
|
+
- Handle special characters
|
|
417
|
+
|
|
418
|
+
</details>
|
|
419
|
+
|
|
420
|
+
<details>
|
|
421
|
+
<summary><strong>Timeout Tests</strong></summary>
|
|
422
|
+
|
|
423
|
+
- Reset copiedText after default timeout (2000ms)
|
|
424
|
+
- Reset after custom timeout
|
|
425
|
+
- Not reset when timeout is 0
|
|
426
|
+
- Reset timer on consecutive copies
|
|
427
|
+
|
|
428
|
+
</details>
|
|
429
|
+
|
|
430
|
+
<details>
|
|
431
|
+
<summary><strong>Fallback Tests</strong></summary>
|
|
432
|
+
|
|
433
|
+
- Use fallback when Clipboard API is not available
|
|
434
|
+
- Try fallback when Clipboard API throws error
|
|
435
|
+
|
|
436
|
+
</details>
|
|
437
|
+
|
|
438
|
+
### Running Tests
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
# Run all tests
|
|
442
|
+
pnpm test
|
|
443
|
+
|
|
444
|
+
# Run tests in watch mode
|
|
445
|
+
pnpm test:watch
|
|
446
|
+
|
|
447
|
+
# Run tests with coverage report
|
|
448
|
+
pnpm test --coverage
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Related Packages
|
|
454
|
+
|
|
455
|
+
Explore other hooks in the **@usefy** collection:
|
|
456
|
+
|
|
457
|
+
| Package | Description |
|
|
458
|
+
|---------|-------------|
|
|
459
|
+
| [@usefy/use-local-storage](https://www.npmjs.com/package/@usefy/use-local-storage) | Persistent localStorage |
|
|
460
|
+
| [@usefy/use-session-storage](https://www.npmjs.com/package/@usefy/use-session-storage) | Session storage persistence |
|
|
461
|
+
| [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
|
|
462
|
+
| [@usefy/use-counter](https://www.npmjs.com/package/@usefy/use-counter) | Counter state management |
|
|
463
|
+
| [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
|
|
464
|
+
| [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Contributing
|
|
469
|
+
|
|
470
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
# Clone the repository
|
|
474
|
+
git clone https://github.com/geon0529/usefy.git
|
|
475
|
+
|
|
476
|
+
# Install dependencies
|
|
477
|
+
pnpm install
|
|
478
|
+
|
|
479
|
+
# Run tests
|
|
480
|
+
pnpm test
|
|
481
|
+
|
|
482
|
+
# Build
|
|
483
|
+
pnpm build
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## License
|
|
489
|
+
|
|
490
|
+
MIT © [mirunamu](https://github.com/geon0529)
|
|
491
|
+
|
|
492
|
+
This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
<p align="center">
|
|
497
|
+
<sub>Built with care by the usefy team</sub>
|
|
498
|
+
</p>
|