aural-ui 2.0.2 → 2.0.3

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 (41) hide show
  1. package/dist/components/button/Button.stories.tsx +43 -0
  2. package/dist/components/button/index.tsx +10 -4
  3. package/dist/components/chip/index.tsx +3 -3
  4. package/dist/components/icon-button/IconButton.stories.tsx +48 -0
  5. package/dist/components/icon-button/index.tsx +6 -2
  6. package/dist/components/input/index.tsx +4 -0
  7. package/dist/components/overlay/index.tsx +22 -5
  8. package/dist/components/tag/index.tsx +2 -2
  9. package/dist/components/textarea/index.tsx +2 -0
  10. package/dist/components/tooltip/index.tsx +45 -0
  11. package/dist/hooks/index.ts +1 -0
  12. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +744 -0
  13. package/dist/hooks/use-change-state/index.tsx +17 -0
  14. package/dist/hooks/use-change-state/meta.ts +6 -0
  15. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +1204 -0
  16. package/dist/icons/circle-tick-icon/index.tsx +22 -0
  17. package/dist/icons/circle-tick-icon/meta.ts +8 -0
  18. package/dist/icons/copy-icon/CopyIcon.stories.tsx +1021 -0
  19. package/dist/icons/copy-icon/index.tsx +21 -0
  20. package/dist/icons/copy-icon/meta.ts +8 -0
  21. package/dist/icons/download-icon/DownloadIcon.stories.tsx +877 -0
  22. package/dist/icons/download-icon/index.tsx +22 -0
  23. package/dist/icons/download-icon/meta.ts +8 -0
  24. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +1109 -0
  25. package/dist/icons/filter-bar-row-icon/index.tsx +24 -0
  26. package/dist/icons/filter-bar-row-icon/meta.ts +8 -0
  27. package/dist/icons/index.ts +8 -0
  28. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +1159 -0
  29. package/dist/icons/notepad-icon/index.tsx +21 -0
  30. package/dist/icons/notepad-icon/meta.ts +8 -0
  31. package/dist/icons/notes-icon/NotesIcon.stories.tsx +1159 -0
  32. package/dist/icons/notes-icon/index.tsx +24 -0
  33. package/dist/icons/notes-icon/meta.ts +8 -0
  34. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +936 -0
  35. package/dist/icons/paper-plane-icon/index.tsx +33 -0
  36. package/dist/icons/paper-plane-icon/meta.ts +8 -0
  37. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +907 -0
  38. package/dist/icons/suggestion-icon/index.tsx +33 -0
  39. package/dist/icons/suggestion-icon/meta.ts +8 -0
  40. package/dist/index.js +1 -1
  41. package/package.json +1 -1
