@codefast/ui 0.3.16-canary.2 → 0.4.0-canary.4

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 (289) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +28 -17
  3. package/dist/components/accordion.d.mts +7 -22
  4. package/dist/components/accordion.mjs +26 -29
  5. package/dist/components/alert-dialog.d.mts +27 -26
  6. package/dist/components/alert-dialog.mjs +53 -45
  7. package/dist/components/alert.d.mts +14 -14
  8. package/dist/components/alert.mjs +17 -28
  9. package/dist/components/aspect-ratio.d.mts +2 -2
  10. package/dist/components/aspect-ratio.mjs +2 -3
  11. package/dist/components/avatar.d.mts +41 -5
  12. package/dist/components/avatar.mjs +40 -10
  13. package/dist/components/badge.d.mts +3 -15
  14. package/dist/components/badge.mjs +6 -48
  15. package/dist/components/breadcrumb.d.mts +1 -0
  16. package/dist/components/breadcrumb.mjs +11 -10
  17. package/dist/components/button-group.d.mts +3 -13
  18. package/dist/components/button-group.mjs +9 -31
  19. package/dist/components/button.d.mts +3 -26
  20. package/dist/components/button.mjs +9 -79
  21. package/dist/components/calendar.d.mts +6 -2
  22. package/dist/components/calendar.mjs +41 -44
  23. package/dist/components/card.d.mts +4 -2
  24. package/dist/components/card.mjs +9 -9
  25. package/dist/components/carousel.d.mts +16 -5
  26. package/dist/components/carousel.mjs +24 -11
  27. package/dist/components/chart.d.mts +9 -6
  28. package/dist/components/chart.mjs +21 -15
  29. package/dist/components/checkbox-cards.mjs +4 -4
  30. package/dist/components/checkbox-group.mjs +3 -4
  31. package/dist/components/checkbox.d.mts +2 -2
  32. package/dist/components/checkbox.mjs +6 -7
  33. package/dist/components/collapsible.d.mts +4 -4
  34. package/dist/components/collapsible.mjs +4 -5
  35. package/dist/components/command.d.mts +11 -1
  36. package/dist/components/command.mjs +35 -32
  37. package/dist/components/context-menu.d.mts +22 -15
  38. package/dist/components/context-menu.mjs +44 -39
  39. package/dist/components/dialog.d.mts +19 -23
  40. package/dist/components/dialog.mjs +48 -47
  41. package/dist/components/direction.d.mts +24 -0
  42. package/dist/components/direction.mjs +18 -0
  43. package/dist/components/drawer.d.mts +3 -21
  44. package/dist/components/drawer.mjs +19 -27
  45. package/dist/components/dropdown-menu.d.mts +22 -15
  46. package/dist/components/dropdown-menu.mjs +41 -37
  47. package/dist/components/empty.d.mts +3 -13
  48. package/dist/components/empty.mjs +8 -23
  49. package/dist/components/field.d.mts +3 -14
  50. package/dist/components/field.mjs +14 -44
  51. package/dist/components/form.d.mts +7 -10
  52. package/dist/components/form.mjs +6 -7
  53. package/dist/components/hover-card.d.mts +5 -5
  54. package/dist/components/hover-card.mjs +14 -12
  55. package/dist/components/input-group.d.mts +4 -31
  56. package/dist/components/input-group.mjs +14 -96
  57. package/dist/components/input-number.d.mts +3 -1
  58. package/dist/components/input-number.mjs +50 -28
  59. package/dist/components/input-otp.mjs +9 -7
  60. package/dist/components/input-password.mjs +1 -4
  61. package/dist/components/input-search.mjs +3 -5
  62. package/dist/components/input.mjs +1 -2
  63. package/dist/components/item.d.mts +4 -29
  64. package/dist/components/item.mjs +12 -65
  65. package/dist/components/kbd.mjs +1 -1
  66. package/dist/components/label.d.mts +2 -2
  67. package/dist/components/label.mjs +3 -4
  68. package/dist/components/menubar.d.mts +22 -16
  69. package/dist/components/menubar.mjs +54 -47
  70. package/dist/components/native-select.d.mts +5 -1
  71. package/dist/components/native-select.mjs +9 -6
  72. package/dist/components/navigation-menu.d.mts +30 -13
  73. package/dist/components/navigation-menu.mjs +35 -32
  74. package/dist/components/pagination.d.mts +7 -1
  75. package/dist/components/pagination.mjs +27 -12
  76. package/dist/components/popover.d.mts +40 -7
  77. package/dist/components/popover.mjs +46 -14
  78. package/dist/components/progress-circle.d.mts +3 -47
  79. package/dist/components/progress-circle.mjs +2 -48
  80. package/dist/components/progress.d.mts +2 -2
  81. package/dist/components/progress.mjs +5 -6
  82. package/dist/components/radio-cards.d.mts +3 -3
  83. package/dist/components/radio-cards.mjs +11 -11
  84. package/dist/components/radio-group.d.mts +3 -3
  85. package/dist/components/radio-group.mjs +9 -9
  86. package/dist/components/radio.mjs +2 -3
  87. package/dist/components/resizable.mjs +3 -8
  88. package/dist/components/scroll-area.d.mts +8 -24
  89. package/dist/components/scroll-area.mjs +16 -70
  90. package/dist/components/select.d.mts +14 -14
  91. package/dist/components/select.mjs +47 -47
  92. package/dist/components/separator.d.mts +4 -19
  93. package/dist/components/separator.mjs +6 -27
  94. package/dist/components/sheet.d.mts +18 -31
  95. package/dist/components/sheet.mjs +46 -87
  96. package/dist/components/sidebar.d.mts +3 -19
  97. package/dist/components/sidebar.mjs +48 -84
  98. package/dist/components/skeleton.mjs +1 -1
  99. package/dist/components/slider.d.mts +2 -2
  100. package/dist/components/slider.mjs +9 -11
  101. package/dist/components/sonner.mjs +11 -3
  102. package/dist/components/spinner.mjs +6 -7
  103. package/dist/components/switch.d.mts +5 -2
  104. package/dist/components/switch.mjs +7 -7
  105. package/dist/components/table.mjs +10 -10
  106. package/dist/components/tabs.d.mts +8 -5
  107. package/dist/components/tabs.mjs +18 -12
  108. package/dist/components/textarea.mjs +1 -1
  109. package/dist/components/toggle-group.d.mts +11 -7
  110. package/dist/components/toggle-group.mjs +20 -21
  111. package/dist/components/toggle.d.mts +4 -24
  112. package/dist/components/toggle.mjs +6 -45
  113. package/dist/components/tooltip.d.mts +7 -6
  114. package/dist/components/tooltip.mjs +19 -17
  115. package/dist/hooks/use-animated-value.mjs +0 -1
  116. package/dist/hooks/use-copy-to-clipboard.mjs +0 -1
  117. package/dist/hooks/use-is-mobile.mjs +0 -1
  118. package/dist/hooks/use-media-query.mjs +0 -1
  119. package/dist/hooks/use-mutation-observer.mjs +0 -1
  120. package/dist/hooks/use-pagination.mjs +0 -1
  121. package/dist/index.d.mts +38 -21
  122. package/dist/index.mjs +40 -23
  123. package/dist/lib/utils.d.mts +1 -12
  124. package/dist/lib/utils.mjs +1 -9
  125. package/dist/primitives/checkbox-group.d.mts +9 -11
  126. package/dist/primitives/checkbox-group.mjs +14 -19
  127. package/dist/primitives/input-number.d.mts +3 -4
  128. package/dist/primitives/input-number.mjs +3 -5
  129. package/dist/primitives/input.d.mts +4 -5
  130. package/dist/primitives/input.mjs +2 -3
  131. package/dist/primitives/progress-circle.d.mts +3 -4
  132. package/dist/primitives/progress-circle.mjs +2 -3
  133. package/dist/variants/alert.d.mts +18 -0
  134. package/dist/variants/alert.mjs +15 -0
  135. package/dist/variants/badge.d.mts +22 -0
  136. package/dist/variants/badge.mjs +19 -0
  137. package/dist/variants/button-group.d.mts +18 -0
  138. package/dist/variants/button-group.mjs +15 -0
  139. package/dist/variants/button.d.mts +32 -0
  140. package/dist/variants/button.mjs +34 -0
  141. package/dist/variants/empty.d.mts +18 -0
  142. package/dist/variants/empty.mjs +15 -0
  143. package/dist/variants/field.d.mts +19 -0
  144. package/dist/variants/field.mjs +16 -0
  145. package/dist/variants/input-group.d.mts +43 -0
  146. package/dist/variants/input-group.mjs +34 -0
  147. package/dist/variants/input-number.d.mts +45 -0
  148. package/dist/variants/input-number.mjs +40 -0
  149. package/dist/variants/item.d.mts +38 -0
  150. package/dist/variants/item.mjs +38 -0
  151. package/dist/variants/navigation-menu.d.mts +13 -0
  152. package/dist/variants/navigation-menu.mjs +8 -0
  153. package/dist/variants/progress-circle.d.mts +52 -0
  154. package/dist/variants/progress-circle.mjs +45 -0
  155. package/dist/variants/scroll-area.d.mts +24 -0
  156. package/dist/variants/scroll-area.mjs +58 -0
  157. package/dist/variants/separator.d.mts +23 -0
  158. package/dist/variants/separator.mjs +25 -0
  159. package/dist/variants/sheet.d.mts +20 -0
  160. package/dist/variants/sheet.mjs +17 -0
  161. package/dist/variants/sidebar.d.mts +23 -0
  162. package/dist/variants/sidebar.mjs +25 -0
  163. package/dist/variants/tabs.d.mts +18 -0
  164. package/dist/variants/tabs.mjs +15 -0
  165. package/dist/variants/toggle.d.mts +23 -0
  166. package/dist/variants/toggle.mjs +25 -0
  167. package/package.json +186 -55
  168. package/src/components/accordion.tsx +114 -0
  169. package/src/components/alert-dialog.tsx +298 -0
  170. package/src/components/alert.tsx +94 -0
  171. package/src/components/aspect-ratio.tsx +25 -0
  172. package/src/components/avatar.tsx +171 -0
  173. package/src/components/badge.tsx +35 -0
  174. package/src/components/breadcrumb.tsx +191 -0
  175. package/src/components/button-group.tsx +97 -0
  176. package/src/components/button.tsx +55 -0
  177. package/src/components/calendar.tsx +222 -0
  178. package/src/components/card.tsx +169 -0
  179. package/src/components/carousel.tsx +349 -0
  180. package/src/components/chart.tsx +536 -0
  181. package/src/components/checkbox-cards.tsx +72 -0
  182. package/src/components/checkbox-group.tsx +60 -0
  183. package/src/components/checkbox.tsx +44 -0
  184. package/src/components/collapsible.tsx +57 -0
  185. package/src/components/command.tsx +298 -0
  186. package/src/components/context-menu.tsx +410 -0
  187. package/src/components/dialog.tsx +243 -0
  188. package/src/components/direction.tsx +32 -0
  189. package/src/components/drawer.tsx +209 -0
  190. package/src/components/dropdown-menu.tsx +419 -0
  191. package/src/components/empty.tsx +155 -0
  192. package/src/components/field.tsx +329 -0
  193. package/src/components/form.tsx +258 -0
  194. package/src/components/hover-card.tsx +93 -0
  195. package/src/components/input-group.tsx +185 -0
  196. package/src/components/input-number.tsx +141 -0
  197. package/src/components/input-otp.tsx +132 -0
  198. package/src/components/input-password.tsx +50 -0
  199. package/src/components/input-search.tsx +81 -0
  200. package/src/components/input.tsx +36 -0
  201. package/src/components/item.tsx +266 -0
  202. package/src/components/kbd.tsx +47 -0
  203. package/src/components/label.tsx +36 -0
  204. package/src/components/menubar.tsx +440 -0
  205. package/src/components/native-select.tsx +87 -0
  206. package/src/components/navigation-menu.tsx +235 -0
  207. package/src/components/pagination.tsx +198 -0
  208. package/src/components/popover.tsx +170 -0
  209. package/src/components/progress-circle.tsx +185 -0
  210. package/src/components/progress.tsx +41 -0
  211. package/src/components/radio-cards.tsx +66 -0
  212. package/src/components/radio-group.tsx +59 -0
  213. package/src/components/radio.tsx +40 -0
  214. package/src/components/resizable.tsx +78 -0
  215. package/src/components/scroll-area.tsx +95 -0
  216. package/src/components/select.tsx +296 -0
  217. package/src/components/separator.tsx +60 -0
  218. package/src/components/sheet.tsx +241 -0
  219. package/src/components/sidebar.tsx +926 -0
  220. package/src/components/skeleton.tsx +35 -0
  221. package/src/components/slider.tsx +66 -0
  222. package/src/components/sonner.tsx +57 -0
  223. package/src/components/spinner.tsx +66 -0
  224. package/src/components/switch.tsx +44 -0
  225. package/src/components/table.tsx +183 -0
  226. package/src/components/tabs.tsx +110 -0
  227. package/src/components/textarea.tsx +35 -0
  228. package/src/components/toggle-group.tsx +137 -0
  229. package/src/components/toggle.tsx +30 -0
  230. package/src/components/tooltip.tsx +115 -0
  231. package/src/css/foundation/base.css +50 -0
  232. package/src/css/foundation/motion.css +36 -0
  233. package/src/css/foundation/source.css +3 -0
  234. package/src/css/foundation/tokens.css +71 -0
  235. package/src/css/foundation/variants.css +113 -0
  236. package/src/css/preset.css +5 -195
  237. package/src/css/style.css +1 -1
  238. package/src/css/{amber.css → themes/amber.css} +59 -22
  239. package/src/css/{blue.css → themes/blue.css} +59 -22
  240. package/src/css/{cyan.css → themes/cyan.css} +59 -22
  241. package/src/css/{emerald.css → themes/emerald.css} +59 -22
  242. package/src/css/{fuchsia.css → themes/fuchsia.css} +59 -22
  243. package/src/css/{gray.css → themes/gray.css} +59 -22
  244. package/src/css/{green.css → themes/green.css} +59 -22
  245. package/src/css/{indigo.css → themes/indigo.css} +59 -22
  246. package/src/css/{lime.css → themes/lime.css} +59 -22
  247. package/src/css/{neutral.css → themes/neutral.css} +59 -22
  248. package/src/css/{orange.css → themes/orange.css} +59 -22
  249. package/src/css/{pink.css → themes/pink.css} +59 -22
  250. package/src/css/{purple.css → themes/purple.css} +59 -22
  251. package/src/css/{red.css → themes/red.css} +59 -22
  252. package/src/css/{rose.css → themes/rose.css} +59 -22
  253. package/src/css/{sky.css → themes/sky.css} +59 -22
  254. package/src/css/{slate.css → themes/slate.css} +59 -22
  255. package/src/css/{stone.css → themes/stone.css} +59 -22
  256. package/src/css/{teal.css → themes/teal.css} +59 -22
  257. package/src/css/{violet.css → themes/violet.css} +59 -22
  258. package/src/css/{yellow.css → themes/yellow.css} +59 -22
  259. package/src/css/{zinc.css → themes/zinc.css} +59 -22
  260. package/src/hooks/use-animated-value.ts +91 -0
  261. package/src/hooks/use-copy-to-clipboard.ts +58 -0
  262. package/src/hooks/use-is-mobile.ts +25 -0
  263. package/src/hooks/use-media-query.ts +69 -0
  264. package/src/hooks/use-mutation-observer.ts +51 -0
  265. package/src/hooks/use-pagination.ts +164 -0
  266. package/src/index.ts +679 -0
  267. package/src/lib/utils.ts +5 -0
  268. package/src/primitives/checkbox-group.tsx +346 -0
  269. package/src/primitives/input-number.tsx +967 -0
  270. package/src/primitives/input.tsx +227 -0
  271. package/src/primitives/progress-circle.tsx +507 -0
  272. package/src/variants/alert.ts +34 -0
  273. package/src/variants/badge.ts +39 -0
  274. package/src/variants/button-group.ts +36 -0
  275. package/src/variants/button.ts +56 -0
  276. package/src/variants/empty.ts +34 -0
  277. package/src/variants/field.ts +37 -0
  278. package/src/variants/input-group.ts +80 -0
  279. package/src/variants/input-number.ts +65 -0
  280. package/src/variants/item.ts +68 -0
  281. package/src/variants/navigation-menu.ts +25 -0
  282. package/src/variants/progress-circle.ts +46 -0
  283. package/src/variants/scroll-area.ts +73 -0
  284. package/src/variants/separator.ts +40 -0
  285. package/src/variants/sheet.ts +37 -0
  286. package/src/variants/sidebar.ts +41 -0
  287. package/src/variants/tabs.ts +34 -0
  288. package/src/variants/toggle.ts +40 -0
  289. package/dist/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/clsx.d.mts +0 -6
