@catalystsoftware/ui 1.0.5 → 1.0.7

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 (155) hide show
  1. package/dist/data/data.tsx +29 -29
  2. package/dist/data/tailwind.config.js +3821 -261
  3. package/dist/data.tsx +29 -29
  4. package/package.json +4 -3
  5. package/components/catalyst-ui/buttons/burger.tsx +0 -207
  6. package/components/catalyst-ui/core/data-display/timeline.tsx +0 -210
  7. package/components/catalyst-ui/core/feedback/alert.tsx +0 -491
  8. package/components/catalyst-ui/core/feedback/spinner-1.tsx +0 -65
  9. package/components/catalyst-ui/core/feedback/toast.tsx +0 -1857
  10. package/components/catalyst-ui/core/navigation/menu.tsx +0 -164
  11. package/components/catalyst-ui/forms/toggle-class.tsx +0 -176
  12. package/components/catalyst-ui/hooks/use-copy-to-clipboard.tsx +0 -419
  13. package/components/catalyst-ui/hooks/use-counter.tsx +0 -13
  14. package/components/catalyst-ui/hooks/use-event-listener.tsx +0 -23
  15. package/components/catalyst-ui/hooks/use-export-markdown.tsx +0 -47
  16. package/components/catalyst-ui/hooks/use-focus.tsx +0 -17
  17. package/components/catalyst-ui/hooks/use-interval.tsx +0 -23
  18. package/components/catalyst-ui/hooks/use-is-client.tsx +0 -16
  19. package/components/catalyst-ui/hooks/use-media-query.tsx +0 -19
  20. package/components/catalyst-ui/hooks/use-mobile.tsx +0 -19
  21. package/components/catalyst-ui/hooks/use-resize-observer.tsx +0 -81
  22. package/components/catalyst-ui/hooks/use-timeout.tsx +0 -21
  23. package/components/catalyst-ui/hooks/use-timer.tsx +0 -209
  24. package/components/catalyst-ui/hooks/use-toggle.tsx +0 -12
  25. package/components/catalyst-ui/media/image.tsx +0 -13
  26. package/components/catalyst-ui/overlays/dual-sidebar.tsx +0 -4142
  27. package/components/catalyst-ui/overlays/sidebar-original.tsx +0 -726
  28. package/components/catalyst-ui/primitives/accordion.tsx +0 -250
  29. package/components/catalyst-ui/primitives/alert-dialog.tsx +0 -126
  30. package/components/catalyst-ui/primitives/aspect-ratio.tsx +0 -9
  31. package/components/catalyst-ui/primitives/avatar.tsx +0 -296
  32. package/components/catalyst-ui/primitives/badge.tsx +0 -57
  33. package/components/catalyst-ui/primitives/breadcrumb.tsx +0 -101
  34. package/components/catalyst-ui/primitives/button.tsx +0 -265
  35. package/components/catalyst-ui/primitives/calendar-v4.tsx +0 -208
  36. package/components/catalyst-ui/primitives/calendar.tsx +0 -295
  37. package/components/catalyst-ui/primitives/card.tsx +0 -618
  38. package/components/catalyst-ui/primitives/carousel.tsx +0 -238
  39. package/components/catalyst-ui/primitives/chart.tsx +0 -347
  40. package/components/catalyst-ui/primitives/checkbox.tsx +0 -225
  41. package/components/catalyst-ui/primitives/collapsible.tsx +0 -212
  42. package/components/catalyst-ui/primitives/command.tsx +0 -393
  43. package/components/catalyst-ui/primitives/context-menu.tsx +0 -236
  44. package/components/catalyst-ui/primitives/dialog.tsx +0 -471
  45. package/components/catalyst-ui/primitives/drawer.tsx +0 -761
  46. package/components/catalyst-ui/primitives/dropdown-menu.tsx +0 -290
  47. package/components/catalyst-ui/primitives/empty.tsx +0 -104
  48. package/components/catalyst-ui/primitives/field.tsx +0 -244
  49. package/components/catalyst-ui/primitives/hover-card.tsx +0 -124
  50. package/components/catalyst-ui/primitives/input-otp.tsx +0 -76
  51. package/components/catalyst-ui/primitives/input.tsx +0 -64
  52. package/components/catalyst-ui/primitives/item.tsx +0 -196
  53. package/components/catalyst-ui/primitives/kbd.tsx +0 -75
  54. package/components/catalyst-ui/primitives/label.tsx +0 -24
  55. package/components/catalyst-ui/primitives/navigation-menu.tsx +0 -150
  56. package/components/catalyst-ui/primitives/pagination.tsx +0 -198
  57. package/components/catalyst-ui/primitives/popover.tsx +0 -232
  58. package/components/catalyst-ui/primitives/progress.tsx +0 -34
  59. package/components/catalyst-ui/primitives/radio-group.tsx +0 -43
  60. package/components/catalyst-ui/primitives/resizable.tsx +0 -56
  61. package/components/catalyst-ui/primitives/select.tsx +0 -155
  62. package/components/catalyst-ui/primitives/separator.tsx +0 -74
  63. package/components/catalyst-ui/primitives/sheet.tsx +0 -126
  64. package/components/catalyst-ui/primitives/skeleton.tsx +0 -15
  65. package/components/catalyst-ui/primitives/slider.tsx +0 -27
  66. package/components/catalyst-ui/primitives/switch.tsx +0 -187
  67. package/components/catalyst-ui/primitives/tabs.tsx +0 -335
  68. package/components/catalyst-ui/primitives/textarea.tsx +0 -24
  69. package/components/catalyst-ui/primitives/toggle-group.tsx +0 -55
  70. package/components/catalyst-ui/primitives/toggle.tsx +0 -42
  71. package/components/catalyst-ui/primitives/tooltip.tsx +0 -116
  72. package/components/catalyst-ui/utils/basic-auth.tsx +0 -40
  73. package/components/catalyst-ui/utils/context-storage.tsx +0 -19
  74. package/components/catalyst-ui/utils/cors-middleware.tsx +0 -71
  75. package/components/catalyst-ui/utils/deferred-content.tsx +0 -595
  76. package/components/catalyst-ui/utils/honeypot-middleware.tsx +0 -38
  77. package/components/catalyst-ui/utils/incId.tsx +0 -75
  78. package/components/catalyst-ui/utils/jwk-auth.tsx +0 -36
  79. package/components/catalyst-ui/utils/request-id.tsx +0 -14
  80. package/components/catalyst-ui/utils/secure-headers.tsx +0 -37
  81. package/components/catalyst-ui/utils/server-timing.tsx +0 -23
  82. package/components/catalyst-ui/utils/utils.ts +0 -43
  83. package/components/catalyst-ui/utils/with-cookie.tsx +0 -43
  84. package/components/catalyst-ui/x/accordian-x.tsx +0 -428
  85. package/components/catalyst-ui/x/alert-x.tsx +0 -413
  86. package/components/catalyst-ui/x/animated-text-x.tsx +0 -2242
  87. package/components/catalyst-ui/x/avatar-x.tsx +0 -515
  88. package/components/catalyst-ui/x/badge-x.tsx +0 -670
  89. package/components/catalyst-ui/x/button-X.tsx +0 -2857
  90. package/components/catalyst-ui/x/button-group-x.tsx +0 -847
  91. package/components/catalyst-ui/x/calendar-x.tsx +0 -1910
  92. package/components/catalyst-ui/x/card-x.tsx +0 -2597
  93. package/components/catalyst-ui/x/checkbox-x.tsx +0 -656
  94. package/components/catalyst-ui/x/collapsible-x.tsx +0 -1360
  95. package/components/catalyst-ui/x/combobox-x.tsx +0 -911
  96. package/components/catalyst-ui/x/data-table-x.tsx +0 -1753
  97. package/components/catalyst-ui/x/date-picker-x.tsx +0 -648
  98. package/components/catalyst-ui/x/dialog-x.tsx +0 -659
  99. package/components/catalyst-ui/x/dropdown-menu-x.tsx +0 -612
  100. package/components/catalyst-ui/x/hover-card-x.tsx +0 -375
  101. package/components/catalyst-ui/x/icon-x.tsx +0 -840
  102. package/components/catalyst-ui/x/input-mask-x.tsx +0 -981
  103. package/components/catalyst-ui/x/input-otp-x.tsx +0 -659
  104. package/components/catalyst-ui/x/loader-x.tsx +0 -1757
  105. package/components/catalyst-ui/x/pagination-x.tsx +0 -622
  106. package/components/catalyst-ui/x/popover-x.tsx +0 -744
  107. package/components/catalyst-ui/x/radio-group-x.tsx +0 -499
  108. package/components/catalyst-ui/x/select-x.tsx +0 -1127
  109. package/components/catalyst-ui/x/sheet-x.tsx +0 -668
  110. package/components/catalyst-ui/x/switch-x.tsx +0 -681
  111. package/components/catalyst-ui/x/table-x.tsx +0 -574
  112. package/components/catalyst-ui/x/tabs-x.tsx +0 -839
  113. package/components/catalyst-ui/x/textarea-x.tsx +0 -1263
  114. package/components/catalyst-ui/x/tooltip-x.tsx +0 -396
  115. package/components/catalyst-ui/x/tracker-x.tsx +0 -560
  116. package/data/bg-data.tsx +0 -901
  117. package/data/buttons-data.tsx +0 -2327
  118. package/data/charts-data.tsx +0 -102
  119. package/data/chat-data.tsx +0 -83
  120. package/data/code-data.tsx +0 -1040
  121. package/data/comboboxes-data.tsx +0 -1843
  122. package/data/command-data.tsx +0 -1381
  123. package/data/core-data.tsx +0 -15953
  124. package/data/crm-data.tsx +0 -47
  125. package/data/data.tsx +0 -159
  126. package/data/date-and-time-data.tsx +0 -554
  127. package/data/dependencies.tsx +0 -7
  128. package/data/ecommerce-data.tsx +0 -1387
  129. package/data/forms-data.tsx +0 -7890
  130. package/data/hooks-data.tsx +0 -5487
  131. package/data/index.ts +0 -34
  132. package/data/inputs-data.tsx +0 -557
  133. package/data/interactive-data.tsx +0 -5394
  134. package/data/lofi-data.tsx +0 -18295
  135. package/data/marketing-data.tsx +0 -2546
  136. package/data/media-data.tsx +0 -1510
  137. package/data/motion-data.tsx +0 -5801
  138. package/data/overlay-data.tsx +0 -4136
  139. package/data/pdf-data.tsx +0 -124
  140. package/data/pos-data.tsx +0 -213
  141. package/data/postcss.config.js +0 -6
  142. package/data/primitive-data.tsx +0 -5170
  143. package/data/prompt-data.tsx +0 -1226
  144. package/data/requiredLibs.ts +0 -4
  145. package/data/sandbox-data.tsx +0 -1
  146. package/data/sidebars-data.tsx +0 -5421
  147. package/data/stacks-data.tsx +0 -32
  148. package/data/table-data.tsx +0 -706
  149. package/data/tailwind.config.js +0 -270
  150. package/data/tailwind.config.ngin.js +0 -3830
  151. package/data/tailwind.css +0 -431
  152. package/data/tools-data.tsx +0 -6910
  153. package/data/typography-data.tsx +0 -2050
  154. package/data/utils-data.tsx +0 -6500
  155. package/data/x-data.tsx +0 -1171