@@ -0,0 +1,744 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react"
3
+
4
+ import { LightBulbSimpleIcon } from "src/ui/icons"
5
+
6
+ import { useChangeState } from "."
7
+
8
+ const ChangeStateDemo: React.FC<{
9
+ title?: string
10
+ }> = ({ title = "useChangeState Demo" }) => {
11
+ const { open, onOpenChange } = useChangeState()
12
+
13
+ return (
14
+ <div className="!space-y-6 rounded-lg border border-white/10 bg-white/5 !p-6">
15
+ <h3 className="!text-xl font-semibold !text-white">{title}</h3>
16
+
17
+ {/* State Display */}
18
+ <div className="rounded-lg border border-blue-500/20 bg-blue-500/10 !p-4">
19
+ <h4 className="!mb-3 !text-lg font-semibold !text-blue-300">
20
+ Current State
21
+ </h4>
22
+ <div className="flex items-center justify-between rounded-lg bg-black/20 !p-3">
23
+ <span className="!text-sm !text-white/70">Open:</span>
24
+ <span
25
+ className={`font-mono !text-lg font-bold ${
26
+ open ? "!text-green-300" : "!text-red-300"
27
+ }`}
28
+ >
29
+ {String(open)}
30
+ </span>
31
+ </div>
32
+ </div>
33
+
34
+ {/* Controls */}
35
+ <div className="!space-y-3">
36
+ <button
37
+ onClick={() => onOpenChange()}
38
+ className={`w-full rounded-lg border !px-4 !py-3 !text-sm font-medium transition-colors ${
39
+ open
40
+ ? "border-green-500/30 bg-green-500/20 !text-green-300 hover:bg-green-500/30"
41
+ : "border-red-500/30 bg-red-500/20 !text-red-300 hover:bg-red-500/30"
42
+ }`}
43
+ >
44
+ Toggle State ({open ? "ON" : "OFF"})
45
+ </button>
46
+
47
+ <div className="grid grid-cols-2 gap-3">
48
+ <button
49
+ onClick={() => onOpenChange(true)}
50
+ className="rounded-lg border border-green-500/30 bg-green-500/20 !px-3 !py-2 !text-sm !text-green-300 transition-colors hover:bg-green-500/30"
51
+ >
52
+ Set True
53
+ </button>
54
+ <button
55
+ onClick={() => onOpenChange(false)}
56
+ className="rounded-lg border border-red-500/30 bg-red-500/20 !px-3 !py-2 !text-sm !text-red-300 transition-colors hover:bg-red-500/30"
57
+ >
58
+ Set False
59
+ </button>
60
+ </div>
61
+ </div>
62
+
63
+ {/* Usage Information */}
64
+ <div className="rounded-lg border border-white/10 bg-black/20 !p-4">
65
+ <h4 className="!mb-3 !text-lg font-semibold !text-white">Hook Usage</h4>
66
+ <div className="!space-y-2 !text-sm">
67
+ <div className="!text-white/70">
68
+ <span className="font-medium !text-white">Pattern:</span> const{" "}
69
+ {`{ open, onOpenChange }`} = useChangeState()
70
+ </div>
71
+ <div className="!text-white/70">
72
+ <span className="font-medium !text-white">Toggle:</span>{" "}
73
+ onOpenChange() - toggles current state
74
+ </div>
75
+ <div className="!text-white/70">
76
+ <span className="font-medium !text-white">Set value:</span>{" "}
77
+ onOpenChange(true/false) - sets specific value
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ const meta: Meta<typeof ChangeStateDemo> = {
86
+ title: "Hooks/useChangeState",
87
+ component: ChangeStateDemo,
88
+ parameters: {
89
+ layout: "fullscreen",
90
+ backgrounds: {
91
+ default: "dark",
92
+ values: [
93
+ { name: "dark", value: "#0a0a0a" },
94
+ { name: "darker", value: "#000000" },
95
+ { name: "light", value: "#ffffff" },
96
+ ],
97
+ },
98
+ docs: {
99
+ page: () => (
100
+ <>
101
+ {/* Override default docs styling */}
102
+ <style>
103
+ {`
104
+ .sbdocs-wrapper {
105
+ padding: 0 !important;
106
+ max-width: none !important;
107
+ background: transparent !important;
108
+ }
109
+ .sbdocs-content {
110
+ max-width: none !important;
111
+ padding: 0 !important;
112
+ margin: 0 !important;
113
+ background: transparent !important;
114
+ }
115
+ .docs-story {
116
+ background: transparent !important;
117
+ }
118
+ .sbdocs {
119
+ background: transparent !important;
120
+ }
121
+ body {
122
+ background: #0a0a0a !important;
123
+ }
124
+ #storybook-docs {
125
+ background: #0a0a0a !important;
126
+ }
127
+ .sbdocs-preview {
128
+ background: transparent !important;
129
+ border: none !important;
130
+ }
131
+ .sbdocs-h1, .sbdocs-h2, .sbdocs-h3, .sbdocs-h4, .sbdocs-h5, .sbdocs-h6 {
132
+ color: white !important;
133
+ }
134
+ .sbdocs-p, .sbdocs-li {
135
+ color: rgba(255, 255, 255, 0.7) !important;
136
+ }
137
+ .sbdocs-code {
138
+ background: rgba(255, 255, 255, 0.1) !important;
139
+ color: rgba(168, 85, 247, 1) !important;
140
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
141
+ }
142
+ .sbdocs-pre {
143
+ background: rgba(0, 0, 0, 0.4) !important;
144
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
145
+ }
146
+ .sbdocs-table {
147
+ background: rgba(255, 255, 255, 0.05) !important;
148
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
149
+ }
150
+ .sbdocs-table th {
151
+ background: rgba(255, 255, 255, 0.05) !important;
152
+ color: white !important;
153
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
154
+ }
155
+ .sbdocs-table td {
156
+ color: rgba(255, 255, 255, 0.7) !important;
157
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
158
+ }
159
+ `}
160
+ </style>
161
+
162
+ <div className="min-h-screen bg-gradient-to-br from-gray-900 via-purple-900/20 to-gray-900">
163
+ {/* Header */}
164
+ <div className="relative overflow-hidden border-b border-white/10 bg-black/20 backdrop-blur-xl">
165
+ <div className="absolute inset-0 bg-gradient-to-r from-purple-500/10 via-transparent to-indigo-500/10" />
166
+ <div className="relative !mx-auto max-w-7xl !px-6 !py-16">
167
+ <div className="!space-y-6 text-center">
168
+ <div className="!mx-auto flex !h-24 !w-24 items-center justify-center rounded-2xl border border-purple-500/30 bg-gradient-to-br from-purple-500/20 to-indigo-500/20">
169
+ <span className="!text-4xl">
170
+ <LightBulbSimpleIcon className="text-fm-primary size-10" />
171
+ </span>
172
+ </div>
173
+ <h1 className="!text-fm-primary !text-5xl font-bold">
174
+ useChangeState
175
+ </h1>
176
+ <p className="!mx-auto max-w-3xl !text-xl leading-relaxed !text-white/70">
177
+ A versatile React hook that manages boolean state with a
178
+ flexible toggle function. Perfect for modals, dropdowns,
179
+ accordions, and any component that needs show/hide
180
+ functionality with customizable state control.
181
+ </p>
182
+
183
+ {/* Stats */}
184
+ <div className="flex items-center justify-center gap-8 !pt-8">
185
+ <div className="text-center">
186
+ <div className="!text-3xl font-bold !text-purple-300">
187
+ Flexible
188
+ </div>
189
+ <div className="!text-sm !text-white/60">
190
+ Toggle or set specific value
191
+ </div>
192
+ </div>
193
+ <div className="!h-8 !w-px bg-white/20" />
194
+ <div className="text-center">
195
+ <div className="!text-3xl font-bold !text-indigo-300">
196
+ Optimized
197
+ </div>
198
+ <div className="!text-sm !text-white/60">
199
+ Memoized callbacks
200
+ </div>
201
+ </div>
202
+ <div className="!h-8 !w-px bg-white/20" />
203
+ <div className="text-center">
204
+ <div className="!text-3xl font-bold !text-cyan-300">
205
+ Intuitive
206
+ </div>
207
+ <div className="!text-sm !text-white/60">
208
+ Natural API design
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ {/* Content */}
217
+ <div className="!mx-auto max-w-7xl !space-y-16 !px-6 !py-12">
218
+ {/* Quick Usage */}
219
+ <div className="!space-y-8">
220
+ <h2 className="text-center !text-3xl font-bold !text-white">
221
+ Quick Start
222
+ </h2>
223
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
224
+ <div className="!space-y-4">
225
+ <h3 className="!text-xl font-semibold !text-purple-300">
226
+ Basic Usage
227
+ </h3>
228
+ <div className="rounded-lg bg-black/40 !p-4">
229
+ <pre className="overflow-x-auto !text-sm !text-green-300">
230
+ {`import { useChangeState } from "@hooks/use-change-state"
231
+
232
+ function Modal() {
233
+ const { open, onOpenChange } = useChangeState()
234
+
235
+ return (
236
+ <>
237
+ <button onClick={() => onOpenChange(true)}>
238
+ Open Modal
239
+ </button>
240
+
241
+ {open && (
242
+ <div className="modal-overlay">
243
+ <div className="modal-content">
244
+ <h2>Modal Title</h2>
245
+ <button onClick={() => onOpenChange(false)}>
246
+ Close
247
+ </button>
248
+ <button onClick={() => onOpenChange()}>
249
+ Toggle
250
+ </button>
251
+ </div>
252
+ </div>
253
+ )}
254
+ </>
255
+ )
256
+ }`}
257
+ </pre>
258
+ </div>
259
+ </div>
260
+
261
+ <div className="!space-y-4">
262
+ <h3 className="!text-xl font-semibold !text-purple-300">
263
+ Hook Properties
264
+ </h3>
265
+ <div className="!space-y-3 rounded-lg border border-white/10 bg-white/5 !p-4">
266
+ <div className="flex justify-between">
267
+ <span className="!text-sm !text-white/70">
268
+ Returns:
269
+ </span>
270
+ <span className="!text-sm font-medium !text-white">
271
+ Object with state & handler
272
+ </span>
273
+ </div>
274
+ <div className="flex justify-between">
275
+ <span className="!text-sm !text-white/70">
276
+ Initial state:
277
+ </span>
278
+ <span className="!text-sm font-medium !text-white">
279
+ false
280
+ </span>
281
+ </div>
282
+ <div className="flex justify-between">
283
+ <span className="!text-sm !text-white/70">
284
+ Toggle behavior:
285
+ </span>
286
+ <span className="!text-sm font-medium !text-white">
287
+ Smart default
288
+ </span>
289
+ </div>
290
+ <div className="flex justify-between">
291
+ <span className="!text-sm !text-white/70">
292
+ Performance:
293
+ </span>
294
+ <span className="!text-sm font-medium !text-white">
295
+ Memoized callback
296
+ </span>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+
303
+ {/* API Documentation */}
304
+ <div className="!space-y-8">
305
+ <h2 className="text-center !text-3xl font-bold !text-white">
306
+ API Reference
307
+ </h2>
308
+
309
+ <div className="overflow-hidden rounded-lg border border-white/10 bg-white/5">
310
+ <div className="bg-white/5 !p-4">
311
+ <h3 className="!text-xl font-semibold !text-white">
312
+ Hook Signature
313
+ </h3>
314
+ </div>
315
+ <div className="!p-6">
316
+ <div className="rounded-lg bg-black/40 !p-4">
317
+ <pre className="!text-sm !text-green-300">
318
+ {`function useChangeState(): {
319
+ open: boolean
320
+ onOpenChange: (value?: boolean) => void
321
+ }`}
322
+ </pre>
323
+ </div>
324
+ </div>
325
+ </div>
326
+
327
+ <div className="overflow-hidden rounded-lg border border-white/10 bg-white/5">
328
+ <div className="bg-white/5 !p-4">
329
+ <h3 className="!text-xl font-semibold !text-white">
330
+ Return Value & Methods
331
+ </h3>
332
+ </div>
333
+ <table className="!my-0 w-full">
334
+ <thead className="bg-white/5">
335
+ <tr className="border-b border-white/10">
336
+ <th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
337
+ Property
338
+ </th>
339
+ <th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
340
+ Type
341
+ </th>
342
+ <th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
343
+ Description
344
+ </th>
345
+ </tr>
346
+ </thead>
347
+ <tbody>
348
+ <tr className="border-b border-white/5">
349
+ <td className="!px-6 !py-4 font-mono !text-sm !text-purple-300">
350
+ open
351
+ </td>
352
+ <td className="!px-6 !py-4 !text-sm !text-white/70">
353
+ boolean
354
+ </td>
355
+ <td className="!px-6 !py-4 !text-sm !text-white/70">
356
+ Current state value (initially false)
357
+ </td>
358
+ </tr>
359
+ <tr className="!bg-black/10">
360
+ <td className="!px-6 !py-4 font-mono !text-sm !text-purple-300">
361
+ onOpenChange
362
+ </td>
363
+ <td className="!px-6 !py-4 !text-sm !text-white/70">
364
+ (value?: boolean) ={">"} void
365
+ </td>
366
+ <td className="!px-6 !py-4 !text-sm !text-white/70">
367
+ Function to update state. Toggles if no value
368
+ provided, sets specific value if provided
369
+ </td>
370
+ </tr>
371
+ </tbody>
372
+ </table>
373
+ </div>
374
+ </div>
375
+
376
+ {/* Use Cases */}
377
+ <div className="!space-y-8">
378
+ <h2 className="text-center !text-3xl font-bold !text-white">
379
+ Common Use Cases
380
+ </h2>
381
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
382
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
383
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-blue-500/20">
384
+ <span className="!text-2xl">🪟</span>
385
+ </div>
386
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
387
+ Modals & Dialogs
388
+ </h3>
389
+ <p className="!text-sm !text-white/70">
390
+ Control modal visibility with precise open/close
391
+ functionality and smooth toggle behavior.
392
+ </p>
393
+ </div>
394
+
395
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
396
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-green-500/20">
397
+ <span className="!text-2xl">📋</span>
398
+ </div>
399
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
400
+ Dropdowns
401
+ </h3>
402
+ <p className="!text-sm !text-white/70">
403
+ Manage dropdown menu states with intelligent toggle and
404
+ explicit control methods.
405
+ </p>
406
+ </div>
407
+
408
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
409
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-purple-500/20">
410
+ <span className="!text-2xl">📖</span>
411
+ </div>
412
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
413
+ Accordions
414
+ </h3>
415
+ <p className="!text-sm !text-white/70">
416
+ Handle expand/collapse states for accordion components and
417
+ collapsible content sections.
418
+ </p>
419
+ </div>
420
+
421
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
422
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-orange-500/20">
423
+ <span className="!text-2xl">👁️</span>
424
+ </div>
425
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
426
+ Show/Hide Content
427
+ </h3>
428
+ <p className="!text-sm !text-white/70">
429
+ Toggle visibility of any content with flexible control
430
+ over show and hide operations.
431
+ </p>
432
+ </div>
433
+
434
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
435
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-cyan-500/20">
436
+ <span className="!text-2xl">🎛️</span>
437
+ </div>
438
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
439
+ UI Controls
440
+ </h3>
441
+ <p className="!text-sm !text-white/70">
442
+ Power switches, toggles, and other boolean-based UI
443
+ components with optimized state management.
444
+ </p>
445
+ </div>
446
+
447
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
448
+ <div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-pink-500/20">
449
+ <span className="!text-2xl">🔧</span>
450
+ </div>
451
+ <h3 className="!mb-2 !text-lg font-semibold !text-white">
452
+ Feature Flags
453
+ </h3>
454
+ <p className="!text-sm !text-white/70">
455
+ Implement feature toggles and conditional rendering with
456
+ clean state management patterns.
457
+ </p>
458
+ </div>
459
+ </div>
460
+ </div>
461
+
462
+ {/* Usage Patterns */}
463
+ <div className="!space-y-8">
464
+ <h2 className="text-center !text-3xl font-bold !text-white">
465
+ Usage Patterns
466
+ </h2>
467
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
468
+ <div className="!space-y-4">
469
+ <h3 className="!text-xl font-semibold !text-purple-300">
470
+ Modal with Backdrop Close
471
+ </h3>
472
+ <div className="rounded-lg bg-black/40 !p-4">
473
+ <pre className="overflow-x-auto !text-sm !text-green-300">
474
+ {`function ConfirmDialog({ message, onConfirm }) {
475
+ const { open, onOpenChange } = useChangeState()
476
+
477
+ const handleBackdropClick = (e) => {
478
+ if (e.target === e.currentTarget) {
479
+ onOpenChange(false)
480
+ }
481
+ }
482
+
483
+ return (
484
+ <>
485
+ <button onClick={() => onOpenChange(true)}>
486
+ Delete Item
487
+ </button>
488
+
489
+ {open && (
490
+ <div
491
+ className="modal-overlay"
492
+ onClick={handleBackdropClick}
493
+ >
494
+ <div className="modal-content">
495
+ <p>{message}</p>
496
+ <button onClick={onConfirm}>Confirm</button>
497
+ <button onClick={() => onOpenChange(false)}>
498
+ Cancel
499
+ </button>
500
+ </div>
501
+ </div>
502
+ )}
503
+ </>
504
+ )
505
+ }`}
506
+ </pre>
507
+ </div>
508
+ </div>
509
+
510
+ <div className="!space-y-4">
511
+ <h3 className="!text-xl font-semibold !text-purple-300">
512
+ Collapsible Section
513
+ </h3>
514
+ <div className="rounded-lg bg-black/40 !p-4">
515
+ <pre className="overflow-x-auto !text-sm !text-green-300">
516
+ {`function CollapsibleCard({ title, children }) {
517
+ const { open, onOpenChange } = useChangeState()
518
+
519
+ return (
520
+ <div className="card">
521
+ <div
522
+ className="card-header"
523
+ onClick={() => onOpenChange()}
524
+ >
525
+ <h3>{title}</h3>
526
+ <span className={\`icon \${open ? 'rotate' : ''}\`}>
527
+
528
+ </span>
529
+ </div>
530
+
531
+ {open && (
532
+ <div className="card-content">
533
+ {children}
534
+ </div>
535
+ )}
536
+ </div>
537
+ )
538
+ }`}
539
+ </pre>
540
+ </div>
541
+ </div>
542
+
543
+ <div className="!space-y-4">
544
+ <h3 className="!text-xl font-semibold !text-purple-300">
545
+ Controlled Dropdown Menu
546
+ </h3>
547
+ <div className="rounded-lg bg-black/40 !p-4">
548
+ <pre className="overflow-x-auto !text-sm !text-green-300">
549
+ {`function DropdownMenu({ trigger, items }) {
550
+ const { open, onOpenChange } = useChangeState()
551
+ const menuRef = useRef()
552
+
553
+ useEffect(() => {
554
+ const handleClickOutside = (event) => {
555
+ if (menuRef.current &&
556
+ !menuRef.current.contains(event.target)) {
557
+ onOpenChange(false)
558
+ }
559
+ }
560
+
561
+ if (open) {
562
+ document.addEventListener('click', handleClickOutside)
563
+ return () => document.removeEventListener('click', handleClickOutside)
564
+ }
565
+ }, [open, onOpenChange])
566
+
567
+ return (
568
+ <div className="dropdown" ref={menuRef}>
569
+ <button onClick={() => onOpenChange()}>
570
+ {trigger}
571
+ </button>
572
+
573
+ {open && (
574
+ <div className="dropdown-menu">
575
+ {items.map(item => (
576
+ <div
577
+ key={item.id}
578
+ onClick={() => {
579
+ item.onClick()
580
+ onOpenChange(false)
581
+ }}
582
+ >
583
+ {item.label}
584
+ </div>
585
+ ))}
586
+ </div>
587
+ )}
588
+ </div>
589
+ )
590
+ }`}
591
+ </pre>
592
+ </div>
593
+ </div>
594
+
595
+ <div className="!space-y-4">
596
+ <h3 className="!text-xl font-semibold !text-purple-300">
597
+ Conditional Form Section
598
+ </h3>
599
+ <div className="rounded-lg bg-black/40 !p-4">
600
+ <pre className="overflow-x-auto !text-sm !text-green-300">
601
+ {`function ProfileForm() {
602
+ const { open: showAdvanced, onOpenChange } = useChangeState()
603
+ const [formData, setFormData] = useState({})
604
+
605
+ const toggleAdvancedOptions = () => {
606
+ onOpenChange()
607
+
608
+ // Reset advanced fields when hiding
609
+ if (showAdvanced) {
610
+ setFormData(prev => ({
611
+ ...prev,
612
+ advancedField1: '',
613
+ advancedField2: ''
614
+ }))
615
+ }
616
+ }
617
+
618
+ return (
619
+ <div>
620
+ <input placeholder="Name" />
621
+ <input placeholder="Email" />
622
+
623
+ <button
624
+ type="button"
625
+ onClick={toggleAdvancedOptions}
626
+ >
627
+ {showAdvanced ? 'Hide' : 'Show'} Advanced Options
628
+ </button>
629
+
630
+ {showAdvanced && (
631
+ <div className="advanced-section">
632
+ <input placeholder="Advanced Field 1" />
633
+ <input placeholder="Advanced Field 2" />
634
+ </div>
635
+ )}
636
+ </div>
637
+ )
638
+ }`}
639
+ </pre>
640
+ </div>
641
+ </div>
642
+ </div>
643
+ </div>
644
+
645
+ {/* Implementation Details */}
646
+ <div className="!space-y-8">
647
+ <h2 className="text-center !text-3xl font-bold !text-white">
648
+ Implementation
649
+ </h2>
650
+ <div className="rounded-lg border border-white/10 bg-white/5 !p-6">
651
+ <h3 className="!mb-4 !text-xl font-semibold !text-white">
652
+ Hook Implementation
653
+ </h3>
654
+ <div className="rounded-lg bg-black/40 !p-4">
655
+ <pre className="overflow-x-auto !text-sm !text-blue-300">
656
+ {`import React from "react"
657
+
658
+ export const useChangeState = () => {
659
+ const [open, setOpen] = React.useState(false)
660
+
661
+ const onOpenChange = React.useCallback(
662
+ (_value = !open) => {
663
+ setOpen(_value)
664
+ },
665
+ [open]
666
+ )
667
+
668
+ return {
669
+ open,
670
+ onOpenChange,
671
+ }
672
+ }`}
673
+ </pre>
674
+ </div>
675
+ <div className="!mt-4 !space-y-2 !text-sm !text-white/70">
676
+ <p className="!text-white">
677
+ The hook combines{" "}
678
+ <code className="!text-purple-300">useState</code> for
679
+ boolean state management with{" "}
680
+ <code className="!text-purple-300">useCallback</code> for
681
+ optimized performance.
682
+ </p>
683
+ <p className="!text-white">
684
+ The <code className="!text-purple-300">onOpenChange</code>{" "}
685
+ function defaults to toggling the current state when no
686
+ parameter is provided, or sets the exact value when a
687
+ boolean is passed.
688
+ </p>
689
+ <p className="!text-white">
690
+ <code className="!text-purple-300">useCallback</code>{" "}
691
+ prevents unnecessary re-renders by memoizing the state
692
+ updater function.
693
+ </p>
694
+ </div>
695
+ </div>
696
+ </div>
697
+ </div>
698
+
699
+ {/* Footer */}
700
+ <div className="border-t border-white/10 bg-black/20 backdrop-blur-xl">
701
+ <div className="!mx-auto max-w-7xl !px-6 !py-8">
702
+ <div className="!space-y-4 text-center">
703
+ <p className="!text-white/60">
704
+ useChangeState provides an elegant solution for boolean
705
+ state management with intelligent defaults and flexible
706
+ control options, perfect for any show/hide UI pattern in
707
+ React applications.
708
+ </p>
709
+ <p className="!text-sm !text-white/40">
710
+ Optimized, intuitive, and versatile - the essential hook for
711
+ managing toggleable component states with precision.
712
+ </p>
713
+ </div>
714
+ </div>
715
+ </div>
716
+ </div>
717
+ </>
718
+ ),
719
+ },
720
+ },
721
+
722
+ tags: ["autodocs"],
723
+ argTypes: {
724
+ title: {
725
+ control: "text",
726
+ description: "Title for the demo component",
727
+ },
728
+ },
729
+ }
730
+
731
+ export default meta
732
+ type Story = StoryObj<typeof meta>
733
+
734
+ export const Default: Story = {
735
+ args: {
736
+ title: "useChangeState Demo",
737
+ },
738
+ }
739
+
740
+ export const Playground: Story = {
741
+ args: {
742
+ title: "Toggle State Hook",
743
+ },
744
+ }