@@ -0,0 +1,507 @@
1
+ import { Context } from "radix-ui/internal";
2
+ import type { ComponentProps, ReactNode } from "react";
3
+ import { useId, useMemo } from "react";
4
+
5
+ /* -----------------------------------------------------------------------------
6
+ * Context: ProgressCircleProvider
7
+ * --------------------------------------------------------------------------- */
8
+
9
+ const PROGRESS_CIRCLE_PROVIDER_NAME = "ProgressCircleProvider";
10
+
11
+ type ScopedProps<P> = P & { __scopeProgressCircle?: Context.Scope };
12
+
13
+ /**
14
+ * Defines color thresholds based on progress values
15
+ */
16
+ interface Threshold {
17
+ /**
18
+ * Background color to be applied
19
+ */
20
+ background: string;
21
+
22
+ /**
23
+ * Foreground color to be applied
24
+ */
25
+ color: string;
26
+
27
+ /**
28
+ * The value at which this threshold becomes active
29
+ */
30
+ value: number;
31
+ }
32
+
33
+ /**
34
+ * Props for the ProgressCircleProvider context
35
+ */
36
+ interface ProgressCircleContextValue {
37
+ /**
38
+ * Center coordinate of the circle (half of size)
39
+ */
40
+ center: number;
41
+
42
+ /**
43
+ * Total circumference of the circle for stroke calculations
44
+ */
45
+ circumference: number;
46
+
47
+ /**
48
+ * Clamped progress value, undefined for indeterminate state
49
+ */
50
+ clampedValue: number | undefined;
51
+
52
+ /**
53
+ * Unique identifier for the progress circle
54
+ */
55
+ id: string;
56
+
57
+ /**
58
+ * Maximum progress value
59
+ */
60
+ max: number;
61
+
62
+ /**
63
+ * Minimum progress value
64
+ */
65
+ min: number;
66
+
67
+ /**
68
+ * Radius of the circle for SVG rendering
69
+ */
70
+ radius: number;
71
+
72
+ /**
73
+ * CSS transform for rotating the progress indicator
74
+ */
75
+ rotationTransform: string;
76
+
77
+ /**
78
+ * Size of the progress circle in pixels
79
+ */
80
+ size: number;
81
+
82
+ /**
83
+ * Stroke dash offset for progress visualization
84
+ */
85
+ strokeDashoffset: number;
86
+
87
+ /**
88
+ * Width of the stroke for the progress circle
89
+ */
90
+ strokeWidth: number;
91
+
92
+ /**
93
+ * Threshold configuration for color changes
94
+ */
95
+ threshold: Threshold | undefined;
96
+
97
+ /**
98
+ * Raw progress value (can be null/undefined for indeterminate)
99
+ */
100
+ value: null | number | undefined;
101
+
102
+ /**
103
+ * Text representation of the current value for accessibility
104
+ */
105
+ valueText: string;
106
+ }
107
+
108
+ const [createProgressCircleContext, createProgressCircleScope] =
109
+ Context.createContextScope(PROGRESS_CIRCLE_PROVIDER_NAME);
110
+
111
+ const [ProgressCircleContextProvider, useProgressCircleContext] =
112
+ createProgressCircleContext<ProgressCircleContextValue>(PROGRESS_CIRCLE_PROVIDER_NAME);
113
+
114
+ /* -----------------------------------------------------------------------------
115
+ * Component: ProgressCircleProvider
116
+ * --------------------------------------------------------------------------- */
117
+
118
+ /**
119
+ * @since 0.3.16-canary.0
120
+ */
121
+ interface ProgressCircleProviderProps {
122
+ /**
123
+ * React children to be rendered inside the progress circle
124
+ */
125
+ children: ReactNode;
126
+
127
+ /**
128
+ * Custom function to format the numeric value for display
129
+ */
130
+ formatValue?: (value: number) => string;
131
+
132
+ /**
133
+ * Unique identifier for the progress circle component
134
+ */
135
+ id?: string;
136
+
137
+ /**
138
+ * Maximum value of the progress (defaults to 100)
139
+ */
140
+ max?: number;
141
+
142
+ /**
143
+ * Minimum value of the progress (defaults to 0)
144
+ */
145
+ min?: number;
146
+
147
+ /**
148
+ * Size of the progress circle in pixels
149
+ */
150
+ size?: number;
151
+
152
+ /**
153
+ * Starting angle of the progress circle in degrees (0 = top)
154
+ */
155
+ startAngle?: number;
156
+
157
+ /**
158
+ * Width of the progress circle's stroke in pixels
159
+ */
160
+ strokeWidth?: number;
161
+
162
+ /**
163
+ * Array of threshold configurations for different value ranges
164
+ */
165
+ thresholds?: Array<Threshold>;
166
+
167
+ /**
168
+ * Current progress value (null for indeterminate state)
169
+ */
170
+ value?: null | number;
171
+ }
172
+
173
+ /**
174
+ * Provides context for the ProgressCircle component
175
+ *
176
+ * Manages calculations for rendering the circular progress indicator,
177
+ * including value clamping, sizing, thresholds, and indeterminate state.
178
+ *
179
+ * @example
180
+ * ```tsx
181
+ * <ProgressCircleProvider
182
+ * value={75}
183
+ * min={0}
184
+ * max={100}
185
+ * size={64}
186
+ * thresholds={[
187
+ * { value: 30, color: 'red', background: 'pink' },
188
+ * { value: 70, color: 'yellow', background: 'lightyellow' },
189
+ * { value: 100, color: 'green', background: 'lightgreen' }
190
+ * ]}
191
+ * >
192
+ * <ProgressCircleSVG>
193
+ * <ProgressCircleIndicator />
194
+ * <ProgressCircleTrack />
195
+ * </ProgressCircleSVG>
196
+ * <ProgressCircleValue />
197
+ * </ProgressCircleProvider>
198
+ * ```
199
+ *
200
+ * @since 0.3.16-canary.0
201
+ */
202
+ function ProgressCircleProvider({
203
+ __scopeProgressCircle,
204
+ children,
205
+ formatValue,
206
+ id: propertyId,
207
+ max = 100,
208
+ min = 0,
209
+ size = 48,
210
+ startAngle = -90,
211
+ strokeWidth = 4,
212
+ thresholds,
213
+ value,
214
+ }: ScopedProps<ProgressCircleProviderProps>): ReactNode {
215
+ const uniqueId = useId();
216
+ const id = propertyId ?? uniqueId;
217
+
218
+ // Ensure size and stroke width are non-negative
219
+ const validSize = Math.max(0, size);
220
+ const validStrokeWidth = Math.max(0, strokeWidth);
221
+ const validStartAngle = startAngle % 360;
222
+
223
+ // Validate min and max, swap if min > max
224
+ let validMin = min;
225
+ let validMax = max;
226
+
227
+ if (validMin > validMax) {
228
+ [validMin, validMax] = [validMax, validMin];
229
+ }
230
+
231
+ // Handle indeterminate state
232
+ const isIndeterminate = value === null || value === undefined;
233
+ const clampedValue = isIndeterminate ? undefined : clamp(validMin, validMax, value);
234
+ const range = validMax - validMin;
235
+ const percentage = clampedValue !== undefined && range > 0 ? ((clampedValue - validMin) / range) * 100 : 0;
236
+ const valueText =
237
+ clampedValue !== undefined && formatValue ? formatValue(clampedValue) : `${Math.round(percentage).toString()}%`;
238
+
239
+ // Sort thresholds by value
240
+ const sortedThresholds = useMemo(
241
+ () => (thresholds && thresholds.length > 0 ? [...thresholds].toSorted((a, b) => a.value - b.value) : []),
242
+ [thresholds],
243
+ );
244
+
245
+ // Determine an active threshold based on a clamped value
246
+ const threshold = useMemo(() => {
247
+ if (clampedValue === undefined) {
248
+ return;
249
+ }
250
+
251
+ for (const sortedThreshold of sortedThresholds) {
252
+ if (clampedValue <= sortedThreshold.value) {
253
+ return sortedThreshold;
254
+ }
255
+ }
256
+
257
+ return sortedThresholds.at(-1);
258
+ }, [sortedThresholds, clampedValue]);
259
+
260
+ // Calculate circle properties
261
+ const center = validSize / 2;
262
+ const radius = Math.max(0, center - validStrokeWidth / 2);
263
+ const circumference = 2 * Math.PI * radius;
264
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
265
+ const rotationTransform = `rotate(${validStartAngle.toString()}, 0, 0)`;
266
+
267
+ return (
268
+ <ProgressCircleContextProvider
269
+ center={center}
270
+ circumference={circumference}
271
+ clampedValue={clampedValue} // Undefined for indeterminate
272
+ id={id}
273
+ max={validMax}
274
+ min={validMin}
275
+ radius={radius}
276
+ rotationTransform={rotationTransform}
277
+ scope={__scopeProgressCircle}
278
+ size={validSize}
279
+ strokeDashoffset={strokeDashoffset}
280
+ strokeWidth={validStrokeWidth}
281
+ threshold={threshold}
282
+ value={value ?? 0}
283
+ valueText={valueText}
284
+ >
285
+ {children}
286
+ </ProgressCircleContextProvider>
287
+ );
288
+ }
289
+
290
+ /* -----------------------------------------------------------------------------
291
+ * Component: ProgressCircle
292
+ * --------------------------------------------------------------------------- */
293
+
294
+ /**
295
+ * @since 0.3.16-canary.0
296
+ */
297
+ type ProgressCircleProps = ComponentProps<"div">;
298
+
299
+ /**
300
+ * Root component for the progress circle
301
+ *
302
+ * Serves as a wrapper for other progress circle components.
303
+ *
304
+ * @since 0.3.16-canary.0
305
+ */
306
+ function ProgressCircle({ __scopeProgressCircle, ...props }: ScopedProps<ProgressCircleProps>): ReactNode {
307
+ return <div {...props} />;
308
+ }
309
+
310
+ /* -----------------------------------------------------------------------------
311
+ * Component: ProgressCircleSVG
312
+ * --------------------------------------------------------------------------- */
313
+
314
+ const PROGRESS_CIRCLE_SVG_NAME = "ProgressCircleSVG";
315
+
316
+ /**
317
+ * @since 0.3.16-canary.0
318
+ */
319
+ type ProgressCircleSVGProps = ComponentProps<"svg">;
320
+
321
+ /**
322
+ * SVG container for the progress circle
323
+ *
324
+ * Renders the SVG with accessibility attributes and supports indeterminate state.
325
+ *
326
+ * @since 0.3.16-canary.0
327
+ */
328
+ function ProgressCircleSVG({ __scopeProgressCircle, ...props }: ScopedProps<ProgressCircleSVGProps>): ReactNode {
329
+ const { clampedValue, id, max, min, size, valueText } = useProgressCircleContext(
330
+ PROGRESS_CIRCLE_SVG_NAME,
331
+ __scopeProgressCircle,
332
+ );
333
+
334
+ return (
335
+ <svg
336
+ aria-label="Progress"
337
+ aria-valuemax={max}
338
+ aria-valuemin={min}
339
+ aria-valuenow={clampedValue} // Undefined for indeterminate state
340
+ aria-valuetext={clampedValue === undefined ? undefined : valueText}
341
+ height={size}
342
+ id={id}
343
+ role="progressbar"
344
+ viewBox={`0 0 ${size.toString()} ${size.toString()}`}
345
+ width={size}
346
+ {...props}
347
+ />
348
+ );
349
+ }
350
+
351
+ /* -----------------------------------------------------------------------------
352
+ * Component: ProgressCircleTrack
353
+ * --------------------------------------------------------------------------- */
354
+
355
+ const PROGRESS_CIRCLE_TRACK_NAME = "ProgressCircleTrack";
356
+
357
+ /**
358
+ * @since 0.3.16-canary.0
359
+ */
360
+ type ProgressCircleTrackProps = ComponentProps<"circle">;
361
+
362
+ /**
363
+ * Background circle for the progress indicator
364
+ *
365
+ * Renders the static track of the progress circle.
366
+ *
367
+ * @since 0.3.16-canary.0
368
+ */
369
+ function ProgressCircleTrack({ __scopeProgressCircle, ...props }: ScopedProps<ProgressCircleTrackProps>): ReactNode {
370
+ const { center, radius, strokeWidth, threshold } = useProgressCircleContext(
371
+ PROGRESS_CIRCLE_TRACK_NAME,
372
+ __scopeProgressCircle,
373
+ );
374
+
375
+ return (
376
+ <circle
377
+ cx={center}
378
+ cy={center}
379
+ fill="transparent"
380
+ r={radius}
381
+ stroke={threshold?.background ?? "currentColor"}
382
+ strokeWidth={strokeWidth}
383
+ {...props}
384
+ />
385
+ );
386
+ }
387
+
388
+ /* -----------------------------------------------------------------------------
389
+ * Component: ProgressCircleIndicator
390
+ * --------------------------------------------------------------------------- */
391
+
392
+ const PROGRESS_CIRCLE_INDICATOR_NAME = "ProgressCircleIndicator";
393
+
394
+ /**
395
+ * @since 0.3.16-canary.0
396
+ */
397
+ type ProgressCircleIndicatorProps = ComponentProps<"circle">;
398
+
399
+ /**
400
+ * Foreground circle showing progress
401
+ *
402
+ * Renders the dynamic progress indicator with stroke dash properties.
403
+ *
404
+ * @since 0.3.16-canary.0
405
+ */
406
+ function ProgressCircleIndicator({
407
+ __scopeProgressCircle,
408
+ ...props
409
+ }: ScopedProps<ProgressCircleIndicatorProps>): ReactNode {
410
+ const { center, circumference, radius, rotationTransform, strokeDashoffset, strokeWidth, threshold } =
411
+ useProgressCircleContext(PROGRESS_CIRCLE_INDICATOR_NAME, __scopeProgressCircle);
412
+
413
+ return (
414
+ <circle
415
+ cx={center}
416
+ cy={center}
417
+ fill="transparent"
418
+ r={radius}
419
+ stroke={threshold?.color ?? "currentColor"}
420
+ strokeDasharray={circumference}
421
+ strokeDashoffset={strokeDashoffset}
422
+ strokeLinecap="round"
423
+ strokeWidth={strokeWidth}
424
+ transform={rotationTransform}
425
+ {...props}
426
+ />
427
+ );
428
+ }
429
+
430
+ /* -----------------------------------------------------------------------------
431
+ * Component: ProgressCircleValue
432
+ * --------------------------------------------------------------------------- */
433
+
434
+ const PROGRESS_CIRCLE_VALUE_NAME = "ProgressCircleValue";
435
+
436
+ /**
437
+ * @since 0.3.16-canary.0
438
+ */
439
+ interface ProgressCircleValueProps extends Omit<ComponentProps<"div">, "children"> {
440
+ children?: ((context: { value: number | undefined; valueText: string }) => ReactNode) | ReactNode;
441
+ }
442
+
443
+ /**
444
+ * Displays the current progress value
445
+ *
446
+ * Supports custom content or default value text rendering.
447
+ *
448
+ * @since 0.3.16-canary.0
449
+ */
450
+ function ProgressCircleValue({
451
+ __scopeProgressCircle,
452
+ children,
453
+ ...props
454
+ }: ScopedProps<ProgressCircleValueProps>): ReactNode {
455
+ const { clampedValue, valueText } = useProgressCircleContext(PROGRESS_CIRCLE_VALUE_NAME, __scopeProgressCircle);
456
+
457
+ if (typeof children === "function") {
458
+ return children({ value: clampedValue, valueText });
459
+ }
460
+
461
+ return <div {...props}>{children ?? valueText}</div>;
462
+ }
463
+
464
+ /* -----------------------------------------------------------------------------
465
+ * Helpers
466
+ * -------------------------------------------------------------------------- */
467
+
468
+ /**
469
+ * Clamps a value within a specified min/max range
470
+ *
471
+ * @param min - Minimum value
472
+ * @param max - Maximum value
473
+ * @param value - Value to clamp
474
+ * @returns Clamped value
475
+ */
476
+ function clamp(min: number, max: number, value: number): number {
477
+ return Math.min(max, Math.max(min, value));
478
+ }
479
+
480
+ /* -----------------------------------------------------------------------------
481
+ * Exports
482
+ * -------------------------------------------------------------------------- */
483
+
484
+ export {
485
+ createProgressCircleScope,
486
+ ProgressCircleIndicator as Indicator,
487
+ ProgressCircle,
488
+ ProgressCircleIndicator,
489
+ ProgressCircleProvider,
490
+ ProgressCircleSVG,
491
+ ProgressCircleTrack,
492
+ ProgressCircleValue,
493
+ ProgressCircleProvider as Provider,
494
+ ProgressCircle as Root,
495
+ ProgressCircleSVG as SVG,
496
+ ProgressCircleTrack as Track,
497
+ ProgressCircleValue as Value,
498
+ };
499
+
500
+ export type {
501
+ ProgressCircleIndicatorProps,
502
+ ProgressCircleProps,
503
+ ProgressCircleProviderProps,
504
+ ProgressCircleSVGProps,
505
+ ProgressCircleTrackProps,
506
+ ProgressCircleValueProps,
507
+ };
@@ -0,0 +1,34 @@
1
+ import type { VariantProps } from "#/lib/utils";
2
+ import { tv } from "#/lib/utils";
3
+
4
+ /* -----------------------------------------------------------------------------
5
+ * Variant: Alert
6
+ * -------------------------------------------------------------------------- */
7
+
8
+ /**
9
+ * @since 0.3.16-canary.0
10
+ */
11
+ const alertVariants = tv({
12
+ base: "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
13
+ defaultVariants: {
14
+ variant: "default",
15
+ },
16
+ variants: {
17
+ variant: {
18
+ default: "bg-card text-card-foreground",
19
+ destructive: "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
20
+ },
21
+ },
22
+ });
23
+
24
+ /**
25
+ * @since 0.3.16-canary.0
26
+ */
27
+ type AlertVariants = VariantProps<typeof alertVariants>;
28
+
29
+ /* -----------------------------------------------------------------------------
30
+ * Exports
31
+ * -------------------------------------------------------------------------- */
32
+
33
+ export { alertVariants };
34
+ export type { AlertVariants };
@@ -0,0 +1,39 @@
1
+ import type { VariantProps } from "#/lib/utils";
2
+ import { tv } from "#/lib/utils";
3
+
4
+ /* -----------------------------------------------------------------------------
5
+ * Variant: Badge
6
+ * -------------------------------------------------------------------------- */
7
+
8
+ /**
9
+ * @since 0.3.16-canary.0
10
+ */
11
+ const badgeVariants = tv({
12
+ base: "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
13
+ defaultVariants: {
14
+ variant: "default",
15
+ },
16
+ variants: {
17
+ variant: {
18
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
19
+ destructive:
20
+ "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
21
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
24
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
25
+ },
26
+ },
27
+ });
28
+
29
+ /**
30
+ * @since 0.3.16-canary.0
31
+ */
32
+ type BadgeVariants = VariantProps<typeof badgeVariants>;
33
+
34
+ /* -----------------------------------------------------------------------------
35
+ * Exports
36
+ * -------------------------------------------------------------------------- */
37
+
38
+ export { badgeVariants };
39
+ export type { BadgeVariants };
@@ -0,0 +1,36 @@
1
+ import type { VariantProps } from "#/lib/utils";
2
+ import { tv } from "#/lib/utils";
3
+
4
+ /* -----------------------------------------------------------------------------
5
+ * Variant: ButtonGroup
6
+ * -------------------------------------------------------------------------- */
7
+
8
+ /**
9
+ * @since 0.3.16-canary.0
10
+ */
11
+ const buttonGroupVariants = tv({
12
+ base: "group/button-group flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
13
+ defaultVariants: {
14
+ orientation: "horizontal",
15
+ },
16
+ variants: {
17
+ orientation: {
18
+ horizontal:
19
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md!",
20
+ vertical:
21
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md!",
22
+ },
23
+ },
24
+ });
25
+
26
+ /**
27
+ * @since 0.3.16-canary.0
28
+ */
29
+ type ButtonGroupVariants = VariantProps<typeof buttonGroupVariants>;
30
+
31
+ /* -----------------------------------------------------------------------------
32
+ * Exports
33
+ * -------------------------------------------------------------------------- */
34
+
35
+ export { buttonGroupVariants };
36
+ export type { ButtonGroupVariants };
@@ -0,0 +1,56 @@
1
+ import type { VariantProps } from "#/lib/utils";
2
+ import { tv } from "#/lib/utils";
3
+
4
+ /* -----------------------------------------------------------------------------
5
+ * Variant: Button
6
+ * -------------------------------------------------------------------------- */
7
+
8
+ /**
9
+ * @since 0.3.16-canary.0
10
+ */
11
+ const buttonVariants = tv({
12
+ base: "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
13
+ defaultVariants: {
14
+ size: "default",
15
+ variant: "default",
16
+ },
17
+ variants: {
18
+ size: {
19
+ default:
20
+ "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", // 36px
21
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", // 24px
22
+ sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5", // 32px
23
+ lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", // 40px
24
+
25
+ icon: "size-9", // 36px
26
+ "icon-xs":
27
+ "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3", // 24px
28
+ "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md", // 32px
29
+ "icon-lg": "size-10", // 40px
30
+ },
31
+ variant: {
32
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
33
+ destructive:
34
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
35
+ ghost:
36
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
37
+ link: "text-primary underline-offset-4 hover:underline",
38
+ outline:
39
+ "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
40
+ secondary:
41
+ "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
42
+ },
43
+ },
44
+ });
45
+
46
+ /**
47
+ * @since 0.3.16-canary.0
48
+ */
49
+ type ButtonVariants = VariantProps<typeof buttonVariants>;
50
+
51
+ /* -----------------------------------------------------------------------------
52
+ * Exports
53
+ * -------------------------------------------------------------------------- */
54
+
55
+ export { buttonVariants };
56
+ export type { ButtonVariants };