@@ -1,2857 +0,0 @@
1
- import React, { useEffect, useState, useCallback, Children, cloneElement, ComponentPropsWithoutRef, createContext, CSSProperties, ReactElement, ReactNode, useContext, useRef } from 'react'
2
- import { AlertCircle, ArchiveX, ArrowRight, Ban, CheckIcon, ChevronDown, Command, CopyIcon, Download, LucideProps, Magnet, MousePointerClick, StarIcon, Trash2, XCircle } from 'lucide-react'
3
- import { Instagram, Linkedin,Twitter,} from '@catalystsoftware/icons'
4
- import { cva, type VariantProps } from 'class-variance-authority'
5
- import { attractButtonVariants, BurgerButton, buttonVariants, cn, commandButtonVariants, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, holdButtonVariants, LoaderProps, particleButtonVariants, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Transition, useCopyToClipboard, useExport } from '~/components/catalyst-ui'
6
- import { Slot } from "@radix-ui/react-slot"
7
- import { Link, useNavigation, useResolvedPath } from '@remix-run/react';
8
- import { SpinnerV4 } from '~/components/catalyst-ui'
9
- import { useControllableState } from '@radix-ui/react-use-controllable-state'
10
- import { motion, AnimatePresence, Variant, HTMLMotionProps } from 'framer-motion'
11
-
12
- // @devsearch app/components/catalyst-ui/data/primitive-data.tsx:buttonX
13
-
14
- /**
15
- ============================ BUTTONX =====================================================================
16
- * **variant**: default | destructive | outline | secondary | ghost | link | elevated | filled | tonal | outlined | text | styled | reverted | dashed | gradient | soft | glass | success | warning | info | shine | pulse | violet | blue | green
17
- * **size**: default | sm | lg | icon
18
- * **align**: left | middle | right
19
- * **effect**: expandIcon | ringHover | shine | shineHover | gooeyRight | gooeyLeft | underline | hoverUnderline | gradientSlideShow
20
- * **prefetch**: none | intent | render | viewport
21
- * **nav**: null | navlink | anchor
22
- * **to**: string (navigation path)
23
- * **iconPlacement**: left | right
24
- * **icon**: React.ReactNode
25
- * **newTab**: boolean
26
- * **onClick**: function
27
- * **className**: string
28
- * **...props**: standard button props
29
- *
30
- ============================ Attract =====================================================================
31
- * **particleCount**: number (default: 12)
32
- * **attractRadius**: number (default: 50)
33
- * **attractVariant**: default | violet | blue | green
34
- * **icon**: React.ReactNode
35
- * **activeText**: string (default: "Attracting")
36
- * **inactiveText**: string (default: "Hover me")
37
- * **className**: string
38
- * **...props**: standard button props
39
- *
40
- ============================ DEFAULT (Button) ============================================================
41
- * **className**: string
42
- * **variant**: default | destructive | outline | secondary | ghost | link | elevated | filled | tonal | outlined | text | styled | reverted | dashed | gradient | soft | glass | success | warning | info | shine | pulse | violet | blue | green
43
- * **size**: default | sm | lg | icon
44
- * **align**: left | middle | right
45
- * **effect**: expandIcon | ringHover | shine | shineHover | gooeyRight | gooeyLeft | underline | hoverUnderline | gradientSlideShow
46
- * **asChild**: boolean (default: false)
47
- * **type**: string (default: null)
48
- * **children**: React.ReactNode
49
- * **...props**: standard button props
50
- *
51
- ============================ Burger ======================================================================
52
- * **className**: string
53
- * **...props**: standard button props
54
- * *(Uses BurgerButton component from catalyst-ui)*
55
- *
56
- ============================ Command =====================================================================
57
- * **className**: string
58
- * **variant**: default | primary | secondary
59
- * **size**: sm | default | lg
60
- * **children**: React.ReactNode (default: "CMD + K")
61
- * **...props**: standard button props
62
- *
63
- ============================ Copy ========================================================================
64
- * **content**: string (text to copy)
65
- * **className**: string
66
- * **size**: default | sm | lg | icon
67
- * **variant**: default | destructive | outline | secondary | ghost | link
68
- * **delay**: number (default: 3000) - time in ms before icon resets
69
- * **onClick**: function
70
- * **onCopy**: function(content: string) - callback when copy succeeds
71
- * **isCopied**: boolean - controlled copied state
72
- * **onCopyChange**: function(isCopied: boolean) - controlled copied state handler
73
- * **...props**: standard button props
74
- *
75
- ============================ CopyText ====================================================================
76
- * **code**: string (text to copy)
77
- * **className**: string
78
- * **ttText**: string (default: 'Copy') - tooltip text
79
- * **icon**: React.Component (default: CopyIcon)
80
- * **size**: default | sm | lg | icon (default: 'icon')
81
- * **variant**: default | destructive | outline | secondary | ghost | link (default: 'ghost')
82
- * **...props**: standard button props
83
- *
84
- ============================ Export ======================================================================
85
- * **code**: string (default: null) - content to export
86
- * **filename**: string (default: 'MyComponent.tsx') - filename for download
87
- * **className**: string
88
- *
89
- ============================ Flip ========================================================================
90
- * **frontText**: string (required) - text displayed on front
91
- * **backText**: string (required) - text displayed on back
92
- * **transition**: object (default: { type: 'spring', stiffness: 280, damping: 20 })
93
- * **className**: string
94
- * **frontClassName**: string - styling for front face
95
- * **backClassName**: string - styling for back face
96
- * **from**: 'top' | 'bottom' | 'left' | 'right' (default: 'top') - flip direction
97
- * **...props**: standard motion button props
98
- *
99
- ============================ Form ========================================================================
100
- * **primary**: true | false | "dashed" (default: true)
101
- * **variant**: default | destructive | outline | secondary | ghost | link (default: "default")
102
- * **align**: left | middle | right (default: "default")
103
- * **size**: default | sm | lg | icon (default: 'sm')
104
- * **loadingText**: string (default: "Submitting...")
105
- * **className**: string
106
- * **children**: React.ReactNode
107
- * **...props**: standard button props
108
- *
109
- ============================ Float =======================================================================
110
- * **icon**: React.ReactNode
111
- * **description**: string - text description below icon
112
- * **tooltip**: string - tooltip text on hover
113
- * **type**: "default" | "primary" (default: "default")
114
- * **shape**: "circle" | "square" (default: "circle")
115
- * **size**: "small" | "default" | "large" (default: "default")
116
- * **className**: string
117
- * **onClick**: function
118
- * **children**: React.ReactNode
119
- *
120
- ============================ Hold ========================================================================
121
- * **holdDuration**: number (default: 3000) - time in ms to hold
122
- * **onComplete**: function - callback when hold completes
123
- * **icon**: React.ReactNode
124
- * **holdText**: string (default: "Hold me")
125
- * **releaseText**: string (default: "Release")
126
- * **holdVariant**: "red" | "green" | "blue" | "orange" | "grey" (default: "red")
127
- * **className**: string
128
- * **disabled**: boolean
129
- * **...props**: standard button props
130
- *
131
- ============================ Input =======================================================================
132
- * **placeholder**: string (default: "Enter text") - for InputButtonInput
133
- * **submitText**: string (default: "Submit") - for InputButtonSubmit
134
- * **className**: string
135
- * **transition**: object (default: { type: 'spring', stiffness: 300, damping: 20 })
136
- * **showInput**: boolean - controlled input visibility
137
- * **setShowInput**: function - controlled input visibility handler
138
- * **id**: string - unique identifier
139
- * **children**: React.ReactNode (or default structure)
140
- * **...props**: standard div props
141
- *
142
- ============================ Liquid ======================================================================
143
- * **className**: string
144
- * **variant**: default | destructive | outline | secondary | ghost | link
145
- * **size**: default | sm | lg | icon
146
- * **children**: React.ReactNode
147
- * **...props**: standard motion button props
148
- *
149
- ============================ Particle ====================================================================
150
- * **children**: React.ReactNode
151
- * **onClick**: function
152
- * **onSuccess**: function - callback on click
153
- * **successDuration**: number (default: 1000) - particle animation duration
154
- * **particleCount**: number | "low" | "high" (default: 6) - number of particles (low=4, high=10)
155
- * **showIcon**: boolean (default: true)
156
- * **icon**: React.ReactNode
157
- * **variant**: default | destructive | outline | secondary | ghost | link
158
- * **size**: default | sm | lg | icon
159
- * **align**: left | middle | right
160
- * **className**: string
161
- * **disabled**: boolean
162
- * **...props**: standard button props
163
- *
164
- ============================ Rate ========================================================================
165
- * **defaultValue**: number (default: 0) - initial rating value
166
- * **value**: number - controlled rating value
167
- * **onChange**: function(event, value) - callback on rating change
168
- * **onValueChange**: function(value) - callback on rating value change
169
- * **readOnly**: boolean (default: false)
170
- * **className**: string
171
- * **starSize**: number (default: 20) - size of star icons
172
- * **icon**: React.ReactNode (default: <StarIcon />)
173
- * **children**: React.ReactNode (or auto-generated 5 stars)
174
- *
175
- ============================ Rate1 =======================================================================
176
- * **defaultValue**: number (default: 3) - initial rating value
177
- * **value**: number - controlled rating value
178
- * **onChange**: function(event, value) - callback on rating change
179
- * **onValueChange**: function(value) - callback on rating value change
180
- * **readOnly**: boolean (default: false)
181
- * **className**: string
182
- * **showText**: boolean (default: true) - show rating text below
183
- * **text**: string - custom text to display
184
- * **children**: React.ReactNode (or auto-generated 5 stars)
185
- * **...props**: standard div props
186
- *
187
- ============================ Ripple ======================================================================
188
- * **ref**: React.Ref<HTMLButtonElement>
189
- * **children**: React.ReactNode
190
- * **onClick**: function
191
- * **className**: string
192
- * **rippleClassName**: string - styling for ripple effect
193
- * **variant**: default | destructive | outline | secondary | ghost | link
194
- * **size**: default | sm | lg | icon
195
- * **scale**: number (default: 10) - ripple scale multiplier
196
- * **transition**: object (default: { duration: 0.6, ease: 'easeOut' })
197
- * **...props**: standard motion button props
198
- *
199
- ============================ Social ======================================================================
200
- * **shareButtons**: array of { icon: Component, label: string, onClick?: function } (default: Twitter, Instagram, LinkedIn, Link)
201
- * **label**: string (default: "Hover me")
202
- * **icon**: React.ReactNode
203
- * **variant**: "light" | "dark" | "outline"
204
- * **className**: string
205
- * **...props**: standard button props
206
- *
207
- ============================ Split =======================================================================
208
- * **label**: string | null (default: null) - dropdown label or ChevronDown icon
209
- * **icon**: React.ReactNode
210
- * **items**: array of { label: string, icon?: ReactNode, command?: function, separator?: boolean } (required)
211
- * **onClick**: function - main button click handler
212
- * **className**: string
213
- * **containerClassName**: string - wrapper styling
214
- * **variant**: "default" | "destructive" | "outline" | "secondary" | "ghost" (default: "default")
215
- * **align**: left | middle | right (default: "default")
216
- * **size**: default | sm | lg | icon (default: "sm")
217
- * **children**: React.ReactNode
218
- * **...props**: standard button props
219
- *
220
- ============================ Switch ======================================================================
221
- * **checked**: boolean (default: false) - switch state
222
- * **onCheckedChange**: function(checked: boolean) - callback on state change
223
- * **className**: string
224
- * **variant**: 'default' | 'primary' | 'secondary' | 'destructive' (default: 'default')
225
- * **size**: 'sm' | 'md' | 'lg' (default: 'md')
226
- * **disabled**: boolean (default: false)
227
- * **labels**: { checked: string, unchecked: string } - optional labels for states
228
- *
229
- ============================ Toggle ======================================================================
230
- * **checked**: boolean (default: false) - toggle state
231
- * **onChange**: function(checked: boolean) - callback on state change
232
- * **disabled**: boolean (default: false)
233
- * **className**: string
234
- * **variant**: "default" | "outline" | "ghost" (default: "default")
235
- * **size**: "sm" | "md" | "lg" | "icon" (default: "md")
236
- * **children**: React.ReactNode (default: "Toggle")
237
- * **...props**: standard button props
238
- *
239
- ============================ Slide =======================================================================
240
- * **className**: string
241
- * **variant**: string (slideTextButtonVariants)
242
- * **size**: "sm" | "default" | "lg" (default: "default")
243
- * **text**: string (default: "Browse Components") - default text
244
- * **hoverText**: string - text on hover (defaults to text if not provided)
245
- * **to**: string (default: "/") - navigation path
246
- * **prefetch**: "none" | "intent" | "render" | "viewport" (default: "intent")
247
- * **...props**: Link component props
248
- *
249
- ============================ TextReveal ==================================================================
250
- * **text**: string (default: "shadcn.io") - text to reveal
251
- * **revealColor**: string (default: "#37FF8B") - revealed text color
252
- * **strokeColor**: string (default: "rgba(100, 100, 100, 0.7)") - text stroke color
253
- * **className**: string
254
- * **style**: React.CSSProperties
255
- * **ref**: React.Ref<HTMLButtonElement>
256
- * **...props**: standard button props
257
- *
258
- ============================ Shimmer =====================================================================
259
- * **shimmerColor**: string (default: "#ffffff") - shimmer effect color
260
- * **shimmerSize**: string (default: "0.05em") - shimmer width
261
- * **shimmerDuration**: string (default: "3s") - animation duration
262
- * **borderRadius**: string (default: "100px") - button border radius
263
- * **background**: string (default: "rgba(0, 0, 0, 1)") - button background
264
- * **className**: string
265
- * **children**: React.ReactNode
266
- * **ref**: React.Ref<HTMLButtonElement>
267
- * **...props**: standard button props
268
- *
269
- ============================ TooltipButton ===============================================================
270
- * **icon**: React.ReactNode (required) - icon to display
271
- * **content**: string (required) - tooltip content
272
- * **onClick**: function (default: null) - click handler
273
- * **size**: "default" | "sm" | "lg" | "icon" (default: 'icon')
274
- * **variant**: default | destructive | outline | secondary | ghost | link (default: 'ghost')
275
- * **className**: string
276
- *
277
- */
278
-
279
-
280
- function ButtonX({ button = 'default', children, ...props }: any) {
281
- switch (button) {
282
- case 'Attract':
283
- return <AttractButton {...props}>{children}</AttractButton>;
284
- case 'Burger':
285
- return <BurgerButton {...props}>{children}</BurgerButton>;
286
- case 'Command':
287
- return <CommandButton {...props}>{children}</CommandButton>;
288
- case 'Copy':
289
- return <CopyButton {...props}>{children}</CopyButton>;
290
- case 'CopyText':
291
- return <CopyText {...props}>{children}</CopyText>;
292
- case 'Export':
293
- return <ExportFile {...props} />;
294
- case 'Flip':
295
- return <FlipButton {...props} />;
296
- case 'Form':
297
- return <FormButton {...props}>{children}</FormButton>;
298
- case 'Float':
299
- return <FloatButton {...props}>{children}</FloatButton>;
300
- case 'Hold':
301
- return <HoldButton {...props}>{children}</HoldButton>;
302
- case 'Input':
303
- return (
304
- <InputButtonProvider {...props}>
305
- <InputButton>
306
- <InputButtonAction>{children || "Click me"}</InputButtonAction>
307
- <InputButtonInput placeholder={props.placeholder || "Enter text"} />
308
- <InputButtonSubmit>{props.submitText || "Submit"}</InputButtonSubmit>
309
- </InputButton>
310
- </InputButtonProvider>
311
- );
312
- case 'Liquid':
313
- return <LiquidButton {...props}>{children}</LiquidButton>;
314
- case 'Particle':
315
- return <ParticleButton {...props}>{children}</ParticleButton>;
316
- case 'Rate':
317
- return (
318
- <Rating
319
- defaultValue={props.defaultValue || 0}
320
- value={props.value}
321
- onChange={props.onChange}
322
- onValueChange={props.onValueChange}
323
- readOnly={props.readOnly || false}
324
- className={props.className}
325
- >
326
- {children || Array.from({ length: 5 }).map((_, index) => (
327
- <RatingButton
328
- key={index}
329
- index={index}
330
- size={props.starSize || 20}
331
- icon={props.icon || <StarIcon />}
332
- />
333
- ))}
334
- </Rating>
335
- );
336
- case 'Rate1':
337
- return (
338
- <div>
339
- <Rate1 defaultValue={props.defaultValue || 3} {...props}>
340
- {children || Array.from({ length: 5 }).map((_, index) => (
341
- <RatingButton1 key={index} />
342
- ))}
343
- </Rate1>
344
- {props.showText !== false && (
345
- <p className="text-sm text-muted-foreground mt-2">
346
- {props.text || `Rating: ${props.defaultValue || 3} stars`}
347
- </p>
348
- )}
349
- </div>
350
- );
351
- case 'Ripple':
352
- return <RippleButton {...props}>{children}</RippleButton>;
353
- case 'Social':
354
- return <SocialButton {...props}>{children}</SocialButton>;
355
- case 'Split':
356
- return <SplitButton {...props}>{children}</SplitButton>;
357
- case 'Switch':
358
- return <SwitchButton {...props}>{children}</SwitchButton>;
359
- case 'Toggle':
360
- return <ToggleButton {...props}>{children || "Toggle"}</ToggleButton>;
361
- case 'Slide':
362
- return <SlideButton {...props}>{children}</SlideButton>;
363
- case 'TextReveal':
364
- return <TextRevealButton {...props}>{children}</TextRevealButton>;
365
- case 'Shimmer':
366
- return <ShimmerButton {...props}>{children}</ShimmerButton>;
367
- case 'TooltipButton':
368
- return <TooltipButton {...props} />;
369
- case 'default':
370
- default:
371
- return <Button {...props}>{children}</Button>;
372
- }
373
- }
374
-
375
- ButtonX.displayName = "ButtonX"
376
-
377
- export { ButtonX }
378
-
379
- function Button({
380
- className,
381
- variant,
382
- size,
383
- align,
384
- effect,
385
- icon: Icon,
386
- iconPlacement = "left",
387
- prefetch = "intent",
388
- to = "/",
389
- nav = null,
390
- newTab = true,
391
- asChild = false,
392
- type = null,
393
- children,
394
- ...props
395
- }: React.ComponentProps<"button"> &
396
- VariantProps<typeof buttonVariants> & {
397
- asChild?: boolean
398
- }) {
399
- const Comp = asChild ? Slot : "button"
400
- const n = useNavigation()
401
- const resolvedTo = useResolvedPath(to);
402
- const isSubmitting = n.state === 'submitting';
403
- const isNavigating = n.state === 'loading' && n.location?.pathname === resolvedTo.pathname;
404
- const isDisabled = isSubmitting && isNavigating;
405
- let loadingText = 'Navigating...'
406
- if (nav === 'navlink') {
407
- return (
408
- <Link
409
- to={to}
410
- prefetch={prefetch}
411
- preventScrollReset
412
- disabled={isDisabled}
413
- {...props}>
414
- <Comp
415
- data-slot="button"
416
- type={type || 'button'}
417
- className={cn(buttonVariants({ variant, effect, size, align, className }))}
418
- disabled={isDisabled}
419
- >
420
- {isNavigating ? (
421
- <div className='flex items-center gap-3'>
422
- <SpinnerV4 title={loadingText} subtitle='' size='icon' />
423
- </div>
424
- ) : (
425
- children
426
- )}
427
- </Comp>
428
- </Link>
429
- )
430
- } else if (nav === 'anchor') {
431
- const anchorProps = newTab ? {
432
- target: "_blank",
433
- rel: "noopener noreferrer"
434
- } : {};
435
- return (
436
- <a
437
- href={to}
438
- preventScrollReset
439
- disabled={isDisabled}
440
- {...anchorProps}
441
- >
442
- <Comp
443
- data-slot="button"
444
- type={type || 'button'}
445
- className={cn(buttonVariants({ variant, effect, size, align, className }))}
446
- disabled={isDisabled}
447
- {...props}
448
- >
449
- {isNavigating ? (
450
- <div className='flex items-center gap-3'>
451
- <SpinnerV4 title={loadingText} subtitle='' size='icon' />
452
- </div>
453
- ) : (
454
- children
455
- )}
456
- </Comp>
457
- </a>
458
- )
459
- } else if (effect) {
460
- let loadingText = 'Submitting...'
461
- return (
462
- <Comp
463
- data-slot="button"
464
- type={type || 'submit'}
465
- className={cn(buttonVariants({ variant, effect, size, align, className }))}
466
- {...props}
467
- >
468
- {Icon &&
469
- iconPlacement === 'left' &&
470
- (effect === 'expandIcon' ? (
471
- <div className="w-0 translate-x-[0%] pr-0 opacity-0 transition-all duration-200 group-hover:w-5 group-hover:translate-x-100 group-hover:pr-2 group-hover:opacity-100">
472
- <Icon />
473
- </div>
474
- ) : (
475
- <Icon />
476
- ))}
477
-
478
- {isNavigating ? (
479
- <div className='flex items-center gap-3'>
480
- <SpinnerV4 title={loadingText} subtitle='' size='icon' />
481
- </div>
482
- ) : (
483
- children
484
- )}
485
- {Icon &&
486
- iconPlacement === 'right' &&
487
- (effect === 'expandIcon' ? (
488
- <div className="w-0 translate-x-[100%] pl-0 opacity-0 transition-all duration-200 group-hover:w-5 group-hover:translate-x-0 group-hover:pl-2 group-hover:opacity-100">
489
- <Icon />
490
- </div>
491
- ) : (
492
- <Icon />
493
- ))}
494
- </Comp>
495
- )
496
- } else {
497
- let loadingText = 'Submitting...'
498
- return (
499
- <Comp
500
- data-slot="button"
501
- type={type || 'submit'}
502
- className={cn(buttonVariants({ variant, effect, size, align, className }))}
503
- {...props}
504
- >
505
- {isNavigating ? (
506
- <div className='flex items-center gap-3'>
507
- <SpinnerV4 title={loadingText} subtitle='' size='icon' />
508
- </div>
509
- ) : (
510
- children
511
- )}
512
- </Comp>
513
- )
514
- }
515
-
516
- }
517
- function AttractButton({
518
- particleCount = 12,
519
- attractRadius = 50,
520
- attractVariant,
521
- icon,
522
- activeText = "Attracting",
523
- inactiveText = "Hover me",
524
- className,
525
- ...props
526
- }: AttractButtonProps) {
527
- const [isAttracting, setIsAttracting] = useState(false)
528
- const [particles, setParticles] = useState<Particle[]>([])
529
- const [particlePositions, setParticlePositions] = useState<{ [key: number]: { x: number; y: number } }>({})
530
-
531
- useEffect(() => {
532
- const newParticles = Array.from({ length: particleCount }, (_, i) => ({
533
- id: i,
534
- x: Math.random() * 360 - 180,
535
- y: Math.random() * 360 - 180,
536
- }))
537
- setParticles(newParticles)
538
-
539
- const initialPositions: { [key: number]: { x: number; y: number } } = {}
540
- newParticles.forEach(p => {
541
- initialPositions[p.id] = { x: p.x, y: p.y }
542
- })
543
- setParticlePositions(initialPositions)
544
- }, [particleCount])
545
-
546
- const handleInteractionStart = useCallback(() => {
547
- setIsAttracting(true)
548
- const attracted: { [key: number]: { x: number; y: number } } = {}
549
- particles.forEach(p => {
550
- attracted[p.id] = { x: 0, y: 0 }
551
- })
552
- setParticlePositions(attracted)
553
- }, [particles])
554
-
555
- const handleInteractionEnd = useCallback(() => {
556
- setIsAttracting(false)
557
- const scattered: { [key: number]: { x: number; y: number } } = {}
558
- particles.forEach(p => {
559
- scattered[p.id] = { x: p.x, y: p.y }
560
- })
561
- setParticlePositions(scattered)
562
- }, [particles])
563
-
564
- const particleColor = attractVariant === "violet"
565
- ? "bg-violet-400 dark:bg-violet-300"
566
- : attractVariant === "blue"
567
- ? "bg-blue-400 dark:bg-blue-300"
568
- : attractVariant === "green"
569
- ? "bg-green-400 dark:bg-green-300"
570
- : "bg-primary"
571
-
572
- return (
573
- <Button
574
- className={cn(attractButtonVariants({ attractVariant }), className)}
575
- onMouseEnter={handleInteractionStart}
576
- onMouseLeave={handleInteractionEnd}
577
- onTouchStart={handleInteractionStart}
578
- onTouchEnd={handleInteractionEnd}
579
- asChild={false}
580
- {...props}
581
- >
582
- <>
583
- {particles.map((particle) => (
584
- <div
585
- key={particle.id}
586
- className={cn(
587
- "absolute w-1.5 h-1.5 rounded-full transition-all duration-300",
588
- particleColor,
589
- isAttracting ? "opacity-100" : "opacity-40"
590
- )}
591
- style={{
592
- transform: `translate(${particlePositions[particle.id]?.x || particle.x}px, ${particlePositions[particle.id]?.y || particle.y}px)`,
593
- }}
594
- />
595
- ))}
596
- <span className="relative w-full flex items-center justify-center gap-2">
597
- {icon || (
598
- <Magnet
599
- className={cn(
600
- "w-4 h-4 transition-transform duration-300",
601
- isAttracting && "scale-110"
602
- )}
603
- />
604
- )}
605
- {isAttracting ? activeText : inactiveText}
606
- </span>
607
- </>
608
- </Button>
609
- )
610
- }
611
- function CommandButton({
612
- className,
613
- variant,
614
- size,
615
- children,
616
- ...props
617
- }: React.ComponentProps<"button"> &
618
- VariantProps<typeof commandButtonVariants> & {
619
- children?: React.ReactNode
620
- }) {
621
- return (
622
- <button
623
- data-slot="command-button"
624
- className={cn(commandButtonVariants({ variant, size, className }))}
625
- {...props}
626
- >
627
- <Command
628
- className={cn(
629
- "text-zinc-600 dark:text-zinc-400 transition-all duration-300 group-hover:scale-110 group-hover:rotate-[-4deg] group-active:scale-95",
630
- size === "sm" && "size-3",
631
- size === "default" && "size-4",
632
- size === "lg" && "size-5"
633
- )}
634
- />
635
- <span className="text-zinc-600 dark:text-zinc-400">
636
- {children || "CMD + K"}
637
- </span>
638
- <span
639
- className={cn(
640
- "absolute inset-0 bg-gradient-to-r from-indigo-500/0 via-indigo-500/10 to-indigo-500/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-500 ease-out"
641
- )}
642
- />
643
- </button>
644
- )
645
- }
646
- function CopyButton({
647
- content,
648
- className,
649
- size,
650
- variant,
651
- delay = 3000,
652
- onClick,
653
- onCopy,
654
- isCopied,
655
- onCopyChange,
656
- ...props
657
- }: CopyButtonProps) {
658
- const [localIsCopied, setLocalIsCopied] = React.useState(isCopied ?? false);
659
- const Icon = localIsCopied ? CheckIcon : CopyIcon;
660
-
661
- React.useEffect(() => {
662
- setLocalIsCopied(isCopied ?? false);
663
- }, [isCopied]);
664
-
665
- const handleIsCopied = React.useCallback(
666
- (isCopied: boolean) => {
667
- setLocalIsCopied(isCopied);
668
- onCopyChange?.(isCopied);
669
- },
670
- [onCopyChange],
671
- );
672
-
673
- const handleCopy = React.useCallback(
674
- (e: React.MouseEvent<HTMLButtonElement>) => {
675
- if (isCopied) return;
676
- if (content) {
677
- navigator.clipboard
678
- .writeText(content)
679
- .then(() => {
680
- handleIsCopied(true);
681
- setTimeout(() => handleIsCopied(false), delay);
682
- onCopy?.(content);
683
- })
684
- .catch((error) => {
685
- console.error('Error copying command', error);
686
- });
687
- }
688
- onClick?.(e);
689
- },
690
- [isCopied, content, delay, onClick, onCopy, handleIsCopied],
691
- );
692
-
693
- return (
694
- <motion.button
695
- data-slot="copy-button"
696
- whileHover={{ scale: 1.05 }}
697
- whileTap={{ scale: 0.95 }}
698
- className={cn(buttonVariants({ variant, size }), className)}
699
- onClick={handleCopy}
700
- {...props}
701
- >
702
- <AnimatePresence mode="wait">
703
- <motion.span
704
- key={localIsCopied ? 'check' : 'copy'}
705
- data-slot="copy-button-icon"
706
- initial={{ scale: 0 }}
707
- animate={{ scale: 1 }}
708
- exit={{ scale: 0 }}
709
- transition={{ duration: 0.15 }}
710
- >
711
- <Icon />
712
- </motion.span>
713
- </AnimatePresence>
714
- </motion.button>
715
- );
716
- }
717
- function CopyText({
718
- code,
719
- className,
720
- ttText = 'Copy',
721
- icon = CopyIcon,
722
- size = 'icon',
723
- variant = 'ghost',
724
- ...props
725
- }: CopyButtonProps) {
726
- const { isCopied, copyToClipboard } = useCopyToClipboard()
727
-
728
- const Icon = isCopied ? CheckIcon : icon;
729
-
730
- return (
731
- <Tooltip>
732
- <TooltipTrigger>
733
- <motion.button
734
- data-slot="copy-button"
735
- whileHover={{ scale: 1.05 }}
736
- whileTap={{ scale: 0.95 }}
737
- className={cn(buttonVariants({ variant, size }), className)}
738
- onClick={() => copyToClipboard(code)}
739
- {...props}
740
- >
741
- <AnimatePresence mode="wait">
742
- <motion.span
743
- key={isCopied ? 'check' : 'copy'}
744
- data-slot="copy-button-icon"
745
- initial={{ scale: 0 }}
746
- animate={{ scale: 1 }}
747
- exit={{ scale: 0 }}
748
- transition={{ duration: 0.15 }}
749
- >
750
- <Icon />
751
- </motion.span>
752
- </AnimatePresence>
753
- </motion.button>
754
- </TooltipTrigger>
755
- <TooltipContent>
756
- <p>{ttText}</p>
757
- </TooltipContent>
758
- </Tooltip>
759
-
760
- );
761
- }
762
- function ExportFile({ code = null, filename = 'MyComponent.tsx', className }) {
763
- const exportFile = useExport();
764
-
765
- const handleClick = () => {
766
- if (!code || !filename) return null;
767
- exportFile(code, filename);
768
- };
769
- return (
770
- <TooltipButton icon={<Download />} onClick={handleClick} content="Export File" className={cn("h-8 w-8 cursor-pointer hover:bg-background/80", className)} />
771
- );
772
- }
773
- const DEFAULT_SPAN_CLASS_NAME = 'absolute inset-0 flex items-center justify-center rounded-lg';
774
- function FlipButton({
775
- frontText,
776
- backText,
777
- transition = { type: 'spring', stiffness: 280, damping: 20 },
778
- className,
779
- frontClassName,
780
- backClassName,
781
- from = 'top',
782
- ...props
783
- }: FlipButtonProps) {
784
- const isVertical = from === 'top' || from === 'bottom';
785
- const rotateAxis = isVertical ? 'rotateX' : 'rotateY';
786
-
787
- const frontOffset = from === 'top' || from === 'left' ? '50%' : '-50%';
788
- const backOffset = from === 'top' || from === 'left' ? '-50%' : '50%';
789
-
790
- const buildVariant = (
791
- opacity: number,
792
- rotation: number,
793
- offset: string | null = null,
794
- ): Variant => ({
795
- opacity,
796
- [rotateAxis]: rotation,
797
- ...(isVertical && offset !== null ? { y: offset } : {}),
798
- ...(!isVertical && offset !== null ? { x: offset } : {}),
799
- });
800
-
801
- const frontVariants = {
802
- initial: buildVariant(1, 0, '0%'),
803
- hover: buildVariant(0, 90, frontOffset),
804
- };
805
-
806
- const backVariants = {
807
- initial: buildVariant(0, 90, backOffset),
808
- hover: buildVariant(1, 0, '0%'),
809
- };
810
-
811
- return (
812
- <motion.button
813
- data-slot="flip-button"
814
- initial="initial"
815
- whileHover="hover"
816
- whileTap={{ scale: 0.95 }}
817
- className={cn(
818
- 'relative inline-block h-10 px-4 py-2 text-sm font-medium cursor-pointer perspective-[1000px] focus:outline-none',
819
- className,
820
- )}
821
- {...props}
822
- >
823
- <motion.span
824
- data-slot="flip-button-front"
825
- variants={frontVariants}
826
- transition={transition}
827
- className={cn(
828
- DEFAULT_SPAN_CLASS_NAME,
829
- 'bg-muted text-black dark:text-white',
830
- frontClassName,
831
- )}
832
- >
833
- {frontText}
834
- </motion.span>
835
- <motion.span
836
- data-slot="flip-button-back"
837
- variants={backVariants}
838
- transition={transition}
839
- className={cn(
840
- DEFAULT_SPAN_CLASS_NAME,
841
- 'bg-primary text-primary-foreground',
842
- backClassName,
843
- )}
844
- >
845
- {backText}
846
- </motion.span>
847
- <span className="invisible">{frontText}</span>
848
- </motion.button>
849
- );
850
- }
851
- function FormButton({
852
- primary = true, // true | false | dashed
853
- variant = "default",
854
- align = "default",
855
- size = 'sm',
856
- loadingText = "Submitting...",
857
- className,
858
- children,
859
- ...props
860
- }: ButtonNavLinkProps) {
861
- const nav = useNavigation()
862
-
863
- const isSubmitting = nav.state === 'submitting';
864
-
865
- const baseClasses = cn(
866
- "transition duration-300 border-2 border-border w-[95%] text-center mx-auto text-foreground px-6 py-3 rounded-none",
867
- buttonVariants({ variant, size, className, align }),
868
- className
869
- );
870
-
871
- const primaryClasses =
872
- primary === true
873
- ? "bg-[#2563eb] hover:bg-[#16171a]"
874
- : primary === false
875
- ? "bg-[#121212] hover:bg-[#2563eb]"
876
- : primary === "dashed"
877
- ? "h-8 border-dashed"
878
- : "";
879
- return (
880
- <Button
881
- type="submit"
882
- className={cn(baseClasses, primaryClasses)}
883
- disabled={isSubmitting}
884
- {...props}
885
- >
886
- {isSubmitting ? (
887
- <div className='flex items-center gap-3'>
888
- <div className="flex items-center justify-center h-full">
889
- <div className='animate-spin rounded-full h-6 w-6 border-t-4 border-primary border-solid'></div>
890
- </div>
891
- <p className='ml-3'>{loadingText}</p>
892
- </div>
893
- ) : (
894
- children
895
- )}
896
- </Button>
897
- );
898
- };
899
- function FloatButton({
900
- icon,
901
- description,
902
- tooltip,
903
- type = "default",
904
- shape = "circle",
905
- size = "default",
906
- className,
907
- onClick,
908
- children
909
- }: FloatButtonProps) {
910
- const sizeClasses = {
911
- small: "h-10 w-10 text-sm",
912
- default: "h-12 w-12 text-base",
913
- large: "h-14 w-14 text-lg"
914
- }
915
-
916
- const shapeClasses = {
917
- circle: "rounded-full",
918
- square: "rounded-lg"
919
- }
920
-
921
- const typeClasses = {
922
- default: "bg-background border-border hover:bg-accent hover:text-accent-foreground",
923
- primary: "bg-primary text-primary-foreground hover:bg-primary/90"
924
- }
925
-
926
- const buttonContent = (
927
- <Button
928
- variant={type === "primary" ? "default" : "outline"}
929
- size="icon"
930
- className={cn(
931
- "fixed bottom-6 right-6 shadow-lg border transition-all duration-200 hover:shadow-xl",
932
- sizeClasses[size],
933
- shapeClasses[shape],
934
- type === "default" && typeClasses.default,
935
- type === "primary" && typeClasses.primary,
936
- className
937
- )}
938
- onClick={onClick}
939
- >
940
- <div className="flex flex-col items-center justify-center gap-1">
941
- {icon && <span className="flex items-center justify-center">{icon}</span>}
942
- {description && (
943
- <span className="text-xs font-medium truncate max-w-full">{description}</span>
944
- )}
945
- </div>
946
- {children}
947
- </Button>
948
- )
949
-
950
- if (tooltip) {
951
- return (
952
- <TooltipProvider>
953
- <Tooltip>
954
- <TooltipTrigger asChild>{buttonContent}</TooltipTrigger>
955
- <TooltipContent side="left">
956
- <p>{tooltip}</p>
957
- </TooltipContent>
958
- </Tooltip>
959
- </TooltipProvider>
960
- )
961
- }
962
-
963
- return buttonContent
964
- }
965
- function HoldButton({
966
- holdDuration = 3000,
967
- onComplete,
968
- icon,
969
- holdText = "Hold me",
970
- releaseText = "Release",
971
- holdVariant = "red",
972
- className,
973
- disabled,
974
- ...props
975
- }: HoldButtonProps) {
976
- const [isHolding, setIsHolding] = useState(false)
977
- const [progress, setProgress] = useState(0)
978
- const startTimeRef = useRef<number | null>(null)
979
- const animationFrameRef = useRef<number | null>(null)
980
-
981
- const progressColor = {
982
- red: "bg-red-200/30 dark:bg-red-300/30",
983
- green: "bg-green-200/30 dark:bg-green-300/30",
984
- blue: "bg-blue-200/30 dark:bg-blue-300/30",
985
- orange: "bg-orange-200/30 dark:bg-orange-300/30",
986
- grey: "bg-gray-200/30 dark:bg-gray-300/30",
987
- }[holdVariant || "red"]
988
-
989
- const defaultIcon = {
990
- red: <Trash2 className="w-4 h-4" />,
991
- green: <ArchiveX className="w-4 h-4" />,
992
- blue: <XCircle className="w-4 h-4" />,
993
- orange: <AlertCircle className="w-4 h-4" />,
994
- grey: <Ban className="w-4 h-4" />,
995
- }[holdVariant || "red"]
996
-
997
- useEffect(() => {
998
- if (!isHolding) {
999
- setProgress(0)
1000
- if (animationFrameRef.current) {
1001
- cancelAnimationFrame(animationFrameRef.current)
1002
- }
1003
- return
1004
- }
1005
-
1006
- const animate = () => {
1007
- if (startTimeRef.current === null) {
1008
- startTimeRef.current = Date.now()
1009
- }
1010
-
1011
- const elapsed = Date.now() - startTimeRef.current
1012
- const newProgress = Math.min((elapsed / holdDuration) * 100, 100)
1013
- setProgress(newProgress)
1014
-
1015
- if (newProgress >= 100) {
1016
- setIsHolding(false)
1017
- startTimeRef.current = null
1018
- onComplete?.()
1019
- } else {
1020
- animationFrameRef.current = requestAnimationFrame(animate)
1021
- }
1022
- }
1023
-
1024
- animationFrameRef.current = requestAnimationFrame(animate)
1025
-
1026
- return () => {
1027
- if (animationFrameRef.current) {
1028
- cancelAnimationFrame(animationFrameRef.current)
1029
- }
1030
- }
1031
- }, [isHolding, holdDuration, onComplete])
1032
-
1033
- const handleHoldStart = () => {
1034
- if (disabled) return
1035
- setIsHolding(true)
1036
- startTimeRef.current = null
1037
- }
1038
-
1039
- const handleHoldEnd = () => {
1040
- setIsHolding(false)
1041
- startTimeRef.current = null
1042
- }
1043
-
1044
- return (
1045
- <Button
1046
- className={cn(holdButtonVariants({ holdVariant }), className)}
1047
- onMouseDown={handleHoldStart}
1048
- onMouseUp={handleHoldEnd}
1049
- onMouseLeave={handleHoldEnd}
1050
- onTouchStart={handleHoldStart}
1051
- onTouchEnd={handleHoldEnd}
1052
- onTouchCancel={handleHoldEnd}
1053
- disabled={disabled}
1054
- asChild={false}
1055
- {...props}
1056
- >
1057
- <>
1058
- <div
1059
- className={cn("absolute left-0 top-0 h-full transition-all duration-100", progressColor)}
1060
- style={{ width: `${progress}%` }}
1061
- />
1062
- <span className="relative z-10 w-full flex items-center justify-center gap-2">
1063
- {icon || defaultIcon}
1064
- {isHolding ? releaseText : holdText}
1065
- </span>
1066
- </>
1067
- </Button>
1068
- )
1069
- }
1070
- const InputButtonContext = React.createContext<
1071
- InputButtonContextType | undefined
1072
- >(undefined);
1073
- const useInputButton = (): InputButtonContextType => {
1074
- const context = React.useContext(InputButtonContext);
1075
- if (!context) {
1076
- throw new Error('useInputButton must be used within a InputButton');
1077
- }
1078
- return context;
1079
- };
1080
- function InputButtonProvider({
1081
- className,
1082
- transition = { type: 'spring', stiffness: 300, damping: 20 },
1083
- showInput,
1084
- setShowInput,
1085
- id,
1086
- children,
1087
- ...props
1088
- }: InputButtonProviderProps) {
1089
- const localId = React.useId();
1090
- const [localShowInput, setLocalShowInput] = React.useState(false);
1091
-
1092
- return (
1093
- <InputButtonContext.Provider
1094
- value={{
1095
- showInput: showInput ?? localShowInput,
1096
- setShowInput: setShowInput ?? setLocalShowInput,
1097
- transition,
1098
- id: id ?? localId,
1099
- }}
1100
- >
1101
- <div
1102
- data-slot="input-button-provider"
1103
- className={cn(
1104
- 'relative w-fit flex items-center justify-center h-10',
1105
- (showInput || localShowInput) && 'w-full max-w-[400px]',
1106
- className,
1107
- )}
1108
- {...props}
1109
- >
1110
- {children}
1111
- </div>
1112
- </InputButtonContext.Provider>
1113
- );
1114
- }
1115
- function InputButton({ className, ...props }: InputButtonProps) {
1116
- return (
1117
- <motion.div
1118
- data-slot="input-button"
1119
- className={cn('flex size-full', className)}
1120
- {...props}
1121
- />
1122
- );
1123
- }
1124
- function InputButtonAction({ className, ...props }: InputButtonActionProps) {
1125
- const { transition, setShowInput, id } = useInputButton();
1126
-
1127
- return (
1128
- <motion.button
1129
- data-slot="input-button-action"
1130
- className={cn(
1131
- 'bg-background text-sm whitespace-nowrap disabled:pointer-events-none disabled:opacity-50 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive rounded-full border text-background-foreground cursor-pointer pl-4 pr-12 size-full font-medium',
1132
- className,
1133
- )}
1134
- layoutId={`input-button-action-${id}`}
1135
- transition={transition}
1136
- onClick={() => setShowInput((prev) => !prev)}
1137
- {...props}
1138
- />
1139
- );
1140
- }
1141
- function InputButtonSubmit({
1142
- className,
1143
- children,
1144
- icon: Icon = ArrowRight,
1145
- ...props
1146
- }: InputButtonSubmitProps) {
1147
- const { transition, showInput, setShowInput, id } = useInputButton();
1148
-
1149
- return (
1150
- <motion.button
1151
- data-slot="input-button-submit"
1152
- layoutId={`input-button-submit-${id}`}
1153
- transition={transition}
1154
- className={cn(
1155
- "z-[1] [&_svg:not([class*='size-'])]:size-4 cursor-pointer disabled:pointer-events-none disabled:opacity-50 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap bg-primary hover:bg-primary/90 transition-colors text-primary-foreground rounded-full text-sm flex items-center justify-center font-medium absolute inset-y-1 right-1",
1156
- showInput ? 'px-4' : 'aspect-square',
1157
- className,
1158
- )}
1159
- onClick={() => setShowInput((prev) => !prev)}
1160
- {...props}
1161
- >
1162
- {showInput ? (
1163
- <motion.span
1164
- key="show-button"
1165
- initial={{ opacity: 0, scale: 0 }}
1166
- animate={{ opacity: 1, scale: 1 }}
1167
- transition={{ duration: 0.2 }}
1168
- >
1169
- {children}
1170
- </motion.span>
1171
- ) : (
1172
- <motion.span
1173
- key="show-icon"
1174
- initial={{ opacity: 0, scale: 0 }}
1175
- animate={{ opacity: 1, scale: 1 }}
1176
- transition={{ duration: 0.2 }}
1177
- >
1178
- {React.createElement(Icon as React.ComponentType<any>, { className: "size-4" })}
1179
- </motion.span>
1180
- )}
1181
- </motion.button>
1182
- );
1183
- }
1184
- function InputButtonInput({ className, ...props }: InputButtonInputProps) {
1185
- const { transition, showInput, id } = useInputButton();
1186
-
1187
- return (
1188
- <AnimatePresence>
1189
- {showInput && (
1190
- <div className="absolute inset-0 size-full flex items-center justify-center">
1191
- <motion.div
1192
- layoutId={`input-button-input-${id}`}
1193
- className="size-full flex items-center bg-background rounded-full relative"
1194
- transition={transition}
1195
- >
1196
- <input
1197
- data-slot="input-button-input"
1198
- className={cn(
1199
- 'size-full selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground inset-0 pl-4 focus-visible:border-ring border focus-visible:ring-ring/50 focus-visible:ring-[3px] pr-32 py-2 text-sm bg-background rounded-full focus:outline-none absolute shrink-0 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive disabled:pointer-events-none disabled:cursor-not-allowed',
1200
- className,
1201
- )}
1202
- {...props}
1203
- />
1204
- </motion.div>
1205
- </div>
1206
- )}
1207
- </AnimatePresence>
1208
- );
1209
- }
1210
- function LiquidButton({
1211
- className,
1212
- variant,
1213
- size,
1214
- ...props
1215
- }: LiquidButtonProps) {
1216
- return (
1217
- <motion.button
1218
- whileTap={{ scale: 0.95 }}
1219
- whileHover={{ scale: 1.05 }}
1220
- className={cn(buttonVariants({ variant, size, className }), "relative inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium cursor-pointer overflow-hidden disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive [background:_linear-gradient(var(--liquid-button-color)_0_0)_no-repeat_calc(200%-var(--liquid-button-fill,0%))_100%/200%_var(--liquid-button-fill,0.2em)] hover:[--liquid-button-fill:100%] hover:[--liquid-button-delay:0.3s] [transition:_background_0.3s_var(--liquid-button-delay,0s),_color_0.3s_var(--liquid-button-delay,0s),_background-position_0.3s_calc(0.3s_-_var(--liquid-button-delay,0s))] focus:outline-none [--liquid-button-color:var(--primary)]",)}
1221
- {...props}
1222
- />
1223
- );
1224
- }
1225
- function SuccessParticles({ buttonRef, particleCount }: SuccessParticlesProps) {
1226
- const rect = buttonRef.current?.getBoundingClientRect()
1227
- if (!rect) return null
1228
-
1229
- const centerX = rect.left + rect.width / 2
1230
- const centerY = rect.top + rect.height / 2
1231
-
1232
- return (
1233
- <>
1234
- {[...Array(particleCount)].map((_, i) => (
1235
- <div
1236
- key={i}
1237
- className="fixed w-1 h-1 bg-foreground rounded-full pointer-events-none"
1238
- style={{
1239
- left: centerX,
1240
- top: centerY,
1241
- animation: `particle-${i} 0.6s ease-out forwards`,
1242
- animationDelay: `${i * 0.1}s`,
1243
- }}
1244
- />
1245
- ))}
1246
- <style>
1247
- {[...Array(particleCount)].map((_, i) => `
1248
- @keyframes particle-${i} {
1249
- 0% {
1250
- transform: scale(0) translate(0, 0);
1251
- opacity: 1;
1252
- }
1253
- 50% {
1254
- transform: scale(1) translate(${(i % 2 ? 1 : -1) * (Math.random() * 50 + 20)}px, ${-Math.random() * 50 - 20}px);
1255
- opacity: 1;
1256
- }
1257
- 100% {
1258
- transform: scale(0) translate(${(i % 2 ? 1 : -1) * (Math.random() * 50 + 20)}px, ${-Math.random() * 50 - 20}px);
1259
- opacity: 0;
1260
- }
1261
- }
1262
- `).join('')}
1263
- </style>
1264
- </>
1265
- )
1266
- }
1267
- function ParticleButton({
1268
- children,
1269
- onClick,
1270
- onSuccess,
1271
- successDuration = 1000,
1272
- particleCount = 6,
1273
- showIcon = true,
1274
- icon,
1275
- variant,
1276
- size,
1277
- align,
1278
- className,
1279
- disabled,
1280
- ...props
1281
- }: ParticleButtonProps) {
1282
- const [showParticles, setShowParticles] = useState(false)
1283
- const buttonRef = useRef<HTMLButtonElement>(null)
1284
-
1285
- const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
1286
- if (disabled) return
1287
-
1288
- setShowParticles(true)
1289
- onClick?.(e)
1290
- onSuccess?.()
1291
-
1292
- setTimeout(() => {
1293
- setShowParticles(false)
1294
- }, successDuration)
1295
- }
1296
-
1297
- const getParticleCount = () => {
1298
- if (typeof particleCount === 'number') return particleCount
1299
- switch (particleCount) {
1300
- case 'low':
1301
- return 4
1302
- case 'high':
1303
- return 10
1304
- default:
1305
- return 6
1306
- }
1307
- }
1308
-
1309
- return (
1310
- <>
1311
- {showParticles && (
1312
- <SuccessParticles
1313
- buttonRef={buttonRef}
1314
- particleCount={getParticleCount()}
1315
- />
1316
- )}
1317
- <Button
1318
- ref={buttonRef}
1319
- onClick={handleClick}
1320
- variant={variant}
1321
- size={size}
1322
- align={align}
1323
- disabled={disabled}
1324
- className={cn(
1325
- particleButtonVariants(), "relative transition-transform duration-100",
1326
- showParticles && "scale-95",
1327
- className
1328
- )}
1329
- {...props}
1330
- >
1331
- {children}
1332
- {showIcon && (icon || <MousePointerClick className="h-4 w-4" />)}
1333
- </Button>
1334
- </>
1335
- )
1336
- }
1337
- const RatingContext = createContext<RatingContextValue | null>(null);
1338
- const useRating = () => {
1339
- const context = useContext(RatingContext);
1340
- if (!context) {
1341
- throw new Error('useRating must be used within a Rating1 component');
1342
- }
1343
- return context;
1344
- };
1345
- const RatingButton = ({
1346
- index: providedIndex,
1347
- size = 20,
1348
- className,
1349
- icon = <StarIcon />,
1350
- }: RatingButtonProps) => {
1351
- const {
1352
- value,
1353
- readOnly,
1354
- hoverValue,
1355
- focusedStar,
1356
- handleValueChange,
1357
- handleKeyDown,
1358
- setHoverValue,
1359
- setFocusedStar,
1360
- } = useRating();
1361
-
1362
- const index = providedIndex ?? 0;
1363
- const isActive = index < (hoverValue ?? focusedStar ?? value ?? 0);
1364
- let tabIndex = -1;
1365
-
1366
- if (!readOnly) {
1367
- tabIndex = value === index + 1 ? 0 : -1;
1368
- }
1369
-
1370
- const handleClick = useCallback(
1371
- (event: MouseEvent<HTMLButtonElement>) => {
1372
- handleValueChange(event, index + 1);
1373
- },
1374
- [handleValueChange, index]
1375
- );
1376
-
1377
- const handleMouseEnter = useCallback(() => {
1378
- if (!readOnly) {
1379
- setHoverValue(index + 1);
1380
- }
1381
- }, [readOnly, setHoverValue, index]);
1382
-
1383
- const handleFocus = useCallback(() => {
1384
- setFocusedStar(index + 1);
1385
- }, [setFocusedStar, index]);
1386
-
1387
- const handleBlur = useCallback(() => {
1388
- setFocusedStar(null);
1389
- }, [setFocusedStar]);
1390
-
1391
- return (
1392
- <button
1393
- className={cn(
1394
- 'rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
1395
- 'p-0.5',
1396
- readOnly && 'cursor-default',
1397
- className
1398
- )}
1399
- disabled={readOnly}
1400
- onBlur={handleBlur}
1401
- onClick={handleClick}
1402
- onFocus={handleFocus}
1403
- onKeyDown={handleKeyDown}
1404
- onMouseEnter={handleMouseEnter}
1405
- tabIndex={tabIndex}
1406
- type="button"
1407
- >
1408
- {cloneElement(icon, {
1409
- size,
1410
- className: cn(
1411
- 'transition-colors duration-200',
1412
- isActive && 'fill-current',
1413
- !readOnly && 'cursor-pointer'
1414
- ),
1415
- 'aria-hidden': 'true',
1416
- })}
1417
- </button>
1418
- );
1419
- };
1420
- const Rating = ({
1421
- value: controlledValue,
1422
- onValueChange: controlledOnValueChange,
1423
- defaultValue = 0,
1424
- onChange,
1425
- readOnly = false,
1426
- className,
1427
- children,
1428
- ...props
1429
- }: RatingProps) => {
1430
- const [hoverValue, setHoverValue] = useState<number | null>(null);
1431
- const [focusedStar, setFocusedStar] = useState<number | null>(null);
1432
- const containerRef = useRef<HTMLDivElement>(null);
1433
- const [value, onValueChange] = useControllableState({
1434
- defaultProp: defaultValue,
1435
- prop: controlledValue,
1436
- onChange: controlledOnValueChange,
1437
- });
1438
-
1439
- const handleValueChange = useCallback(
1440
- (
1441
- event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,
1442
- newValue: number
1443
- ) => {
1444
- if (!readOnly) {
1445
- onChange?.(event, newValue);
1446
- onValueChange?.(newValue);
1447
- }
1448
- },
1449
- [readOnly, onChange, onValueChange]
1450
- );
1451
-
1452
- const handleKeyDown = useCallback(
1453
- (event: KeyboardEvent<HTMLButtonElement>) => {
1454
- if (readOnly) {
1455
- return;
1456
- }
1457
-
1458
- const total = Children.count(children);
1459
- let newValue = focusedStar !== null ? focusedStar : (value ?? 0);
1460
-
1461
- switch (event.key) {
1462
- case 'ArrowRight':
1463
- if (event.shiftKey || event.metaKey) {
1464
- newValue = total;
1465
- } else {
1466
- newValue = Math.min(total, newValue + 1);
1467
- }
1468
- break;
1469
- case 'ArrowLeft':
1470
- if (event.shiftKey || event.metaKey) {
1471
- newValue = 1;
1472
- } else {
1473
- newValue = Math.max(1, newValue - 1);
1474
- }
1475
- break;
1476
- default:
1477
- return;
1478
- }
1479
-
1480
- event.preventDefault();
1481
- setFocusedStar(newValue);
1482
- handleValueChange(event, newValue);
1483
- },
1484
- [focusedStar, value, children, readOnly, handleValueChange]
1485
- );
1486
-
1487
- useEffect(() => {
1488
- if (focusedStar !== null && containerRef.current) {
1489
- const buttons = containerRef.current.querySelectorAll('button');
1490
- buttons[focusedStar - 1]?.focus();
1491
- }
1492
- }, [focusedStar]);
1493
-
1494
- const contextValue: RatingContextValue = {
1495
- value: value ?? 0,
1496
- readOnly,
1497
- hoverValue,
1498
- focusedStar,
1499
- handleValueChange,
1500
- handleKeyDown,
1501
- setHoverValue,
1502
- setFocusedStar,
1503
- };
1504
-
1505
- return (
1506
- <RatingContext.Provider value={contextValue}>
1507
- <div
1508
- aria-label="Rating1"
1509
- className={cn('inline-flex items-center gap-0.5', className)}
1510
- onMouseLeave={() => setHoverValue(null)}
1511
- ref={containerRef}
1512
- role="radiogroup"
1513
- {...props}
1514
- >
1515
- {Children.map(children, (child, index) => {
1516
- if (!child) {
1517
- return null;
1518
- }
1519
-
1520
- return cloneElement(child as ReactElement<RatingButtonProps>, {
1521
- index,
1522
- });
1523
- })}
1524
- </div>
1525
- </RatingContext.Provider>
1526
- );
1527
- };
1528
- function RippleButton({
1529
- ref,
1530
- children,
1531
- onClick,
1532
- className,
1533
- rippleClassName,
1534
- variant,
1535
- size,
1536
- scale = 10,
1537
- transition = { duration: 0.6, ease: 'easeOut' },
1538
- ...props
1539
- }: RippleButtonProps) {
1540
- const [ripples, setRipples] = React.useState<Ripple[]>([]);
1541
- const buttonRef = React.useRef<HTMLButtonElement>(null);
1542
- React.useImperativeHandle(ref, () => buttonRef.current as HTMLButtonElement);
1543
-
1544
- const createRipple = React.useCallback(
1545
- (event: React.MouseEvent<HTMLButtonElement>) => {
1546
- const button = buttonRef.current;
1547
- if (!button) return;
1548
-
1549
- const rect = button.getBoundingClientRect();
1550
- const x = event.clientX - rect.left;
1551
- const y = event.clientY - rect.top;
1552
-
1553
- const newRipple: Ripple = {
1554
- id: Date.now(),
1555
- x,
1556
- y,
1557
- };
1558
-
1559
- setRipples((prev) => [...prev, newRipple]);
1560
-
1561
- setTimeout(() => {
1562
- setRipples((prev) => prev.filter((r) => r.id !== newRipple.id));
1563
- }, 600);
1564
- },
1565
- [],
1566
- );
1567
-
1568
- const handleClick = React.useCallback(
1569
- (event: React.MouseEvent<HTMLButtonElement>) => {
1570
- createRipple(event);
1571
- if (onClick) {
1572
- onClick(event);
1573
- }
1574
- },
1575
- [createRipple, onClick],
1576
- );
1577
-
1578
- return (
1579
- <motion.button
1580
- ref={buttonRef}
1581
- data-slot="ripple-button"
1582
- onClick={handleClick}
1583
- whileTap={{ scale: 0.95 }}
1584
- whileHover={{ scale: 1.05 }}
1585
- className={cn(buttonVariants({ variant, size, className }))}
1586
- {...props}
1587
- >
1588
- {children}
1589
- {ripples.map((ripple) => (
1590
- <motion.span
1591
- key={ripple.id}
1592
- initial={{ scale: 0, opacity: 0.5 }}
1593
- animate={{ scale, opacity: 0 }}
1594
- transition={transition}
1595
- className={cn(
1596
- rippleVariants({ variant, className: rippleClassName }),
1597
- )}
1598
- style={{
1599
- top: ripple.y - 10,
1600
- left: ripple.x - 10,
1601
- }}
1602
- />
1603
- ))}
1604
- </motion.button>
1605
- );
1606
- }
1607
- function SocialButton({
1608
- shareButtons = [
1609
- { icon: Twitter, label: "Share on Twitter" },
1610
- { icon: Instagram, label: "Share on Instagram" },
1611
- { icon: Linkedin, label: "Share on LinkedIn" },
1612
- { icon: Link, label: "Copy link" },
1613
- ],
1614
- label = "Hover me",
1615
- icon,
1616
- variant,
1617
- className,
1618
- ...props
1619
- }: SocialButtonProps) {
1620
- const [isVisible, setIsVisible] = useState(false)
1621
- const [activeIndex, setActiveIndex] = useState<number | null>(null)
1622
-
1623
- const handleShare = (index: number, onClick?: () => void) => {
1624
- setActiveIndex(index)
1625
- onClick?.()
1626
- setTimeout(() => setActiveIndex(null), 300)
1627
- }
1628
-
1629
- const buttonBgClass = variant === "dark"
1630
- ? "bg-black dark:bg-white text-white dark:text-black hover:bg-gray-900 dark:hover:bg-gray-100"
1631
- : "bg-white dark:bg-black text-black dark:text-white hover:bg-gray-50 dark:hover:bg-gray-950"
1632
-
1633
- return (
1634
- <div
1635
- className="relative"
1636
- onMouseEnter={() => setIsVisible(true)}
1637
- onMouseLeave={() => setIsVisible(false)}
1638
- >
1639
- <div
1640
- className="transition-opacity duration-200"
1641
- style={{ opacity: isVisible ? 0 : 1 }}
1642
- >
1643
- <Button
1644
- className={cn(socialButtonVariants({ variant }), className)}
1645
- asChild={false}
1646
- {...props}
1647
- >
1648
- <span className="flex items-center gap-2">
1649
- {icon || <Link className="w-4 h-4" />}
1650
- {label}
1651
- </span>
1652
- </Button>
1653
- </div>
1654
-
1655
- <div
1656
- className="absolute top-0 left-0 flex h-10 overflow-hidden transition-all duration-300"
1657
- style={{
1658
- width: isVisible ? `${shareButtons.length * 40}px` : '0px',
1659
- }}
1660
- >
1661
- {shareButtons.map((button, i) => (
1662
- <button
1663
- type="button"
1664
- key={`share-${button.label}`}
1665
- aria-label={button.label}
1666
- onClick={() => handleShare(i, button.onClick)}
1667
- className={cn(
1668
- "h-10 w-10 flex items-center justify-center",
1669
- buttonBgClass,
1670
- i === 0 && "rounded-l-md",
1671
- i === shareButtons.length - 1 && "rounded-r-md",
1672
- "border-r border-white/10 dark:border-black/10 last:border-r-0",
1673
- "outline-none relative overflow-hidden transition-all duration-200"
1674
- )}
1675
- style={{
1676
- opacity: isVisible ? 1 : 0,
1677
- transform: isVisible ? 'translateX(0)' : 'translateX(-20px)',
1678
- transitionDelay: isVisible ? `${i * 50}ms` : '0ms',
1679
- }}
1680
- >
1681
- <div
1682
- className="relative z-10 transition-transform duration-200"
1683
- style={{
1684
- transform: activeIndex === i ? 'scale(0.85)' : 'scale(1)',
1685
- }}
1686
- >
1687
- <button.icon className="w-4 h-4" />
1688
- </div>
1689
- <div
1690
- className="absolute inset-0 bg-white dark:bg-black transition-opacity duration-200"
1691
- style={{
1692
- opacity: activeIndex === i ? 0.15 : 0,
1693
- }}
1694
- />
1695
- </button>
1696
- ))}
1697
- </div>
1698
- </div>
1699
- )
1700
- }
1701
- function SwitchButton({
1702
- checked = false,
1703
- onCheckedChange,
1704
- className,
1705
- variant = 'default',
1706
- size = 'md',
1707
- disabled = false,
1708
- labels
1709
- }: SwitchButtonProps) {
1710
- const [isChecked, setIsChecked] = useState(checked);
1711
-
1712
- const handleToggle = () => {
1713
- if (disabled) return;
1714
-
1715
- const newChecked = !isChecked;
1716
- setIsChecked(newChecked);
1717
- onCheckedChange?.(newChecked);
1718
- };
1719
-
1720
- const sizeClasses = {
1721
- sm: 'w-16 h-8',
1722
- md: 'w-20 h-10',
1723
- lg: 'w-24 h-12'
1724
- };
1725
-
1726
- const knobSizeClasses = {
1727
- sm: 'w-6 h-6',
1728
- md: 'w-8 h-8',
1729
- lg: 'w-10 h-10'
1730
- };
1731
-
1732
- const variantClasses = {
1733
- default: {
1734
- background: isChecked ? 'bg-muted-foreground' : 'bg-muted',
1735
- knob: 'bg-background',
1736
- text: 'text-background'
1737
- },
1738
- primary: {
1739
- background: isChecked ? 'bg-primary' : 'bg-muted',
1740
- knob: 'bg-background',
1741
- text: 'text-primary-foreground'
1742
- },
1743
- secondary: {
1744
- background: isChecked ? 'bg-secondary' : 'bg-muted',
1745
- knob: 'bg-background',
1746
- text: 'text-secondary-foreground'
1747
- },
1748
- destructive: {
1749
- background: isChecked ? 'bg-destructive' : 'bg-muted',
1750
- knob: 'bg-background',
1751
- text: 'text-destructive-foreground'
1752
- }
1753
- };
1754
-
1755
- const variantConfig = variantClasses[variant];
1756
-
1757
- return (
1758
- <button
1759
- type="button"
1760
- role="switch"
1761
- aria-checked={isChecked}
1762
- onClick={handleToggle}
1763
- disabled={disabled}
1764
- className={cn(
1765
- 'relative inline-flex items-center rounded-full border-2 border-transparent transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
1766
- sizeClasses[size],
1767
- variantConfig.background,
1768
- className
1769
- )}
1770
- >
1771
- <span
1772
- className={cn(
1773
- 'inline-block transform rounded-full shadow-lg transition-all duration-200',
1774
- knobSizeClasses[size],
1775
- variantConfig.knob,
1776
- isChecked
1777
- ? size === 'sm' ? 'translate-x-8' :
1778
- size === 'md' ? 'translate-x-10' : 'translate-x-12'
1779
- : 'translate-x-1'
1780
- )}
1781
- />
1782
-
1783
- {labels && (
1784
- <>
1785
- <span
1786
- className={cn(
1787
- 'absolute left-1 text-xs font-medium transition-opacity duration-200',
1788
- variantConfig.text,
1789
- !isChecked ? 'opacity-100' : 'opacity-0'
1790
- )}
1791
- >
1792
- {labels.unchecked}
1793
- </span>
1794
- <span
1795
- className={cn(
1796
- 'absolute right-1 text-xs font-medium transition-opacity duration-200',
1797
- variantConfig.text,
1798
- isChecked ? 'opacity-100' : 'opacity-0'
1799
- )}
1800
- >
1801
- {labels.checked}
1802
- </span>
1803
- </>
1804
- )}
1805
- </button>
1806
- );
1807
- }
1808
- const SplitButton = ({
1809
- label = null,
1810
- icon,
1811
- items,
1812
- onClick,
1813
- className,
1814
- containerClassName,
1815
- variant = "default",
1816
- align = "default",
1817
- size = "sm",
1818
- children,
1819
- ...props
1820
- }: SplitButtonProps) => {
1821
- return (
1822
- <div className={cn("inline-flex", containerClassName)}>
1823
- <Button
1824
- onClick={onClick}
1825
- className={cn("rounded-r-none border-r-0", buttonVariants({ variant, size, className, align }))}
1826
- {...props}
1827
- >
1828
- {children}
1829
- </Button>
1830
- <DropdownMenu>
1831
- <DropdownMenuTrigger asChild>
1832
- <Button className={cn("rounded-l-none px-2", buttonVariants({ variant, size, className }))} >
1833
- {label !== null ? (
1834
- <>
1835
- {label}
1836
- </>
1837
- ) : (
1838
- <ChevronDown className="h-4 w-4" />
1839
- )}
1840
- </Button>
1841
- </DropdownMenuTrigger>
1842
- <DropdownMenuContent align="end" className="w-48">
1843
- {items.map((item, index) => (
1844
- <React.Fragment key={index}>
1845
- {item.separator && <DropdownMenuSeparator />}
1846
- <DropdownMenuItem
1847
- onClick={item.command}
1848
- className="cursor-pointer"
1849
-
1850
- >
1851
- {item.icon && <span className="mr-2">{item.icon}</span>}
1852
- {item.label}
1853
- </DropdownMenuItem>
1854
- </React.Fragment>
1855
- ))}
1856
- </DropdownMenuContent>
1857
- </DropdownMenu>
1858
- </div>
1859
- );
1860
- }
1861
- function ToggleButton({
1862
- checked = false,
1863
- onChange,
1864
- disabled = false,
1865
- className,
1866
- variant = "default",
1867
- size = "md",
1868
- children,
1869
- ...props
1870
- }: ButtonToggleProps) {
1871
- const handleClick = () => {
1872
- if (!disabled && onChange) {
1873
- onChange(!checked)
1874
- }
1875
- }
1876
-
1877
- const handleKeyDown = (e: React.KeyboardEvent) => {
1878
- if (e.key === "Enter" || e.key === " ") {
1879
- e.preventDefault()
1880
- handleClick()
1881
- }
1882
- }
1883
-
1884
- return (
1885
- <button
1886
- type="button"
1887
- className={cn(
1888
- toggleButtonVariants({
1889
- variant,
1890
- size,
1891
- checked: checked ? "true" : "false"
1892
- }),
1893
- className
1894
- )}
1895
- onClick={handleClick}
1896
- onKeyDown={handleKeyDown}
1897
- disabled={disabled}
1898
- role="switch"
1899
- aria-checked={checked}
1900
- aria-disabled={disabled}
1901
- {...props}
1902
- >
1903
- {children}
1904
- </button>
1905
- )
1906
- }
1907
- SplitButton.displayName = "SplitButton"
1908
- function SlideButton({
1909
- className,
1910
- variant,
1911
- size,
1912
- text = "Browse Components",
1913
- hoverText,
1914
- to = "/",
1915
- prefetch = "intent",
1916
- ...props
1917
- }: Omit<React.ComponentProps<typeof Link>, 'to'> &
1918
- VariantProps<typeof slideTextButtonVariants> & {
1919
- text?: string
1920
- hoverText?: string
1921
- to?: string
1922
- }) {
1923
- const slideText = hoverText ?? text
1924
-
1925
- return (
1926
- <Link
1927
- data-slot="slide-text-button"
1928
- className={cn(slideTextButtonVariants({ variant, size, className }))}
1929
- to={to}
1930
- prefetch={prefetch}
1931
- {...props}
1932
- >
1933
- <span className="group-hover:-translate-y-full relative inline-block transition-transform duration-300 ease-in-out">
1934
- <span className="flex items-center gap-2 opacity-100 transition-opacity duration-300 group-hover:opacity-0">
1935
- <span className="font-medium">{text}</span>
1936
- </span>
1937
- <span className="absolute top-full left-0 flex items-center gap-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
1938
- <span className="font-medium">{slideText}</span>
1939
- </span>
1940
- </span>
1941
- </Link>
1942
- )
1943
- }
1944
- const TextRevealButton = React.forwardRef<
1945
- HTMLButtonElement,
1946
- TextRevealButtonProps
1947
- >(({
1948
- text = "shadcn.io",
1949
- revealColor = "#37FF8B",
1950
- strokeColor = "rgba(100, 100, 100, 0.7)",
1951
- className,
1952
- style,
1953
- ...props
1954
- }, ref) => {
1955
- return (
1956
- <button
1957
- ref={ref}
1958
- className={cn(
1959
- "group relative cursor-pointer bg-transparent border-none p-0 m-0 h-auto font-['Arial'] uppercase",
1960
- className
1961
- )}
1962
- style={{
1963
- fontSize: '2em',
1964
- letterSpacing: '3px',
1965
- color: 'transparent',
1966
- WebkitTextStroke: `1px ${strokeColor}`,
1967
- ...style,
1968
- }}
1969
- {...props}
1970
- >
1971
- <span>
1972
- &nbsp;{text}&nbsp;
1973
- </span>
1974
- <span
1975
- aria-hidden="true"
1976
- className="absolute inset-0 w-0 overflow-hidden transition-all duration-500 group-hover:w-full group-hover:[filter:drop-shadow(0_0_23px_#37FF8B)]"
1977
- style={{
1978
- color: revealColor,
1979
- borderRight: `6px solid ${revealColor}`,
1980
- WebkitTextStroke: `1px ${revealColor}`,
1981
- }}
1982
- >
1983
- &nbsp;{text}&nbsp;
1984
- </span>
1985
- </button>
1986
- );
1987
- });
1988
- TextRevealButton.displayName = "TextRevealButton";
1989
- const ShimmerButton = React.forwardRef<
1990
- HTMLButtonElement,
1991
- ShimmerButtonProps
1992
- >(
1993
- (
1994
- {
1995
- shimmerColor = "#ffffff",
1996
- shimmerSize = "0.05em",
1997
- shimmerDuration = "3s",
1998
- borderRadius = "100px",
1999
- background = "rgba(0, 0, 0, 1)",
2000
- className,
2001
- children,
2002
- ...props
2003
- },
2004
- ref
2005
- ) => {
2006
- return (
2007
- <button
2008
- style={
2009
- {
2010
- "--spread": "90deg",
2011
- "--shimmer-color": shimmerColor,
2012
- "--radius": borderRadius,
2013
- "--speed": shimmerDuration,
2014
- "--cut": shimmerSize,
2015
- "--bg": background,
2016
- } as CSSProperties
2017
- }
2018
- className={cn(
2019
- "group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden [border-radius:var(--radius)] border border-white/10 px-6 py-3 whitespace-nowrap text-white [background:var(--bg)]",
2020
- "transform-gpu transition-transform duration-300 ease-in-out active:translate-y-px",
2021
- className
2022
- )}
2023
- ref={ref}
2024
- {...props}
2025
- >
2026
- <div
2027
- className={cn(
2028
- "-z-30 blur-[2px]",
2029
- "[container-type:size] absolute inset-0 overflow-visible"
2030
- )}
2031
- >
2032
- <div className="animate-shimmer-slide absolute inset-0 [aspect-ratio:1] h-[100cqh] [border-radius:0] [mask:none]">
2033
- <div className="animate-spin-around absolute -inset-full w-auto [translate:0_0] rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))]" />
2034
- </div>
2035
- </div>
2036
- {children}
2037
- <div
2038
- className={cn(
2039
- "absolute inset-0 size-full",
2040
- "rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]",
2041
- "transform-gpu transition-all duration-300 ease-in-out",
2042
- "group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]",
2043
- "group-active:shadow-[inset_0_-10px_10px_#ffffff3f]"
2044
- )}
2045
- />
2046
- <div
2047
- className={cn(
2048
- "absolute [inset:var(--cut)] -z-20 [border-radius:var(--radius)] [background:var(--bg)]"
2049
- )}
2050
- />
2051
- </button>
2052
- )
2053
- }
2054
- )
2055
- function TooltipButton({ icon, content, onClick = null, size = 'icon', variant = 'ghost', className }) {
2056
- return (
2057
- <TooltipProvider>
2058
- <Tooltip>
2059
- <TooltipTrigger asChild>
2060
- <Button
2061
- size={size}
2062
- variant={variant}
2063
- className={cn('', className)}
2064
- onClick={onClick}
2065
- >
2066
- {icon}
2067
- </Button>
2068
- </TooltipTrigger>
2069
- <TooltipContent>
2070
- <p>{content}</p>
2071
- </TooltipContent>
2072
- </Tooltip>
2073
- </TooltipProvider>
2074
- );
2075
- }
2076
- const RatingContext1 = createContext<RatingContextValue | null>(null);
2077
- const useRating1 = () => {
2078
- const context = useContext(RatingContext1);
2079
- if (!context) {
2080
- throw new Error('useRating1 must be used within a Rate component');
2081
- }
2082
- return context;
2083
- };
2084
- const RatingButton1 = ({
2085
- index: providedIndex,
2086
- size = 20,
2087
- className,
2088
- icon = <StarIcon />,
2089
- }: RatingButtonProps) => {
2090
- const {
2091
- value,
2092
- readOnly,
2093
- hoverValue,
2094
- focusedStar,
2095
- handleValueChange,
2096
- handleKeyDown,
2097
- setHoverValue,
2098
- setFocusedStar,
2099
- } = useRating1();
2100
- const index = providedIndex ?? 0;
2101
- const isActive = index < (hoverValue ?? focusedStar ?? value ?? 0);
2102
- let tabIndex = -1;
2103
- if (!readOnly) {
2104
- tabIndex = value === index + 1 ? 0 : -1;
2105
- }
2106
- const handleClick = useCallback(
2107
- (event: MouseEvent<HTMLButtonElement>) => {
2108
- handleValueChange(event, index + 1);
2109
- },
2110
- [handleValueChange, index]
2111
- );
2112
- const handleMouseEnter = useCallback(() => {
2113
- if (!readOnly) {
2114
- setHoverValue(index + 1);
2115
- }
2116
- }, [readOnly, setHoverValue, index]);
2117
- const handleFocus = useCallback(() => {
2118
- setFocusedStar(index + 1);
2119
- }, [setFocusedStar, index]);
2120
- const handleBlur = useCallback(() => {
2121
- setFocusedStar(null);
2122
- }, [setFocusedStar]);
2123
- return (
2124
- <button
2125
- className={cn(
2126
- 'rounded-full focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
2127
- 'p-0.5',
2128
- readOnly && 'cursor-default',
2129
- className
2130
- )}
2131
- disabled={readOnly}
2132
- onBlur={handleBlur}
2133
- onClick={handleClick}
2134
- onFocus={handleFocus}
2135
- onKeyDown={handleKeyDown}
2136
- onMouseEnter={handleMouseEnter}
2137
- tabIndex={tabIndex}
2138
- type="button"
2139
- >
2140
- {cloneElement(icon, {
2141
- size,
2142
- className: cn(
2143
- 'transition-colors duration-200',
2144
- isActive && 'fill-current',
2145
- !readOnly && 'cursor-pointer'
2146
- ),
2147
- 'aria-hidden': 'true',
2148
- })}
2149
- </button>
2150
- );
2151
- };
2152
- const Rate1 = ({
2153
- value: controlledValue,
2154
- onValueChange: controlledOnValueChange,
2155
- defaultValue = 0,
2156
- onChange,
2157
- readOnly = false,
2158
- className,
2159
- children,
2160
- ...props
2161
- }: RatingProps) => {
2162
- const [hoverValue, setHoverValue] = useState<number | null>(null);
2163
- const [focusedStar, setFocusedStar] = useState<number | null>(null);
2164
- const containerRef = useRef<HTMLDivElement>(null);
2165
- const [value, onValueChange] = useControllableState({
2166
- defaultProp: defaultValue,
2167
- prop: controlledValue,
2168
- onChange: controlledOnValueChange,
2169
- });
2170
- const handleValueChange = useCallback(
2171
- (
2172
- event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,
2173
- newValue: number
2174
- ) => {
2175
- if (!readOnly) {
2176
- onChange?.(event, newValue);
2177
- onValueChange?.(newValue);
2178
- }
2179
- },
2180
- [readOnly, onChange, onValueChange]
2181
- );
2182
- const handleKeyDown = useCallback(
2183
- (event: KeyboardEvent<HTMLButtonElement>) => {
2184
- if (readOnly) {
2185
- return;
2186
- }
2187
- const total = Children.count(children);
2188
- let newValue = focusedStar !== null ? focusedStar : (value ?? 0);
2189
- switch (event.key) {
2190
- case 'ArrowRight':
2191
- if (event.shiftKey || event.metaKey) {
2192
- newValue = total;
2193
- } else {
2194
- newValue = Math.min(total, newValue + 1);
2195
- }
2196
- break;
2197
- case 'ArrowLeft':
2198
- if (event.shiftKey || event.metaKey) {
2199
- newValue = 1;
2200
- } else {
2201
- newValue = Math.max(1, newValue - 1);
2202
- }
2203
- break;
2204
- default:
2205
- return;
2206
- }
2207
- event.preventDefault();
2208
- setFocusedStar(newValue);
2209
- handleValueChange(event, newValue);
2210
- },
2211
- [focusedStar, value, children, readOnly, handleValueChange]
2212
- );
2213
- useEffect(() => {
2214
- if (focusedStar !== null && containerRef.current) {
2215
- const buttons = containerRef.current.querySelectorAll('button');
2216
- buttons[focusedStar - 1]?.focus();
2217
- }
2218
- }, [focusedStar]);
2219
- const contextValue: RatingContextValue = {
2220
- value: value ?? 0,
2221
- readOnly,
2222
- hoverValue,
2223
- focusedStar,
2224
- handleValueChange,
2225
- handleKeyDown,
2226
- setHoverValue,
2227
- setFocusedStar,
2228
- };
2229
- return (
2230
- <RatingContext1.Provider value={contextValue}>
2231
- <div
2232
- aria-label="Rate"
2233
- className={cn('inline-flex items-center gap-0.5', className)}
2234
- onMouseLeave={() => setHoverValue(null)}
2235
- ref={containerRef}
2236
- role="radiogroup"
2237
- {...props}
2238
- >
2239
- {Children.map(children, (child, index) => {
2240
- if (!child) {
2241
- return null;
2242
- }
2243
- return cloneElement(child as ReactElement<RatingButtonProps>, {
2244
- index,
2245
- });
2246
- })}
2247
- </div>
2248
- </RatingContext1.Provider>
2249
- );
2250
- };
2251
-
2252
- export function ButtonXDemo() {
2253
- const [clicked, setClicked] = useState("")
2254
- const [rating, setRating] = useState(0)
2255
- const [switchChecked, setSwitchChecked] = useState(false)
2256
- const [toggleChecked, setToggleChecked] = useState(false)
2257
-
2258
- return (
2259
- <div className="flex flex-col space-y-8 p-6 max-w-6xl mx-auto">
2260
- <div>
2261
- <h2 className="text-2xl font-bold mb-4">Button Variants</h2>
2262
- <div className="flex gap-4 flex-wrap items-center">
2263
- <Button variant="default">Default</Button>
2264
- <Button variant="destructive">Destructive</Button>
2265
- <Button variant="outline">Outline</Button>
2266
- <Button variant="secondary">Secondary</Button>
2267
- <Button variant="ghost">Ghost</Button>
2268
- <Button variant="link">Link</Button>
2269
- </div>
2270
- </div>
2271
-
2272
- <div>
2273
- <h2 className="text-2xl font-bold mb-4">Command Button Variants</h2>
2274
- <div className="flex gap-4 flex-wrap items-center">
2275
- <CommandButton
2276
- variant="default"
2277
- onClick={() => setClicked("Default clicked")}
2278
- >
2279
- CMD + K
2280
- </CommandButton>
2281
- <CommandButton
2282
- variant="primary"
2283
- onClick={() => setClicked("Primary clicked")}
2284
- >
2285
- CMD + P
2286
- </CommandButton>
2287
- <CommandButton
2288
- variant="secondary"
2289
- onClick={() => setClicked("Secondary clicked")}
2290
- >
2291
- CMD + S
2292
- </CommandButton>
2293
- </div>
2294
- {clicked && (
2295
- <p className="mt-4 text-sm text-muted-foreground">{clicked}</p>
2296
- )}
2297
- </div>
2298
-
2299
- <div>
2300
- <h2 className="text-2xl font-bold mb-4">Attract Button</h2>
2301
- <div className="flex justify-center gap-4">
2302
- <AttractButton attractVariant="default" />
2303
- <AttractButton attractVariant="violet" />
2304
- <AttractButton attractVariant="blue" />
2305
- <AttractButton attractVariant="green" />
2306
- </div>
2307
- </div>
2308
-
2309
- <div>
2310
- <h2 className="text-2xl font-bold mb-4">Copy Button</h2>
2311
- <div className="flex justify-center gap-4">
2312
- <CopyButton content="Hello World!" />
2313
- <CopyButton content="Copy this text" variant="outline" />
2314
- </div>
2315
- </div>
2316
-
2317
- <div>
2318
- <h2 className="text-2xl font-bold mb-4">Flip Button</h2>
2319
- <div className="flex justify-center gap-4">
2320
- <FlipButton frontText="Hover Me" backText="Flipped!" />
2321
- <FlipButton frontText="Left Flip" backText="Success!" from="left" />
2322
- <FlipButton frontText="Right Flip" backText="Nice!" from="right" />
2323
- </div>
2324
- </div>
2325
-
2326
- <div>
2327
- <h2 className="text-2xl font-bold mb-4">Float Button</h2>
2328
- <FloatButton
2329
- icon={<MousePointerClick className="h-4 w-4" />}
2330
- tooltip="Click me!"
2331
- onClick={() => alert("Float button clicked!")}
2332
- />
2333
- </div>
2334
-
2335
- <div>
2336
- <h2 className="text-2xl font-bold mb-4">Hold Button</h2>
2337
- <div className="flex justify-center gap-4">
2338
- <HoldButton
2339
- holdVariant="red"
2340
- onComplete={() => alert("Hold completed!")}
2341
- />
2342
- <HoldButton
2343
- holdVariant="green"
2344
- holdText="Hold to Archive"
2345
- onComplete={() => alert("Archived!")}
2346
- />
2347
- </div>
2348
- </div>
2349
-
2350
- <div>
2351
- <h2 className="text-2xl font-bold mb-4">Input Button</h2>
2352
- <div className="flex justify-center">
2353
- <InputButtonProvider>
2354
- <InputButton>
2355
- <InputButtonAction>Enter your email</InputButtonAction>
2356
- <InputButtonInput placeholder="email@example.com" />
2357
- <InputButtonSubmit>Submit</InputButtonSubmit>
2358
- </InputButton>
2359
- </InputButtonProvider>
2360
- </div>
2361
- </div>
2362
-
2363
- <div>
2364
- <h2 className="text-2xl font-bold mb-4">Liquid Button</h2>
2365
- <div className="flex justify-center gap-4">
2366
- <LiquidButton>Hover Me</LiquidButton>
2367
- <LiquidButton variant="outline">Liquid Effect</LiquidButton>
2368
- </div>
2369
- </div>
2370
-
2371
- <div>
2372
- <h2 className="text-2xl font-bold mb-4">Particle Button</h2>
2373
- <div className="flex justify-center gap-4">
2374
- <ParticleButton
2375
- onSuccess={() => console.log("Success!")}
2376
- particleCount="low"
2377
- >
2378
- Low Particles
2379
- </ParticleButton>
2380
- <ParticleButton
2381
- onSuccess={() => console.log("Success!")}
2382
- particleCount={6}
2383
- >
2384
- Medium Particles
2385
- </ParticleButton>
2386
- <ParticleButton
2387
- onSuccess={() => console.log("Success!")}
2388
- particleCount="high"
2389
- >
2390
- High Particles
2391
- </ParticleButton>
2392
- </div>
2393
- </div>
2394
-
2395
- <div>
2396
- <h2 className="text-2xl font-bold mb-4">Rating Component</h2>
2397
- <div className="flex justify-center flex-col items-center gap-4">
2398
- <Rating
2399
- value={rating}
2400
- onValueChange={(value) => setRating(value)}
2401
- >
2402
- {Array.from({ length: 5 }).map((_, index) => (
2403
- <RatingButton key={index} />
2404
- ))}
2405
- </Rating>
2406
- <p className="text-sm text-muted-foreground">
2407
- Current rating: {rating} stars
2408
- </p>
2409
- </div>
2410
- </div>
2411
-
2412
- <div>
2413
- <h2 className="text-2xl font-bold mb-4">Ripple Button</h2>
2414
- <div className="flex justify-center gap-4">
2415
- <RippleButton>Click Me</RippleButton>
2416
- <RippleButton variant="outline">Ripple Effect</RippleButton>
2417
- <RippleButton variant="secondary">Secondary Ripple</RippleButton>
2418
- </div>
2419
- </div>
2420
-
2421
- <div>
2422
- <h2 className="text-2xl font-bold mb-4">Social Button</h2>
2423
- <div className="flex justify-center gap-4">
2424
- <SocialButton variant="light" />
2425
- <SocialButton variant="dark" />
2426
- </div>
2427
- </div>
2428
-
2429
- <div>
2430
- <h2 className="text-2xl font-bold mb-4">Split Button</h2>
2431
- <div className="flex justify-center">
2432
- <SplitButton
2433
- onClick={() => alert("Primary action")}
2434
- items={[
2435
- { label: "Action 1", command: () => alert("Action 1") },
2436
- { label: "Action 2", command: () => alert("Action 2") },
2437
- { separator: true },
2438
- { label: "Delete", command: () => alert("Delete") },
2439
- ]}
2440
- >
2441
- Primary Action
2442
- </SplitButton>
2443
- </div>
2444
- </div>
2445
-
2446
- <div>
2447
- <h2 className="text-2xl font-bold mb-4">Switch Button</h2>
2448
- <div className="flex justify-center gap-4 flex-col items-center">
2449
- <SwitchButton
2450
- checked={switchChecked}
2451
- onCheckedChange={setSwitchChecked}
2452
- labels={{ checked: "ON", unchecked: "OFF" }}
2453
- />
2454
- <p className="text-sm text-muted-foreground">
2455
- Switch is: {switchChecked ? "ON" : "OFF"}
2456
- </p>
2457
- </div>
2458
- </div>
2459
-
2460
- <div>
2461
- <h2 className="text-2xl font-bold mb-4">Toggle Button</h2>
2462
- <div className="flex justify-center gap-4 flex-col items-center">
2463
- <ToggleButton
2464
- checked={toggleChecked}
2465
- onChange={setToggleChecked}
2466
- >
2467
- Toggle Me
2468
- </ToggleButton>
2469
- <p className="text-sm text-muted-foreground">
2470
- Toggle is: {toggleChecked ? "ON" : "OFF"}
2471
- </p>
2472
- </div>
2473
- </div>
2474
-
2475
- <div>
2476
- <h2 className="text-2xl font-bold mb-4">Slide Button</h2>
2477
- <div className="flex justify-center gap-4">
2478
- <SlideButton text="Hover Me" hoverText="Slide Effect!" />
2479
- <SlideButton text="Components" hoverText="Browse Now!" variant="ghost" />
2480
- </div>
2481
- </div>
2482
-
2483
- <div>
2484
- <h2 className="text-2xl font-bold mb-4">Text Reveal Button</h2>
2485
- <div className="flex justify-center">
2486
- <TextRevealButton text="HOVER ME" />
2487
- </div>
2488
- </div>
2489
-
2490
- <div>
2491
- <h2 className="text-2xl font-bold mb-4">Shimmer Button</h2>
2492
- <div className="flex justify-center gap-4">
2493
- <ShimmerButton>Shimmer Effect</ShimmerButton>
2494
- <ShimmerButton shimmerColor="#00ff00">Green Shimmer</ShimmerButton>
2495
- </div>
2496
- </div>
2497
-
2498
- <div>
2499
- <h2 className="text-2xl font-bold mb-4">Tooltip Button</h2>
2500
- <div className="flex justify-center gap-4">
2501
- <TooltipButton
2502
- icon={<MousePointerClick className="h-4 w-4" />}
2503
- content="Click me!"
2504
- onClick={() => alert("Clicked!")}
2505
- />
2506
- <TooltipButton
2507
- icon={<Download className="h-4 w-4" />}
2508
- content="Download"
2509
- onClick={() => alert("Download clicked!")}
2510
- />
2511
- </div>
2512
- </div>
2513
-
2514
- <div>
2515
- <h2 className="text-2xl font-bold mb-4">Button Effects</h2>
2516
- <div className="flex gap-4 flex-wrap items-center">
2517
- <Button effect="ringHover">Ring Hover</Button>
2518
- <Button effect="shine">Shine</Button>
2519
- <Button effect="shineHover">Shine Hover</Button>
2520
- <Button effect="gooeyRight">Gooey Right</Button>
2521
- <Button effect="gooeyLeft">Gooey Left</Button>
2522
- <Button effect="hoverUnderline">Hover Underline</Button>
2523
- </div>
2524
- </div>
2525
-
2526
- <div>
2527
- <h2 className="text-2xl font-bold mb-4">Button Sizes</h2>
2528
- <div className="flex gap-4 flex-wrap items-center">
2529
- <Button size="sm">Small</Button>
2530
- <Button size="default">Default</Button>
2531
- <Button size="lg">Large</Button>
2532
- <Button size="icon">
2533
- <MousePointerClick className="h-4 w-4" />
2534
- </Button>
2535
- </div>
2536
- </div>
2537
-
2538
- <div>
2539
- <h2 className="text-2xl font-bold mb-4">Advanced Variants</h2>
2540
- <div className="flex gap-4 flex-wrap items-center">
2541
- <Button variant="elevated">Elevated</Button>
2542
- <Button variant="filled">Filled</Button>
2543
- <Button variant="tonal">Tonal</Button>
2544
- <Button variant="soft">Soft</Button>
2545
- <Button variant="glass">Glass</Button>
2546
- <Button variant="success">Success</Button>
2547
- <Button variant="warning">Warning</Button>
2548
- <Button variant="info">Info</Button>
2549
- <Button variant="shine">Shine</Button>
2550
- <Button variant="pulse">Pulse</Button>
2551
- </div>
2552
- </div>
2553
- </div>
2554
- )
2555
- }
2556
-
2557
-
2558
-
2559
- const rippleVariants = cva('absolute rounded-full size-5 pointer-events-none', {
2560
- variants: {
2561
- variant: {
2562
- default: 'bg-primary-foreground',
2563
- destructive: 'bg-destructive',
2564
- outline: 'bg-input',
2565
- secondary: 'bg-secondary',
2566
- ghost: 'bg-accent',
2567
- },
2568
- },
2569
- defaultVariants: {
2570
- variant: 'default',
2571
- },
2572
- });
2573
- interface Particle {
2574
- id: number
2575
- x: number
2576
- y: number
2577
- }
2578
- type FlipDirection = 'top' | 'bottom' | 'left' | 'right';
2579
-
2580
- type FlipButtonProps = HTMLMotionProps<'button'> & {
2581
- frontText: string;
2582
- backText: string;
2583
- transition?: Transition;
2584
- frontClassName?: string;
2585
- backClassName?: string;
2586
- from?: FlipDirection;
2587
- };
2588
- type InputButtonContextType = {
2589
- showInput: boolean;
2590
- setShowInput: React.Dispatch<React.SetStateAction<boolean>>;
2591
- transition: Transition;
2592
- id: string;
2593
- };
2594
- type InputButtonProviderProps = React.ComponentProps<'div'> &
2595
- Partial<InputButtonContextType>;
2596
- interface AttractButtonProps
2597
- extends Omit<React.ComponentProps<"button">, "children">,
2598
- VariantProps<typeof attractButtonVariants> {
2599
- particleCount?: number
2600
- attractRadius?: number
2601
- icon?: React.ReactNode
2602
- activeText?: string
2603
- inactiveText?: string
2604
- }
2605
- type CopyButtonProps = Omit<HTMLMotionProps<'button'>, 'children' | 'onCopy'> &
2606
- VariantProps<typeof buttonVariants> & {
2607
- content?: string;
2608
- delay?: number;
2609
- onCopy?: (content: string) => void;
2610
- isCopied?: boolean;
2611
- onCopyChange?: (isCopied: boolean) => void;
2612
- };
2613
-
2614
- interface FloatButtonProps {
2615
- icon?: React.ReactNode
2616
- description?: string
2617
- tooltip?: string
2618
- type?: "default" | "primary"
2619
- shape?: "circle" | "square"
2620
- size?: "small" | "default" | "large"
2621
- className?: string
2622
- onClick?: () => void
2623
- children?: React.ReactNode
2624
- }
2625
- type InputButtonInputProps = React.ComponentProps<'input'>;
2626
- type InputButtonSubmitProps = HTMLMotionProps<'button'> & {
2627
- icon?: React.ElementType;
2628
- };
2629
- type InputButtonProps = HTMLMotionProps<'div'>;
2630
- type InputButtonActionProps = HTMLMotionProps<'button'>;
2631
- type LiquidButtonProps = HTMLMotionProps<'button'> &
2632
- VariantProps<typeof buttonVariants>;
2633
- interface ParticleButtonProps
2634
- extends Omit<React.ComponentProps<"button">, "onClick">,
2635
- VariantProps<typeof buttonVariants> {
2636
- onSuccess?: () => void
2637
- successDuration?: number
2638
- particleCount?: number
2639
- showIcon?: boolean
2640
- icon?: React.ReactNode
2641
- }
2642
- interface SuccessParticlesProps {
2643
- buttonRef: React.RefObject<HTMLButtonElement>
2644
- particleCount: number
2645
- }
2646
- type RatingButtonProps = LucideProps & {
2647
- index?: number;
2648
- icon?: ReactElement<LucideProps>;
2649
- };
2650
- type RatingProps = {
2651
- defaultValue?: number;
2652
- value?: number;
2653
- onChange?: (
2654
- event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,
2655
- value: number
2656
- ) => void;
2657
- onValueChange?: (value: number) => void;
2658
- readOnly?: boolean;
2659
- className?: string;
2660
- children?: ReactNode;
2661
- };
2662
-
2663
- type Ripple = {
2664
- id: number;
2665
- x: number;
2666
- y: number;
2667
- };
2668
-
2669
- type RippleButtonProps = HTMLMotionProps<'button'> & {
2670
- children: React.ReactNode;
2671
- rippleClassName?: string;
2672
- scale?: number;
2673
- transition?: Transition;
2674
- } & VariantProps<typeof buttonVariants>;
2675
- interface ShimmerButtonProps extends ComponentPropsWithoutRef<"button"> {
2676
- shimmerColor?: string
2677
- shimmerSize?: string
2678
- borderRadius?: string
2679
- shimmerDuration?: string
2680
- background?: string
2681
- className?: string
2682
- children?: React.ReactNode
2683
- }
2684
- const slideTextButtonVariants = cva(
2685
- "group relative inline-flex items-center justify-center overflow-hidden rounded-lg font-medium text-md tracking-tighter transition-all duration-300 outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50",
2686
- {
2687
- variants: {
2688
- variant: {
2689
- default:
2690
- "bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90",
2691
- ghost:
2692
- "border border-black/10 text-black hover:bg-black/5 dark:border-white/10 dark:text-white dark:hover:bg-white/5",
2693
- },
2694
- size: {
2695
- sm: "h-8 px-6 text-sm min-w-[180px]",
2696
- default: "h-10 px-8 md:min-w-56",
2697
- lg: "h-12 px-10 text-lg min-w-[280px]",
2698
- },
2699
- },
2700
- defaultVariants: {
2701
- variant: "default",
2702
- size: "default",
2703
- },
2704
- }
2705
- )
2706
- const socialButtonVariants = cva(
2707
- "min-w-40 relative transition-colors duration-200",
2708
- {
2709
- variants: {
2710
- variant: {
2711
- light: "bg-white dark:bg-black hover:bg-gray-50 dark:hover:bg-gray-950 text-black dark:text-white border border-black/10 dark:border-white/10",
2712
- dark: "bg-black dark:bg-white hover:bg-gray-900 dark:hover:bg-gray-100 text-white dark:text-black border border-white/10 dark:border-black/10",
2713
- outline: "bg-transparent hover:bg-accent text-foreground border border-border",
2714
- },
2715
- },
2716
- defaultVariants: {
2717
- variant: "light",
2718
- },
2719
- }
2720
- )
2721
-
2722
- interface ShareButton {
2723
- icon: React.ComponentType<{ className?: string }>
2724
- label: string
2725
- onClick?: () => void
2726
- }
2727
-
2728
- interface SocialButtonProps
2729
- extends Omit<React.ComponentProps<"button">, "children">,
2730
- VariantProps<typeof socialButtonVariants> {
2731
- shareButtons?: ShareButton[]
2732
- label?: string
2733
- icon?: React.ReactNode
2734
- }
2735
- interface SplitButtonItem {
2736
- label: string;
2737
- icon?: React.ReactNode;
2738
- command?: () => void;
2739
- separator?: boolean;
2740
- }
2741
-
2742
- interface SplitButtonProps {
2743
- label: string;
2744
- icon?: React.ReactNode;
2745
- items: SplitButtonItem[];
2746
- onClick?: () => void;
2747
- className?: string;
2748
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost";
2749
- size?: "default" | "sm" | "lg" | "icon";
2750
- disabled?: boolean;
2751
- align?: "left" | "middle" | "right"
2752
- }
2753
- interface SwitchButtonProps {
2754
- checked?: boolean;
2755
- onCheckedChange?: (checked: boolean) => void;
2756
- className?: string;
2757
- variant?: 'default' | 'primary' | 'secondary' | 'destructive';
2758
- size?: 'sm' | 'md' | 'lg';
2759
- disabled?: boolean;
2760
- labels?: {
2761
- checked: string;
2762
- unchecked: string;
2763
- };
2764
- }
2765
- interface TextRevealButtonProps
2766
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
2767
- text?: string;
2768
- revealColor?: string;
2769
- strokeColor?: string;
2770
- }
2771
- interface ButtonToggleProps {
2772
- checked?: boolean
2773
- onChange?: (checked: boolean) => void
2774
- disabled?: boolean
2775
- className?: string
2776
- variant?: "default" | "outline" | "ghost"
2777
- size?: "sm" | "md" | "lg" | "icon"
2778
- children?: React.ReactNode
2779
- }
2780
- const toggleButtonVariants = cva(
2781
- "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
2782
- {
2783
- variants: {
2784
- variant: {
2785
- default: "",
2786
- outline: "",
2787
- ghost: "",
2788
- },
2789
- size: {
2790
- sm: "h-8 px-3 text-xs",
2791
- md: "h-10 px-4 py-2 text-sm",
2792
- lg: "h-12 px-6 text-base",
2793
- },
2794
- checked: {
2795
- true: "",
2796
- false: "",
2797
- },
2798
- },
2799
- compoundVariants: [
2800
- // Default variant - checked state
2801
- {
2802
- variant: "default",
2803
- checked: true,
2804
- class: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2805
- },
2806
- // Default variant - unchecked state
2807
- {
2808
- variant: "default",
2809
- checked: false,
2810
- class: "bg-background text-foreground border border-input shadow-sm hover:bg-accent hover:text-accent-foreground",
2811
- },
2812
- // Outline variant - checked state
2813
- {
2814
- variant: "outline",
2815
- checked: true,
2816
- class: "border border-primary bg-background text-primary shadow-sm hover:bg-accent",
2817
- },
2818
- // Outline variant - unchecked state
2819
- {
2820
- variant: "outline",
2821
- checked: false,
2822
- class: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2823
- },
2824
- // Ghost variant - checked state
2825
- {
2826
- variant: "ghost",
2827
- checked: true,
2828
- class: "bg-accent text-accent-foreground hover:bg-accent/80",
2829
- },
2830
- // Ghost variant - unchecked state
2831
- {
2832
- variant: "ghost",
2833
- checked: false,
2834
- class: "hover:bg-accent hover:text-accent-foreground",
2835
- },
2836
- ],
2837
- defaultVariants: {
2838
- variant: "default",
2839
- size: "md",
2840
- checked: false,
2841
- },
2842
- }
2843
- )
2844
-
2845
- type RatingContextValue = {
2846
- value: number;
2847
- readOnly: boolean;
2848
- hoverValue: number | null;
2849
- focusedStar: number | null;
2850
- handleValueChange: (
2851
- event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>,
2852
- value: number
2853
- ) => void;
2854
- handleKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
2855
- setHoverValue: (value: number | null) => void;
2856
- setFocusedStar: (value: number | null) => void;
2857
- };