@catalystsoftware/ui 1.0.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.
Files changed (157) hide show
  1. package/README.md +7 -0
  2. package/components/catalyst-ui/buttons/burger.tsx +207 -0
  3. package/components/catalyst-ui/core/data-display/timeline.tsx +210 -0
  4. package/components/catalyst-ui/core/feedback/alert.tsx +491 -0
  5. package/components/catalyst-ui/core/feedback/spinner-1.tsx +65 -0
  6. package/components/catalyst-ui/core/feedback/toast.tsx +1857 -0
  7. package/components/catalyst-ui/core/navigation/menu.tsx +164 -0
  8. package/components/catalyst-ui/forms/toggle-class.tsx +176 -0
  9. package/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +419 -0
  10. package/components/catalyst-ui/hooks/use-counter.tsx +13 -0
  11. package/components/catalyst-ui/hooks/use-event-listener.tsx +23 -0
  12. package/components/catalyst-ui/hooks/use-export-markdown.tsx +47 -0
  13. package/components/catalyst-ui/hooks/use-focus.tsx +17 -0
  14. package/components/catalyst-ui/hooks/use-interval.tsx +23 -0
  15. package/components/catalyst-ui/hooks/use-is-client.tsx +16 -0
  16. package/components/catalyst-ui/hooks/use-media-query.tsx +19 -0
  17. package/components/catalyst-ui/hooks/use-mobile.tsx +19 -0
  18. package/components/catalyst-ui/hooks/use-resize-observer.tsx +81 -0
  19. package/components/catalyst-ui/hooks/use-timeout.tsx +21 -0
  20. package/components/catalyst-ui/hooks/use-timer.tsx +209 -0
  21. package/components/catalyst-ui/hooks/use-toggle.tsx +12 -0
  22. package/components/catalyst-ui/media/image.tsx +13 -0
  23. package/components/catalyst-ui/overlays/dual-sidebar.tsx +4142 -0
  24. package/components/catalyst-ui/overlays/sidebar-original.tsx +726 -0
  25. package/components/catalyst-ui/primitives/accordion.tsx +250 -0
  26. package/components/catalyst-ui/primitives/alert-dialog.tsx +126 -0
  27. package/components/catalyst-ui/primitives/aspect-ratio.tsx +9 -0
  28. package/components/catalyst-ui/primitives/avatar.tsx +296 -0
  29. package/components/catalyst-ui/primitives/badge.tsx +57 -0
  30. package/components/catalyst-ui/primitives/breadcrumb.tsx +101 -0
  31. package/components/catalyst-ui/primitives/button.tsx +265 -0
  32. package/components/catalyst-ui/primitives/calendar-v4.tsx +208 -0
  33. package/components/catalyst-ui/primitives/calendar.tsx +295 -0
  34. package/components/catalyst-ui/primitives/card.tsx +618 -0
  35. package/components/catalyst-ui/primitives/carousel.tsx +238 -0
  36. package/components/catalyst-ui/primitives/chart.tsx +347 -0
  37. package/components/catalyst-ui/primitives/checkbox.tsx +225 -0
  38. package/components/catalyst-ui/primitives/collapsible.tsx +212 -0
  39. package/components/catalyst-ui/primitives/command.tsx +393 -0
  40. package/components/catalyst-ui/primitives/context-menu.tsx +236 -0
  41. package/components/catalyst-ui/primitives/dialog.tsx +471 -0
  42. package/components/catalyst-ui/primitives/drawer.tsx +761 -0
  43. package/components/catalyst-ui/primitives/dropdown-menu.tsx +290 -0
  44. package/components/catalyst-ui/primitives/empty.tsx +104 -0
  45. package/components/catalyst-ui/primitives/field.tsx +244 -0
  46. package/components/catalyst-ui/primitives/hover-card.tsx +124 -0
  47. package/components/catalyst-ui/primitives/input-otp.tsx +76 -0
  48. package/components/catalyst-ui/primitives/input.tsx +64 -0
  49. package/components/catalyst-ui/primitives/item.tsx +196 -0
  50. package/components/catalyst-ui/primitives/kbd.tsx +75 -0
  51. package/components/catalyst-ui/primitives/label.tsx +24 -0
  52. package/components/catalyst-ui/primitives/navigation-menu.tsx +150 -0
  53. package/components/catalyst-ui/primitives/pagination.tsx +198 -0
  54. package/components/catalyst-ui/primitives/popover.tsx +232 -0
  55. package/components/catalyst-ui/primitives/progress.tsx +34 -0
  56. package/components/catalyst-ui/primitives/radio-group.tsx +43 -0
  57. package/components/catalyst-ui/primitives/resizable.tsx +56 -0
  58. package/components/catalyst-ui/primitives/select.tsx +155 -0
  59. package/components/catalyst-ui/primitives/separator.tsx +74 -0
  60. package/components/catalyst-ui/primitives/sheet.tsx +126 -0
  61. package/components/catalyst-ui/primitives/skeleton.tsx +15 -0
  62. package/components/catalyst-ui/primitives/slider.tsx +27 -0
  63. package/components/catalyst-ui/primitives/switch.tsx +187 -0
  64. package/components/catalyst-ui/primitives/tabs.tsx +335 -0
  65. package/components/catalyst-ui/primitives/textarea.tsx +24 -0
  66. package/components/catalyst-ui/primitives/toggle-group.tsx +55 -0
  67. package/components/catalyst-ui/primitives/toggle.tsx +42 -0
  68. package/components/catalyst-ui/primitives/tooltip.tsx +116 -0
  69. package/components/catalyst-ui/utils/basic-auth.tsx +40 -0
  70. package/components/catalyst-ui/utils/context-storage.tsx +19 -0
  71. package/components/catalyst-ui/utils/cors-middleware.tsx +71 -0
  72. package/components/catalyst-ui/utils/deferred-content.tsx +595 -0
  73. package/components/catalyst-ui/utils/honeypot-middleware.tsx +38 -0
  74. package/components/catalyst-ui/utils/incId.tsx +75 -0
  75. package/components/catalyst-ui/utils/jwk-auth.tsx +36 -0
  76. package/components/catalyst-ui/utils/request-id.tsx +14 -0
  77. package/components/catalyst-ui/utils/secure-headers.tsx +37 -0
  78. package/components/catalyst-ui/utils/server-timing.tsx +23 -0
  79. package/components/catalyst-ui/utils/utils.ts +43 -0
  80. package/components/catalyst-ui/utils/with-cookie.tsx +43 -0
  81. package/components/catalyst-ui/x/accordian-x.tsx +428 -0
  82. package/components/catalyst-ui/x/alert-x.tsx +413 -0
  83. package/components/catalyst-ui/x/animated-text-x.tsx +2242 -0
  84. package/components/catalyst-ui/x/avatar-x.tsx +515 -0
  85. package/components/catalyst-ui/x/badge-x.tsx +670 -0
  86. package/components/catalyst-ui/x/button-X.tsx +2857 -0
  87. package/components/catalyst-ui/x/button-group-x.tsx +847 -0
  88. package/components/catalyst-ui/x/calendar-x.tsx +1910 -0
  89. package/components/catalyst-ui/x/card-x.tsx +2597 -0
  90. package/components/catalyst-ui/x/checkbox-x.tsx +656 -0
  91. package/components/catalyst-ui/x/collapsible-x.tsx +1360 -0
  92. package/components/catalyst-ui/x/combobox-x.tsx +911 -0
  93. package/components/catalyst-ui/x/data-table-x.tsx +1753 -0
  94. package/components/catalyst-ui/x/date-picker-x.tsx +648 -0
  95. package/components/catalyst-ui/x/dialog-x.tsx +659 -0
  96. package/components/catalyst-ui/x/dropdown-menu-x.tsx +612 -0
  97. package/components/catalyst-ui/x/hover-card-x.tsx +375 -0
  98. package/components/catalyst-ui/x/icon-x.tsx +840 -0
  99. package/components/catalyst-ui/x/input-mask-x.tsx +981 -0
  100. package/components/catalyst-ui/x/input-otp-x.tsx +659 -0
  101. package/components/catalyst-ui/x/loader-x.tsx +1757 -0
  102. package/components/catalyst-ui/x/pagination-x.tsx +622 -0
  103. package/components/catalyst-ui/x/popover-x.tsx +744 -0
  104. package/components/catalyst-ui/x/radio-group-x.tsx +499 -0
  105. package/components/catalyst-ui/x/select-x.tsx +1127 -0
  106. package/components/catalyst-ui/x/sheet-x.tsx +668 -0
  107. package/components/catalyst-ui/x/switch-x.tsx +681 -0
  108. package/components/catalyst-ui/x/table-x.tsx +574 -0
  109. package/components/catalyst-ui/x/tabs-x.tsx +839 -0
  110. package/components/catalyst-ui/x/textarea-x.tsx +1263 -0
  111. package/components/catalyst-ui/x/tooltip-x.tsx +396 -0
  112. package/components/catalyst-ui/x/tracker-x.tsx +560 -0
  113. package/data/bg-data.tsx +901 -0
  114. package/data/buttons-data.tsx +2327 -0
  115. package/data/charts-data.tsx +102 -0
  116. package/data/chat-data.tsx +83 -0
  117. package/data/code-data.tsx +1040 -0
  118. package/data/comboboxes-data.tsx +1843 -0
  119. package/data/command-data.tsx +1381 -0
  120. package/data/core-data.tsx +15953 -0
  121. package/data/crm-data.tsx +47 -0
  122. package/data/data.tsx +159 -0
  123. package/data/date-and-time-data.tsx +554 -0
  124. package/data/dependencies.tsx +7 -0
  125. package/data/ecommerce-data.tsx +1387 -0
  126. package/data/forms-data.tsx +7890 -0
  127. package/data/hooks-data.tsx +5487 -0
  128. package/data/index.ts +34 -0
  129. package/data/inputs-data.tsx +557 -0
  130. package/data/interactive-data.tsx +5394 -0
  131. package/data/lofi-data.tsx +18295 -0
  132. package/data/marketing-data.tsx +2546 -0
  133. package/data/media-data.tsx +1510 -0
  134. package/data/motion-data.tsx +5801 -0
  135. package/data/overlay-data.tsx +4136 -0
  136. package/data/pdf-data.tsx +124 -0
  137. package/data/pos-data.tsx +213 -0
  138. package/data/postcss.config.js +6 -0
  139. package/data/primitive-data.tsx +5170 -0
  140. package/data/prompt-data.tsx +1226 -0
  141. package/data/requiredLibs.ts +4 -0
  142. package/data/sandbox-data.tsx +1 -0
  143. package/data/sidebars-data.tsx +5421 -0
  144. package/data/stacks-data.tsx +32 -0
  145. package/data/table-data.tsx +706 -0
  146. package/data/tailwind.config.js +3830 -0
  147. package/data/tailwind.config.ngin.js +3830 -0
  148. package/data/tailwind.css +431 -0
  149. package/data/tools-data.tsx +6910 -0
  150. package/data/typography-data.tsx +2050 -0
  151. package/data/utils-data.tsx +6500 -0
  152. package/data/x-data.tsx +1171 -0
  153. package/dist/index.d.ts +3 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +30245 -0
  156. package/dist/index.js.map +362 -0
  157. package/package.json +50 -0
@@ -0,0 +1,419 @@
1
+
2
+ import * as React from "react"
3
+
4
+
5
+ /**
6
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ useCopyToClipboard Hook ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
7
+ * ★ ━━━━ ☆ ━━━━ ━━━━ ☆ ━━━━ ★
8
+ * useCopyToClipboard: A React hook for copying text to clipboard with timeout and callbacks
9
+ *
10
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Basic Usage ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
11
+ * ```jsx
12
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
13
+ *
14
+ * return (
15
+ * <button onClick={() => copyToClipboard('Hello World')}>
16
+ * {isCopied ? 'Copied!' : 'Copy Text'}
17
+ * </button>
18
+ * )
19
+ * ```
20
+ *
21
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ With Custom Timeout ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
22
+ * ```jsx
23
+ * // 2 second timeout
24
+ * const { copyToClipboard } = useCopyToClipboard({
25
+ * timeout: 2000
26
+ * })
27
+ *
28
+ * // 10 second timeout (for important info)
29
+ * const { copyToClipboard } = useCopyToClipboard({
30
+ * timeout: 10000
31
+ * })
32
+ * ```
33
+ *
34
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ With Callback ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
35
+ * ```jsx
36
+ * const { copyToClipboard } = useCopyToClipboard({
37
+ * onCopy: () => {
38
+ * // Show success toast
39
+ * toast.success('Copied to clipboard!')
40
+ *
41
+ * // Track analytics event
42
+ * analytics.track('text_copied')
43
+ *
44
+ * // Update UI state
45
+ * setShowCopiedFeedback(true)
46
+ * }
47
+ * })
48
+ * ```
49
+ *
50
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ In Component Examples ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
51
+ * ```jsx
52
+ * // Copy button component
53
+ * function CopyButton({ text }) {
54
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
55
+ *
56
+ * return (
57
+ * <button
58
+ * onClick={() => copyToClipboard(text)}
59
+ * className={isCopied ? 'copied' : ''}
60
+ * >
61
+ * {isCopied ? '✓ Copied' : 'Copy'}
62
+ * </button>
63
+ * )
64
+ * }
65
+ *
66
+ * // Code snippet with copy
67
+ * function CodeSnippet({ code }) {
68
+ * const { isCopied, copyToClipboard } = useCopyToClipboard({
69
+ * timeout: 3000,
70
+ * onCopy: () => console.log('Code copied:', code)
71
+ * })
72
+ *
73
+ * return (
74
+ * <div className="code-block">
75
+ * <pre>{code}</pre>
76
+ * <button onClick={() => copyToClipboard(code)}>
77
+ * {isCopied ? 'Copied!' : 'Copy Code'}
78
+ * </button>
79
+ * </div>
80
+ * )
81
+ * }
82
+ * ```
83
+ *
84
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Multiple Copy Instances ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
85
+ * ```jsx
86
+ * // Track which item was copied
87
+ * function CopyList({ items }) {
88
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
89
+ *
90
+ * return (
91
+ * <ul>
92
+ * {items.map((item) => (
93
+ * <li key={item.id}>
94
+ * {item.text}
95
+ * <button
96
+ * onClick={() => copyToClipboard(item.text)}
97
+ * aria-label={`Copy ${item.name}`}
98
+ * >
99
+ * {isCopied === item.text ? '✓' : 'Copy'}
100
+ * </button>
101
+ * </li>
102
+ * ))}
103
+ * </ul>
104
+ * )
105
+ * }
106
+ *
107
+ * // Compare current copied text
108
+ * function isCurrentlyCopied(text) {
109
+ * return isCopied === text
110
+ * }
111
+ * ```
112
+ *
113
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ With UI Feedback Patterns ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
114
+ * ```jsx
115
+ * // Tooltip feedback
116
+ * function CopyWithTooltip({ text }) {
117
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
118
+ *
119
+ * return (
120
+ * <Tooltip content={isCopied ? 'Copied!' : 'Click to copy'}>
121
+ * <button onClick={() => copyToClipboard(text)}>
122
+ * {text}
123
+ * </button>
124
+ * </Tooltip>
125
+ * )
126
+ * }
127
+ *
128
+ * // Icon feedback
129
+ * function CopyIconButton({ text }) {
130
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
131
+ *
132
+ * return (
133
+ * <button onClick={() => copyToClipboard(text)}>
134
+ * {isCopied ? <CheckIcon /> : <CopyIcon />}
135
+ * </button>
136
+ * )
137
+ * }
138
+ *
139
+ * // Toast notification
140
+ * function CopyWithToast({ text }) {
141
+ * const { copyToClipboard } = useCopyToClipboard({
142
+ * onCopy: () => toast('Copied to clipboard!')
143
+ * })
144
+ *
145
+ * return (
146
+ * <button onClick={() => copyToClipboard(text)}>
147
+ * Copy
148
+ * </button>
149
+ * )
150
+ * }
151
+ * ```
152
+ *
153
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Hook Return Values ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
154
+ * ```typescript
155
+ * interface UseCopyToClipboardReturn {
156
+ * // The currently copied text (null if nothing copied or timeout expired)
157
+ * isCopied: string | null
158
+ *
159
+ * // Function to copy text to clipboard
160
+ * copyToClipboard: (value: string) => void
161
+ * }
162
+ *
163
+ * // Usage example
164
+ * const { isCopied, copyToClipboard } = useCopyToClipboard(options)
165
+ *
166
+ * // Check if specific text is currently copied
167
+ * const isThisCopied = isCopied === 'specific text'
168
+ *
169
+ * // Check if anything is copied
170
+ * const hasCopied = isCopied !== null
171
+ * ```
172
+ *
173
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Hook Options ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
174
+ * ```typescript
175
+ * interface UseCopyToClipboardOptions {
176
+ * // Time in milliseconds before resetting copied state (default: 4000)
177
+ * timeout?: number
178
+ *
179
+ * // Callback function triggered after successful copy
180
+ * onCopy?: () => void
181
+ * }
182
+ *
183
+ * // Examples of different configurations
184
+ * const defaultOptions = {} // Uses default 4000ms timeout
185
+ *
186
+ * const shortFeedback = { timeout: 1000 } // Quick feedback
187
+ *
188
+ * const withAnalytics = {
189
+ * timeout: 5000,
190
+ * onCopy: () => trackEvent('copy_action')
191
+ * }
192
+ *
193
+ * const withMultipleCallbacks = {
194
+ * onCopy: () => {
195
+ * showToast()
196
+ * trackAnalytics()
197
+ * updateUIState()
198
+ * }
199
+ * }
200
+ * ```
201
+ *
202
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Error Handling ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
203
+ * ```jsx
204
+ * // Safe copy with error handling
205
+ * function SafeCopyButton({ text }) {
206
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
207
+ *
208
+ * const handleCopy = () => {
209
+ * try {
210
+ * copyToClipboard(text)
211
+ * } catch (error) {
212
+ * console.error('Failed to copy:', error)
213
+ * // Show error message to user
214
+ * toast.error('Failed to copy to clipboard')
215
+ * }
216
+ * }
217
+ *
218
+ * return (
219
+ * <button onClick={handleCopy}>
220
+ * {isCopied ? 'Copied!' : 'Copy'}
221
+ * </button>
222
+ * )
223
+ * }
224
+ *
225
+ * // Check clipboard API support
226
+ * function CopyButton({ text }) {
227
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
228
+ *
229
+ * const isClipboardSupported =
230
+ * typeof window !== 'undefined' &&
231
+ * navigator?.clipboard?.writeText
232
+ *
233
+ * if (!isClipboardSupported) {
234
+ * return <span>Clipboard not supported in this browser</span>
235
+ * }
236
+ *
237
+ * return (
238
+ * <button onClick={() => copyToClipboard(text)}>
239
+ * {isCopied ? 'Copied!' : 'Copy'}
240
+ * </button>
241
+ * )
242
+ * }
243
+ * ```
244
+ *
245
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Advanced Usage Patterns ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
246
+ * ```jsx
247
+ * // Debounced copy (prevent rapid clicks)
248
+ * function DebouncedCopyButton({ text }) {
249
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
250
+ * const [isDebouncing, setIsDebouncing] = useState(false)
251
+ *
252
+ * const handleCopy = () => {
253
+ * if (isDebouncing) return
254
+ *
255
+ * setIsDebouncing(true)
256
+ * copyToClipboard(text)
257
+ *
258
+ * setTimeout(() => {
259
+ * setIsDebouncing(false)
260
+ * }, 1000)
261
+ * }
262
+ *
263
+ * return (
264
+ * <button
265
+ * onClick={handleCopy}
266
+ * disabled={isDebouncing}
267
+ * >
268
+ * {isDebouncing ? 'Copying...' : isCopied ? 'Copied!' : 'Copy'}
269
+ * </button>
270
+ * )
271
+ * }
272
+ *
273
+ * // Copy with confirmation
274
+ * function CopyWithConfirmation({ text }) {
275
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
276
+ * const [showConfirm, setShowConfirm] = useState(false)
277
+ *
278
+ * const handleCopy = () => {
279
+ * if (text.length > 1000) {
280
+ * setShowConfirm(true)
281
+ * } else {
282
+ * copyToClipboard(text)
283
+ * }
284
+ * }
285
+ *
286
+ * return (
287
+ * <>
288
+ * <button onClick={handleCopy}>
289
+ * Copy
290
+ * </button>
291
+ *
292
+ * {showConfirm && (
293
+ * <ConfirmationDialog
294
+ * onConfirm={() => {
295
+ * copyToClipboard(text)
296
+ * setShowConfirm(false)
297
+ * }}
298
+ * onCancel={() => setShowConfirm(false)}
299
+ * />
300
+ * )}
301
+ * </>
302
+ * )
303
+ * }
304
+ * ```
305
+ *
306
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Integration Examples ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
307
+ * ```jsx
308
+ * // In a form field
309
+ * function CopyableInput({ value }) {
310
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
311
+ *
312
+ * return (
313
+ * <div className="input-group">
314
+ * <input type="text" value={value} readOnly />
315
+ * <button
316
+ * onClick={() => copyToClipboard(value)}
317
+ * className={isCopied ? 'success' : ''}
318
+ * >
319
+ * {isCopied ? '✓' : 'Copy'}
320
+ * </button>
321
+ * </div>
322
+ * )
323
+ * }
324
+ *
325
+ * // In a data table
326
+ * function DataTable({ rows }) {
327
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
328
+ *
329
+ * return (
330
+ * <table>
331
+ * <tbody>
332
+ * {rows.map(row => (
333
+ * <tr key={row.id}>
334
+ * <td>{row.value}</td>
335
+ * <td>
336
+ * <button
337
+ * onClick={() => copyToClipboard(row.value)}
338
+ * aria-label={`Copy ${row.name}`}
339
+ * >
340
+ * {isCopied === row.value ? 'Copied' : 'Copy'}
341
+ * </button>
342
+ * </td>
343
+ * </tr>
344
+ * ))}
345
+ * </tbody>
346
+ * </table>
347
+ * )
348
+ * }
349
+ *
350
+ * // With rich text/html
351
+ * function CopyHTMLContent({ html }) {
352
+ * const { isCopied, copyToClipboard } = useCopyToClipboard()
353
+ *
354
+ * const handleCopyHTML = async () => {
355
+ * // Convert HTML to plain text for clipboard
356
+ * const plainText = html.replace(/<[^>]*>/g, '')
357
+ * copyToClipboard(plainText)
358
+ * }
359
+ *
360
+ * return (
361
+ * <div>
362
+ * <div dangerouslySetInnerHTML={{ __html: html }} />
363
+ * <button onClick={handleCopyHTML}>
364
+ * {isCopied ? 'Copied as text' : 'Copy text content'}
365
+ * </button>
366
+ * </div>
367
+ * )
368
+ * }
369
+ * ```
370
+ *
371
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Browser Support Notes ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
372
+ * - **Modern Browsers**: Full support in Chrome, Firefox, Safari, Edge
373
+ * - **HTTPS Requirement**: Clipboard API requires secure context (HTTPS)
374
+ * - **User Permission**: Some browsers may require user permission
375
+ * - **Fallback Consideration**: Consider fallback for older browsers
376
+ *
377
+ * ★ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━ Best Practices ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ☆ ━━━━━━━━━━━━━━ ★
378
+ * 1. **Provide Visual Feedback**: Always show when text is copied
379
+ * 2. **Use Appropriate Timeout**: Match timeout to user reading speed
380
+ * 3. **Handle Empty Values**: Check for empty or null text
381
+ * 4. **Add Accessibility**: Include ARIA labels for screen readers
382
+ * 5. **Consider Context**: Show what was copied (especially in lists)
383
+ * 6. **Error Recovery**: Provide fallback options if clipboard fails
384
+ * 7. **Security**: Be cautious with sensitive data in clipboard
385
+ *
386
+ */
387
+
388
+
389
+ export function useCopyToClipboard({
390
+ timeout = 4000,
391
+ onCopy,
392
+ }: {
393
+ timeout?: number
394
+ onCopy?: () => void
395
+ } = {}) {
396
+ const [isCopied, setIsCopied] =React.useState<string | null>(null)
397
+
398
+ const copyToClipboard = (value: string) => {
399
+ if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
400
+ return
401
+ }
402
+
403
+ if (!value) return
404
+
405
+ navigator.clipboard.writeText(value).then(() => {
406
+ setIsCopied(value)
407
+
408
+ if (onCopy) {
409
+ onCopy()
410
+ }
411
+
412
+ setTimeout(() => {
413
+ setIsCopied(null)
414
+ }, timeout)
415
+ }, console.error)
416
+ }
417
+
418
+ return { isCopied, copyToClipboard }
419
+ }
@@ -0,0 +1,13 @@
1
+ // app/hooks/index.ts
2
+ import { useState, useEffect, useRef, useCallback } from 'react';
3
+
4
+ export function useCounter(initialValue = 0, step = 1) {
5
+ const [count, setCount] = useState<number>(initialValue);
6
+
7
+ const increment = useCallback(() => setCount(c => c + step), [step]);
8
+ const decrement = useCallback(() => setCount(c => c - step), [step]);
9
+ const reset = useCallback(() => setCount(initialValue), [initialValue]);
10
+ const setValue = useCallback((value: number) => setCount(value), []);
11
+
12
+ return { count, increment, decrement, reset, setValue } as const;
13
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect, RefObject } from 'react';
2
+
3
+ export function useEventListener<K extends keyof HTMLElementEventMap>(
4
+ eventName: K,
5
+ handler: (event: HTMLElementEventMap[K]) => void,
6
+ element?: RefObject<HTMLElement>,
7
+ options?: boolean | AddEventListenerOptions
8
+ ) {
9
+ useEffect(() => {
10
+ const targetElement = element?.current || window;
11
+
12
+ if (!targetElement?.addEventListener) return;
13
+
14
+ const eventListener = (event: HTMLElementEventMap[K]) => handler(event);
15
+
16
+ targetElement.addEventListener(eventName, eventListener, options);
17
+
18
+ return () => {
19
+ targetElement.removeEventListener(eventName, eventListener, options);
20
+ };
21
+ }, [eventName, handler, element, options]);
22
+ }
23
+
@@ -0,0 +1,47 @@
1
+ import { useCallback } from 'react';
2
+
3
+ export function useExportMarkdown() {
4
+ const exportMarkdown = useCallback((content: string, filename: string = 'document.md') => {
5
+ try {
6
+ if (!content) {
7
+ console.warn('No content provided for export');
8
+ return;
9
+ }
10
+
11
+ const blob = new Blob([content], { type: 'text/markdown' });
12
+ const url = URL.createObjectURL(blob);
13
+ const a = document.createElement('a');
14
+ a.href = url;
15
+ a.download = filename.endsWith('.md') ? filename : `${filename}.md`;
16
+ document.body.appendChild(a);
17
+ a.click();
18
+ document.body.removeChild(a);
19
+
20
+ setTimeout(() => URL.revokeObjectURL(url), 100);
21
+ } catch (error) {
22
+ console.error('Failed to export markdown:', error);
23
+ }
24
+ }, []);
25
+
26
+ return exportMarkdown;
27
+ }
28
+
29
+
30
+ /**
31
+ * import { useExportMarkdown } from './hooks/useExportMarkdown';
32
+
33
+ function MyComponent() {
34
+ const exportMarkdown = useExportMarkdown();
35
+ const [markdown, setMarkdown] = useState('');
36
+
37
+ const handleExport = () => {
38
+ exportMarkdown(markdown, 'custom-filename.md');
39
+ };
40
+
41
+ return (
42
+ <button onClick={handleExport}>
43
+ Export as Markdown
44
+ </button>
45
+ );
46
+ }
47
+ */
@@ -0,0 +1,17 @@
1
+ import { FocusProps, useFocus as useAriaFocus } from 'react-aria';
2
+ import { useRef } from 'react';
3
+
4
+ export interface UseFocusOptions extends FocusProps {
5
+ isDisabled?: boolean;
6
+ }
7
+
8
+ export function useFocus(props: UseFocusOptions = {}) {
9
+ const ref = useRef<HTMLElement>(null);
10
+ const { focusProps, isFocused } = useAriaFocus(props, ref);
11
+
12
+ return {
13
+ focusProps,
14
+ isFocused,
15
+ ref
16
+ };
17
+ }
@@ -0,0 +1,23 @@
1
+ // app/hooks/index.ts
2
+ import { useState, useEffect, useRef, useCallback } from 'react';
3
+
4
+ export function useInterval(callback: () => void, delay: number | null) {
5
+ const savedCallback = useRef<() => void>();
6
+
7
+ useEffect(() => {
8
+ savedCallback.current = callback;
9
+ }, [callback]);
10
+
11
+ useEffect(() => {
12
+ function tick() {
13
+ if (savedCallback.current) {
14
+ savedCallback.current();
15
+ }
16
+ }
17
+
18
+ if (delay !== null) {
19
+ const id = setInterval(tick, delay);
20
+ return () => clearInterval(id);
21
+ }
22
+ }, [delay]);
23
+ }
@@ -0,0 +1,16 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { cn } from '~/components/catalyst-ui/utils'
3
+
4
+ function useIsClient(): boolean {
5
+ const [isClient, setIsClient] = useState(false)
6
+
7
+ useEffect(() => {
8
+ setIsClient(true)
9
+ }, [])
10
+
11
+ return isClient
12
+ }
13
+
14
+
15
+
16
+ export { useIsClient }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ export function useMediaQuery(query: string) {
4
+ const [value, setValue] = React.useState(false)
5
+
6
+ React.useEffect(() => {
7
+ function onChange(event: MediaQueryListEvent) {
8
+ setValue(event.matches)
9
+ }
10
+
11
+ const result = matchMedia(query)
12
+ result.addEventListener("change", onChange)
13
+ setValue(result.matches)
14
+
15
+ return () => result.removeEventListener("change", onChange)
16
+ }, [query])
17
+
18
+ return value
19
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener("change", onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener("change", onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
@@ -0,0 +1,81 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import { cn } from '~/components/catalyst-ui/utils'
3
+
4
+ type BoxModel = 'content-box' | 'border-box' | 'device-pixel-content-box'
5
+
6
+ type Size = {
7
+ width: number
8
+ height: number
9
+ }
10
+
11
+ type UseResizeObserverOptions<T extends HTMLElement> = {
12
+ ref: React.RefObject<T>
13
+ onResize?: (size: Size) => void
14
+ box?: BoxModel
15
+ }
16
+
17
+ type UseResizeObserverReturn = {
18
+ width: number | undefined
19
+ height: number | undefined
20
+ }
21
+
22
+ function useResizeObserver<T extends HTMLElement = HTMLElement>(
23
+ options: UseResizeObserverOptions<T>
24
+ ): UseResizeObserverReturn {
25
+ const { ref, onResize, box = 'content-box' } = options
26
+ const [size, setSize] = useState<Size | undefined>(undefined)
27
+
28
+ useEffect(() => {
29
+ const element = ref.current
30
+ if (!element) return
31
+
32
+ if (typeof window === 'undefined' || !window.ResizeObserver) {
33
+ return
34
+ }
35
+
36
+ const resizeObserver = new ResizeObserver((entries) => {
37
+ if (!Array.isArray(entries) || !entries.length) {
38
+ return
39
+ }
40
+
41
+ const entry = entries[0]
42
+ let width: number
43
+ let height: number
44
+
45
+ if (box === 'border-box') {
46
+ width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width
47
+ height = entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height
48
+ } else if (box === 'device-pixel-content-box') {
49
+ width = entry.devicePixelContentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width
50
+ height = entry.devicePixelContentBoxSize?.[0]?.blockSize ?? entry.contentRect.height
51
+ } else {
52
+ width = entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width
53
+ height = entry.contentBoxSize?.[0]?.blockSize ?? entry.contentRect.height
54
+ }
55
+
56
+ const newSize = { width, height }
57
+
58
+ if (onResize) {
59
+ onResize(newSize)
60
+ } else {
61
+ setSize(newSize)
62
+ }
63
+ })
64
+
65
+ resizeObserver.observe(element, { box })
66
+
67
+ return () => {
68
+ resizeObserver.disconnect()
69
+ }
70
+ }, [ref, box, onResize])
71
+
72
+ return {
73
+ width: size?.width,
74
+ height: size?.height,
75
+ }
76
+ }
77
+
78
+
79
+
80
+
81
+ export { useResizeObserver, }