@luxfi/ui 6.1.3 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/dist/accordion.cjs +213 -0
  2. package/dist/accordion.d.cts +74 -0
  3. package/dist/accordion.d.ts +74 -0
  4. package/dist/accordion.js +186 -0
  5. package/dist/alert.cjs +430 -0
  6. package/dist/alert.d.cts +30 -0
  7. package/dist/alert.d.ts +30 -0
  8. package/dist/alert.js +408 -0
  9. package/dist/avatar.cjs +149 -0
  10. package/dist/avatar.d.cts +25 -0
  11. package/dist/avatar.d.ts +25 -0
  12. package/dist/avatar.js +125 -0
  13. package/dist/badge.cjs +452 -0
  14. package/dist/badge.d.cts +19 -0
  15. package/dist/badge.d.ts +19 -0
  16. package/dist/badge.js +431 -0
  17. package/dist/bank.cjs +272 -0
  18. package/dist/bank.d.cts +76 -0
  19. package/dist/bank.d.ts +76 -0
  20. package/dist/bank.js +258 -0
  21. package/dist/button.cjs +578 -0
  22. package/dist/button.d.cts +55 -0
  23. package/dist/button.d.ts +55 -0
  24. package/dist/button.js +553 -0
  25. package/dist/checkbox.cjs +265 -0
  26. package/dist/checkbox.d.cts +31 -0
  27. package/dist/checkbox.d.ts +31 -0
  28. package/dist/checkbox.js +241 -0
  29. package/dist/close-button.cjs +92 -0
  30. package/dist/close-button.d.cts +9 -0
  31. package/dist/close-button.d.ts +9 -0
  32. package/dist/close-button.js +70 -0
  33. package/dist/collapsible.cjs +555 -0
  34. package/dist/collapsible.d.cts +23 -0
  35. package/dist/collapsible.d.ts +23 -0
  36. package/dist/collapsible.js +532 -0
  37. package/dist/color-mode.cjs +96 -0
  38. package/dist/color-mode.d.cts +25 -0
  39. package/dist/color-mode.d.ts +25 -0
  40. package/dist/color-mode.js +72 -0
  41. package/dist/dialog.cjs +295 -0
  42. package/dist/dialog.d.cts +65 -0
  43. package/dist/dialog.d.ts +65 -0
  44. package/dist/dialog.js +263 -0
  45. package/dist/drawer.cjs +231 -0
  46. package/dist/drawer.d.cts +36 -0
  47. package/dist/drawer.d.ts +36 -0
  48. package/dist/drawer.js +199 -0
  49. package/dist/empty-state.cjs +93 -0
  50. package/dist/empty-state.d.cts +13 -0
  51. package/dist/empty-state.d.ts +13 -0
  52. package/dist/empty-state.js +71 -0
  53. package/dist/expiration-selector.cjs +138 -0
  54. package/dist/expiration-selector.d.cts +16 -0
  55. package/dist/expiration-selector.d.ts +16 -0
  56. package/dist/expiration-selector.js +116 -0
  57. package/dist/field.cjs +183 -0
  58. package/dist/field.d.cts +24 -0
  59. package/dist/field.d.ts +24 -0
  60. package/dist/field.js +160 -0
  61. package/dist/greeks-display.cjs +114 -0
  62. package/dist/greeks-display.d.cts +14 -0
  63. package/dist/greeks-display.d.ts +14 -0
  64. package/dist/greeks-display.js +92 -0
  65. package/dist/heading.cjs +41 -0
  66. package/dist/heading.d.cts +10 -0
  67. package/dist/heading.d.ts +10 -0
  68. package/dist/heading.js +35 -0
  69. package/dist/icon-button.cjs +367 -0
  70. package/dist/icon-button.d.cts +33 -0
  71. package/dist/icon-button.d.ts +33 -0
  72. package/dist/icon-button.js +345 -0
  73. package/dist/image.cjs +425 -0
  74. package/dist/image.d.cts +57 -0
  75. package/dist/image.d.ts +57 -0
  76. package/dist/image.js +404 -0
  77. package/dist/index.cjs +7220 -0
  78. package/dist/index.d.cts +55 -0
  79. package/dist/index.d.ts +55 -0
  80. package/dist/index.js +7043 -0
  81. package/dist/input-group.cjs +155 -0
  82. package/dist/input-group.d.cts +17 -0
  83. package/dist/input-group.d.ts +17 -0
  84. package/dist/input-group.js +133 -0
  85. package/dist/input.cjs +65 -0
  86. package/dist/input.d.cts +11 -0
  87. package/dist/input.d.ts +11 -0
  88. package/dist/input.js +59 -0
  89. package/dist/link.cjs +483 -0
  90. package/dist/link.d.cts +37 -0
  91. package/dist/link.d.ts +37 -0
  92. package/dist/link.js +459 -0
  93. package/dist/menu.cjs +305 -0
  94. package/dist/menu.d.cts +84 -0
  95. package/dist/menu.d.ts +84 -0
  96. package/dist/menu.js +269 -0
  97. package/dist/option-chain.cjs +221 -0
  98. package/dist/option-chain.d.cts +37 -0
  99. package/dist/option-chain.d.ts +37 -0
  100. package/dist/option-chain.js +199 -0
  101. package/dist/option-position.cjs +164 -0
  102. package/dist/option-position.d.cts +29 -0
  103. package/dist/option-position.d.ts +29 -0
  104. package/dist/option-position.js +142 -0
  105. package/dist/pin-input.cjs +182 -0
  106. package/dist/pin-input.d.cts +25 -0
  107. package/dist/pin-input.d.ts +25 -0
  108. package/dist/pin-input.js +160 -0
  109. package/dist/pnl-diagram.cjs +453 -0
  110. package/dist/pnl-diagram.d.cts +11 -0
  111. package/dist/pnl-diagram.d.ts +11 -0
  112. package/dist/pnl-diagram.js +431 -0
  113. package/dist/popover.cjs +342 -0
  114. package/dist/popover.d.cts +73 -0
  115. package/dist/popover.d.ts +73 -0
  116. package/dist/popover.js +310 -0
  117. package/dist/progress-circle.cjs +152 -0
  118. package/dist/progress-circle.d.cts +47 -0
  119. package/dist/progress-circle.d.ts +47 -0
  120. package/dist/progress-circle.js +128 -0
  121. package/dist/progress.cjs +117 -0
  122. package/dist/progress.d.cts +28 -0
  123. package/dist/progress.d.ts +28 -0
  124. package/dist/progress.js +94 -0
  125. package/dist/provider.cjs +62 -0
  126. package/dist/provider.d.cts +7 -0
  127. package/dist/provider.d.ts +7 -0
  128. package/dist/provider.js +40 -0
  129. package/dist/radio.cjs +177 -0
  130. package/dist/radio.d.cts +53 -0
  131. package/dist/radio.d.ts +53 -0
  132. package/dist/radio.js +153 -0
  133. package/dist/rating.cjs +80 -0
  134. package/dist/rating.d.cts +14 -0
  135. package/dist/rating.d.ts +14 -0
  136. package/dist/rating.js +58 -0
  137. package/dist/select.cjs +644 -0
  138. package/dist/select.d.cts +130 -0
  139. package/dist/select.d.ts +130 -0
  140. package/dist/select.js +610 -0
  141. package/dist/separator.cjs +57 -0
  142. package/dist/separator.d.cts +13 -0
  143. package/dist/separator.d.ts +13 -0
  144. package/dist/separator.js +51 -0
  145. package/dist/skeleton.cjs +223 -0
  146. package/dist/skeleton.d.cts +60 -0
  147. package/dist/skeleton.d.ts +60 -0
  148. package/dist/skeleton.js +199 -0
  149. package/dist/slider.cjs +138 -0
  150. package/dist/slider.d.cts +24 -0
  151. package/dist/slider.d.ts +24 -0
  152. package/dist/slider.js +115 -0
  153. package/dist/strategy-builder.cjs +373 -0
  154. package/dist/strategy-builder.d.cts +26 -0
  155. package/dist/strategy-builder.d.ts +26 -0
  156. package/dist/strategy-builder.js +351 -0
  157. package/dist/switch.cjs +163 -0
  158. package/dist/switch.d.cts +27 -0
  159. package/dist/switch.d.ts +27 -0
  160. package/dist/switch.js +140 -0
  161. package/dist/table.cjs +897 -0
  162. package/dist/table.d.cts +107 -0
  163. package/dist/table.d.ts +107 -0
  164. package/dist/table.js +866 -0
  165. package/dist/tabs.cjs +240 -0
  166. package/dist/tabs.d.cts +78 -0
  167. package/dist/tabs.d.ts +78 -0
  168. package/dist/tabs.js +213 -0
  169. package/dist/tag.cjs +529 -0
  170. package/dist/tag.d.cts +21 -0
  171. package/dist/tag.d.ts +21 -0
  172. package/dist/tag.js +507 -0
  173. package/dist/textarea.cjs +65 -0
  174. package/dist/textarea.d.cts +11 -0
  175. package/dist/textarea.d.ts +11 -0
  176. package/dist/textarea.js +59 -0
  177. package/dist/toaster.cjs +99 -0
  178. package/dist/toaster.d.cts +29 -0
  179. package/dist/toaster.d.ts +29 -0
  180. package/dist/toaster.js +96 -0
  181. package/dist/tooltip.cjs +172 -0
  182. package/dist/tooltip.d.cts +38 -0
  183. package/dist/tooltip.d.ts +38 -0
  184. package/dist/tooltip.js +150 -0
  185. package/dist/utils.cjs +11 -0
  186. package/dist/utils.d.cts +5 -0
  187. package/dist/utils.d.ts +5 -0
  188. package/dist/utils.js +9 -0
  189. package/package.json +284 -89
  190. package/src/accordion.tsx +285 -0
  191. package/src/alert.tsx +221 -0
  192. package/src/avatar.tsx +174 -0
  193. package/src/badge.tsx +147 -0
  194. package/src/bank.tsx +325 -0
  195. package/src/button.tsx +477 -0
  196. package/src/checkbox.tsx +307 -0
  197. package/src/close-button.tsx +77 -0
  198. package/src/collapsible.tsx +126 -0
  199. package/src/color-mode.tsx +125 -0
  200. package/src/dialog.tsx +346 -0
  201. package/src/drawer.tsx +186 -0
  202. package/src/empty-state.tsx +97 -0
  203. package/src/expiration-selector.tsx +163 -0
  204. package/src/field.tsx +202 -0
  205. package/src/greeks-display.tsx +138 -0
  206. package/src/heading.tsx +44 -0
  207. package/src/icon-button.tsx +217 -0
  208. package/src/image.tsx +280 -0
  209. package/src/index.ts +267 -138
  210. package/src/input-group.tsx +159 -0
  211. package/src/input.tsx +60 -0
  212. package/src/link.tsx +326 -0
  213. package/src/menu.tsx +471 -0
  214. package/src/option-chain.tsx +261 -0
  215. package/src/option-position.tsx +200 -0
  216. package/src/pin-input.tsx +187 -0
  217. package/src/pnl-diagram.tsx +458 -0
  218. package/src/popover.tsx +386 -0
  219. package/src/progress-circle.tsx +180 -0
  220. package/src/progress.tsx +109 -0
  221. package/src/provider.tsx +12 -0
  222. package/src/radio.tsx +175 -0
  223. package/src/rating.tsx +79 -0
  224. package/src/select.tsx +696 -0
  225. package/src/separator.tsx +59 -0
  226. package/src/skeleton.tsx +254 -0
  227. package/src/slider.tsx +152 -0
  228. package/src/strategy-builder.tsx +466 -0
  229. package/src/switch.tsx +158 -0
  230. package/src/table.tsx +621 -0
  231. package/src/tabs.tsx +354 -0
  232. package/src/tag.tsx +159 -0
  233. package/src/textarea.tsx +60 -0
  234. package/src/toaster.tsx +117 -0
  235. package/src/tokens.css +438 -0
  236. package/src/tooltip.tsx +191 -0
  237. package/src/utils/cn.ts +7 -0
  238. package/src/utils.ts +6 -0
  239. package/tokens.css +438 -0
  240. package/LICENSE +0 -122
  241. package/README.md +0 -109
@@ -0,0 +1,386 @@
1
+ import { Popover as GuiPopover } from '@hanzogui/popover';
2
+ import * as React from 'react';
3
+
4
+ import { cn } from './utils';
5
+
6
+ import { CloseButton } from './close-button';
7
+
8
+ // --- Utility: map Chakra-style placement string to @hanzogui placement ---
9
+
10
+ type Side = 'top' | 'right' | 'bottom' | 'left';
11
+ type Align = 'start' | 'center' | 'end';
12
+
13
+ interface PlacementMapping {
14
+ readonly side: Side;
15
+ readonly align: Align;
16
+ }
17
+
18
+ function parsePlacement(placement: string | undefined): PlacementMapping {
19
+ if (!placement) {
20
+ return { side: 'bottom', align: 'start' };
21
+ }
22
+
23
+ const parts = placement.split('-');
24
+ const side = (parts[0] as Side) ?? 'bottom';
25
+ const alignPart = parts[1];
26
+
27
+ let align: Align = 'center';
28
+ if (alignPart === 'start') {
29
+ align = 'start';
30
+ } else if (alignPart === 'end') {
31
+ align = 'end';
32
+ }
33
+
34
+ return { side, align };
35
+ }
36
+
37
+ // --- PopoverRoot ---
38
+
39
+ interface Positioning {
40
+ readonly placement?: string;
41
+ readonly offset?: {
42
+ readonly mainAxis?: number;
43
+ readonly crossAxis?: number;
44
+ };
45
+ readonly overflowPadding?: number;
46
+ }
47
+
48
+ export interface PopoverRootProps {
49
+ readonly children?: React.ReactNode;
50
+ readonly open?: boolean;
51
+ readonly defaultOpen?: boolean;
52
+
53
+ /** Chakra-style callback: receives `{ open: boolean }` */
54
+ readonly onOpenChange?: (details: { open: boolean }) => void;
55
+ readonly positioning?: Positioning;
56
+ readonly lazyMount?: boolean;
57
+ readonly unmountOnExit?: boolean;
58
+ readonly autoFocus?: boolean;
59
+ readonly closeOnInteractOutside?: boolean;
60
+ readonly modal?: boolean;
61
+ }
62
+
63
+ // Stash positioning info in context so PopoverContent can read it.
64
+ interface PopoverPositioningCtx {
65
+ readonly side: Side;
66
+ readonly align: Align;
67
+ readonly sideOffset: number;
68
+ readonly alignOffset: number;
69
+ readonly collisionPadding: number;
70
+ readonly autoFocus: boolean;
71
+ readonly closeOnInteractOutside: boolean;
72
+ }
73
+
74
+ const PositioningContext = React.createContext<PopoverPositioningCtx>({
75
+ side: 'bottom',
76
+ align: 'start',
77
+ sideOffset: 4,
78
+ alignOffset: 0,
79
+ collisionPadding: 4,
80
+ autoFocus: false,
81
+ closeOnInteractOutside: true,
82
+ });
83
+
84
+ export const PopoverRoot = (props: PopoverRootProps): React.ReactElement => {
85
+ const {
86
+ children,
87
+ open,
88
+ defaultOpen,
89
+ onOpenChange,
90
+ positioning,
91
+ autoFocus = false,
92
+ closeOnInteractOutside = true,
93
+ // lazyMount and unmountOnExit are handled via keepChildrenMounted
94
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
95
+ lazyMount: _lazyMount,
96
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
97
+ unmountOnExit: _unmountOnExit,
98
+ } = props;
99
+
100
+ // Merge default positioning with user positioning
101
+ const mergedPositioning: Positioning = {
102
+ placement: 'bottom-start',
103
+ overflowPadding: 4,
104
+ ...positioning,
105
+ offset: {
106
+ mainAxis: 4,
107
+ ...positioning?.offset,
108
+ },
109
+ };
110
+
111
+ const { side, align } = parsePlacement(mergedPositioning.placement);
112
+
113
+ const positioningValue = React.useMemo<PopoverPositioningCtx>(() => ({
114
+ side,
115
+ align,
116
+ sideOffset: mergedPositioning.offset?.mainAxis ?? 4,
117
+ alignOffset: mergedPositioning.offset?.crossAxis ?? 0,
118
+ collisionPadding: mergedPositioning.overflowPadding ?? 4,
119
+ autoFocus,
120
+ closeOnInteractOutside,
121
+ }), [
122
+ side, align,
123
+ mergedPositioning.offset?.mainAxis, mergedPositioning.offset?.crossAxis,
124
+ mergedPositioning.overflowPadding, autoFocus, closeOnInteractOutside,
125
+ ]);
126
+
127
+ // Bridge Chakra-style onOpenChange ({ open }) to @hanzogui (open)
128
+ const handleOpenChange = React.useCallback((isOpen: boolean) => {
129
+ onOpenChange?.({ open: isOpen });
130
+ }, [ onOpenChange ]);
131
+
132
+ const placement = mergedPositioning.placement ?? 'bottom-start';
133
+ const offset = mergedPositioning.offset?.mainAxis ?? 4;
134
+
135
+ return (
136
+ <PositioningContext.Provider value={ positioningValue }>
137
+ <GuiPopover
138
+ open={ open }
139
+ defaultOpen={ defaultOpen }
140
+ onOpenChange={ handleOpenChange }
141
+ placement={ placement as any }
142
+ offset={ offset }
143
+ disableFocus={ !autoFocus }
144
+ >
145
+ { children }
146
+ </GuiPopover>
147
+ </PositioningContext.Provider>
148
+ );
149
+ };
150
+
151
+ // --- PopoverTrigger ---
152
+
153
+ export interface PopoverTriggerProps extends React.ComponentPropsWithoutRef<'button'> {
154
+ readonly asChild?: boolean;
155
+ }
156
+
157
+ export const PopoverTrigger = React.forwardRef<
158
+ HTMLButtonElement,
159
+ PopoverTriggerProps
160
+ >(function PopoverTrigger(props, ref) {
161
+ const { asChild = true, ...rest } = props;
162
+ return <GuiPopover.Trigger asChild={ asChild ? 'except-style' : undefined } ref={ ref } { ...rest as any }/>;
163
+ });
164
+
165
+ // --- PopoverContent ---
166
+
167
+ export interface PopoverContentProps extends React.ComponentPropsWithoutRef<'div'> {
168
+ readonly portalled?: boolean;
169
+ readonly portalRef?: React.RefObject<HTMLElement>;
170
+ // Legacy Chakra style-prop shims
171
+ readonly w?: string | Record<string, string>;
172
+ readonly minW?: string;
173
+ readonly maxW?: string;
174
+ readonly paddingTop?: number | string;
175
+ }
176
+
177
+ export const PopoverContent = React.forwardRef<
178
+ HTMLDivElement,
179
+ PopoverContentProps
180
+ >(function PopoverContent(props, ref) {
181
+ const { portalled = true, portalRef: _portalRef, className, w, minW, maxW, paddingTop, style: styleProp, ...rest } = props;
182
+ const resolvedW = typeof w === 'object' ? (w as Record<string, string>).base ?? (w as Record<string, string>).lg : w;
183
+ const contentStyle: React.CSSProperties = {
184
+ ...styleProp,
185
+ ...(resolvedW ? { width: resolvedW } : {}),
186
+ ...(minW ? { minWidth: minW } : {}),
187
+ ...(maxW ? { maxWidth: maxW } : {}),
188
+ ...(paddingTop !== undefined ? { paddingTop: typeof paddingTop === 'number' ? `${ paddingTop * 4 }px` : paddingTop } : {}),
189
+ };
190
+
191
+ return (
192
+ <GuiPopover.Content
193
+ ref={ ref }
194
+ unstyled
195
+ className={ cn(
196
+ 'z-50 rounded-lg border border-[var(--color-popover-border,var(--color-border-divider))]',
197
+ 'bg-[var(--color-popover-bg,var(--color-dialog-bg))]',
198
+ 'shadow-[var(--shadow-popover,var(--shadow-lg))]',
199
+ 'outline-none',
200
+ 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
201
+ 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
202
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
203
+ 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
204
+ className,
205
+ ) }
206
+ style={ Object.keys(contentStyle).length > 0 ? contentStyle : undefined }
207
+ { ...rest as any }
208
+ />
209
+ );
210
+ });
211
+
212
+ // --- PopoverArrow ---
213
+
214
+ export interface PopoverArrowProps extends React.ComponentPropsWithoutRef<'svg'> {}
215
+
216
+ export const PopoverArrow = React.forwardRef<
217
+ SVGSVGElement,
218
+ PopoverArrowProps
219
+ >(function PopoverArrow(props, ref) {
220
+ const { className, ...rest } = props;
221
+ return (
222
+ <GuiPopover.Arrow
223
+ ref={ ref }
224
+ unstyled
225
+ className={ cn('fill-[var(--color-popover-bg,var(--color-dialog-bg))]', className) }
226
+ { ...rest as any }
227
+ />
228
+ );
229
+ });
230
+
231
+ // --- PopoverCloseTrigger ---
232
+
233
+ export interface PopoverCloseTriggerProps extends React.ComponentPropsWithoutRef<'button'> {}
234
+
235
+ export const PopoverCloseTrigger = React.forwardRef<
236
+ HTMLButtonElement,
237
+ PopoverCloseTriggerProps
238
+ >(function PopoverCloseTrigger(props, ref) {
239
+ const { className, ...rest } = props;
240
+ return (
241
+ <GuiPopover.Close
242
+ unstyled
243
+ className={ cn('absolute top-1 right-1', className) }
244
+ { ...rest as any }
245
+ asChild
246
+ ref={ ref }
247
+ >
248
+ <CloseButton/>
249
+ </GuiPopover.Close>
250
+ );
251
+ });
252
+
253
+ // --- PopoverCloseTriggerWrapper ---
254
+
255
+ export interface PopoverCloseTriggerWrapperProps extends React.ComponentPropsWithoutRef<'button'> {
256
+ readonly disabled?: boolean;
257
+ }
258
+
259
+ export const PopoverCloseTriggerWrapper = React.forwardRef<
260
+ HTMLButtonElement,
261
+ PopoverCloseTriggerWrapperProps
262
+ >(function PopoverCloseTriggerWrapper(props, ref) {
263
+ const { disabled, children, ...rest } = props;
264
+
265
+ if (disabled) {
266
+ return children as React.ReactElement;
267
+ }
268
+
269
+ return (
270
+ <GuiPopover.Close ref={ ref } { ...rest as any } asChild>
271
+ { children }
272
+ </GuiPopover.Close>
273
+ );
274
+ });
275
+
276
+ // --- Simple wrapper components ---
277
+
278
+ export interface PopoverBodyProps extends React.ComponentPropsWithoutRef<'div'> {
279
+ // Legacy Chakra style-prop shims
280
+ readonly display?: string;
281
+ readonly flexDir?: string;
282
+ readonly rowGap?: number | string;
283
+ readonly px?: number | string;
284
+ readonly py?: number | string;
285
+ readonly textStyle?: string;
286
+ readonly alignItems?: string;
287
+ }
288
+
289
+ export const PopoverBody = React.forwardRef<
290
+ HTMLDivElement,
291
+ PopoverBodyProps
292
+ >(function PopoverBody(props, ref) {
293
+ const {
294
+ className, style: styleProp,
295
+ display: _display, flexDir: _flexDir, rowGap: _rowGap, px: _px, py: _py,
296
+ textStyle: _textStyle, alignItems: _alignItems,
297
+ ...rest
298
+ } = props;
299
+ const bodyStyle: React.CSSProperties = {
300
+ ...styleProp,
301
+ ...(_display ? { display: _display } : {}),
302
+ ...(_flexDir ? { flexDirection: _flexDir as React.CSSProperties['flexDirection'] } : {}),
303
+ ...(_rowGap !== undefined ? { rowGap: typeof _rowGap === 'number' ? `${ _rowGap * 4 }px` : _rowGap } : {}),
304
+ ...(_px !== undefined ? {
305
+ paddingLeft: typeof _px === 'number' ? `${ _px * 4 }px` : _px,
306
+ paddingRight: typeof _px === 'number' ? `${ _px * 4 }px` : _px,
307
+ } : {}),
308
+ ...(_py !== undefined ? {
309
+ paddingTop: typeof _py === 'number' ? `${ _py * 4 }px` : _py,
310
+ paddingBottom: typeof _py === 'number' ? `${ _py * 4 }px` : _py,
311
+ } : {}),
312
+ ...(_alignItems ? { alignItems: _alignItems } : {}),
313
+ };
314
+ return (
315
+ <div
316
+ ref={ ref }
317
+ className={ cn('p-4', className) }
318
+ style={ Object.keys(bodyStyle).length > 0 ? bodyStyle : undefined }
319
+ { ...rest }
320
+ />
321
+ );
322
+ });
323
+
324
+ export interface PopoverHeaderProps extends React.ComponentPropsWithoutRef<'div'> {}
325
+
326
+ export const PopoverHeader = React.forwardRef<
327
+ HTMLDivElement,
328
+ PopoverHeaderProps
329
+ >(function PopoverHeader(props, ref) {
330
+ const { className, ...rest } = props;
331
+ return (
332
+ <div
333
+ ref={ ref }
334
+ className={ cn('px-4 pt-4 pb-0 font-semibold', className) }
335
+ { ...rest }
336
+ />
337
+ );
338
+ });
339
+
340
+ export interface PopoverFooterProps extends React.ComponentPropsWithoutRef<'div'> {}
341
+
342
+ export const PopoverFooter = React.forwardRef<
343
+ HTMLDivElement,
344
+ PopoverFooterProps
345
+ >(function PopoverFooter(props, ref) {
346
+ const { className, ...rest } = props;
347
+ return (
348
+ <div
349
+ ref={ ref }
350
+ className={ cn('px-4 pb-4 pt-0', className) }
351
+ { ...rest }
352
+ />
353
+ );
354
+ });
355
+
356
+ export interface PopoverTitleProps extends React.ComponentPropsWithoutRef<'h3'> {}
357
+
358
+ export const PopoverTitle = React.forwardRef<
359
+ HTMLHeadingElement,
360
+ PopoverTitleProps
361
+ >(function PopoverTitle(props, ref) {
362
+ const { className, ...rest } = props;
363
+ return (
364
+ <h3
365
+ ref={ ref }
366
+ className={ cn('text-base font-semibold', className) }
367
+ { ...rest }
368
+ />
369
+ );
370
+ });
371
+
372
+ export interface PopoverDescriptionProps extends React.ComponentPropsWithoutRef<'p'> {}
373
+
374
+ export const PopoverDescription = React.forwardRef<
375
+ HTMLParagraphElement,
376
+ PopoverDescriptionProps
377
+ >(function PopoverDescription(props, ref) {
378
+ const { className, ...rest } = props;
379
+ return (
380
+ <p
381
+ ref={ ref }
382
+ className={ cn('text-sm', className) }
383
+ { ...rest }
384
+ />
385
+ );
386
+ });
@@ -0,0 +1,180 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Size presets matching the original Chakra progressCircle recipe
7
+ // ---------------------------------------------------------------------------
8
+
9
+ const SIZE_MAP = {
10
+ xs: { size: 24, thickness: 4, textClass: 'text-[10px]' },
11
+ sm: { size: 32, thickness: 5, textClass: 'text-[10px]' },
12
+ md: { size: 40, thickness: 6, textClass: 'text-xs' },
13
+ lg: { size: 48, thickness: 7, textClass: 'text-sm' },
14
+ xl: { size: 64, thickness: 8, textClass: 'text-sm' },
15
+ } as const;
16
+
17
+ type Size = keyof typeof SIZE_MAP;
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Context
21
+ // ---------------------------------------------------------------------------
22
+
23
+ interface ProgressCircleCtx {
24
+ readonly value: number | null;
25
+ readonly size: Size;
26
+ readonly colorPalette: string;
27
+ }
28
+
29
+ const Ctx = React.createContext<ProgressCircleCtx>({
30
+ value: 0,
31
+ size: 'md',
32
+ colorPalette: 'blue',
33
+ });
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Root
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export interface ProgressCircleRootProps
40
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
41
+ readonly value?: number | null;
42
+ readonly size?: Size;
43
+ readonly colorPalette?: string;
44
+ }
45
+
46
+ export const ProgressCircleRoot = React.forwardRef<
47
+ HTMLDivElement,
48
+ ProgressCircleRootProps
49
+ >(function ProgressCircleRoot(props, ref) {
50
+ const {
51
+ value = 0,
52
+ size = 'md',
53
+ colorPalette = 'blue',
54
+ className,
55
+ children,
56
+ ...rest
57
+ } = props;
58
+
59
+ const ctx = React.useMemo<ProgressCircleCtx>(
60
+ () => ({ value: value ?? null, size, colorPalette }),
61
+ [ value, size, colorPalette ],
62
+ );
63
+
64
+ return (
65
+ <Ctx.Provider value={ ctx }>
66
+ <div
67
+ ref={ ref }
68
+ role="progressbar"
69
+ aria-valuenow={ value ?? undefined }
70
+ aria-valuemin={ 0 }
71
+ aria-valuemax={ 100 }
72
+ className={ cn('relative inline-flex text-sm', className) }
73
+ { ...rest }
74
+ >
75
+ { children }
76
+ </div>
77
+ </Ctx.Provider>
78
+ );
79
+ });
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Ring (SVG track + range arc)
83
+ // ---------------------------------------------------------------------------
84
+
85
+ export interface ProgressCircleRingProps
86
+ extends Omit<React.SVGAttributes<SVGSVGElement>, 'color'> {
87
+ readonly trackColor?: string;
88
+ readonly cap?: React.SVGAttributes<SVGCircleElement>['strokeLinecap'];
89
+ readonly color?: string;
90
+ }
91
+
92
+ export const ProgressCircleRing = React.forwardRef<
93
+ SVGSVGElement,
94
+ ProgressCircleRingProps
95
+ >(function ProgressCircleRing(props, ref) {
96
+ const { trackColor, cap, color, className, ...rest } = props;
97
+ const { value, size: sizeKey } = React.useContext(Ctx);
98
+
99
+ const { size, thickness } = SIZE_MAP[sizeKey];
100
+ const radius = size / 2 - thickness / 2;
101
+ const circumference = 2 * Math.PI * radius;
102
+ const percent = value ?? 0;
103
+ const offset = circumference * ((100 - percent) / 100);
104
+
105
+ return (
106
+ <svg
107
+ ref={ ref }
108
+ width={ size }
109
+ height={ size }
110
+ viewBox={ `0 0 ${ size } ${ size }` }
111
+ className={ cn(
112
+ value === null && 'animate-spin',
113
+ className,
114
+ ) }
115
+ { ...rest }
116
+ >
117
+ { /* Track */ }
118
+ <circle
119
+ cx={ size / 2 }
120
+ cy={ size / 2 }
121
+ r={ radius }
122
+ fill="transparent"
123
+ strokeWidth={ thickness }
124
+ stroke={ trackColor ?? 'currentColor' }
125
+ className={ !trackColor ? 'opacity-20' : undefined }
126
+ />
127
+ { /* Range */ }
128
+ <circle
129
+ cx={ size / 2 }
130
+ cy={ size / 2 }
131
+ r={ radius }
132
+ fill="transparent"
133
+ strokeWidth={ thickness }
134
+ stroke={ color ?? 'currentColor' }
135
+ strokeLinecap={ cap }
136
+ strokeDasharray={ circumference }
137
+ strokeDashoffset={ offset }
138
+ style={{
139
+ transformOrigin: 'center',
140
+ transform: 'rotate(-90deg)',
141
+ transition: 'stroke-dashoffset 0.6s, stroke-dasharray 0.6s',
142
+ opacity: percent === 0 ? 0 : undefined,
143
+ }}
144
+ />
145
+ </svg>
146
+ );
147
+ });
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Value text (centered number)
151
+ // ---------------------------------------------------------------------------
152
+
153
+ export interface ProgressCircleValueTextProps
154
+ extends React.HTMLAttributes<HTMLDivElement> {}
155
+
156
+ export const ProgressCircleValueText = React.forwardRef<
157
+ HTMLDivElement,
158
+ ProgressCircleValueTextProps
159
+ >(function ProgressCircleValueText(props, ref) {
160
+ const { className, children, ...rest } = props;
161
+ const { value, size: sizeKey } = React.useContext(Ctx);
162
+
163
+ const { textClass } = SIZE_MAP[sizeKey];
164
+
165
+ return (
166
+ <div
167
+ ref={ ref }
168
+ aria-live="polite"
169
+ className={ cn(
170
+ 'absolute inset-0 flex items-center justify-center',
171
+ 'font-medium tracking-tight tabular-nums leading-none',
172
+ textClass,
173
+ className,
174
+ ) }
175
+ { ...rest }
176
+ >
177
+ { children ?? `${ Math.round(value ?? 0) }%` }
178
+ </div>
179
+ );
180
+ });
@@ -0,0 +1,109 @@
1
+ import * as RadixProgress from '@radix-ui/react-progress';
2
+ import * as React from 'react';
3
+
4
+ import { cn } from './utils';
5
+
6
+ const SIZE_CLASSES = {
7
+ sm: 'h-1',
8
+ md: 'h-2',
9
+ lg: 'h-3',
10
+ xl: 'h-4',
11
+ } as const;
12
+
13
+ type Size = keyof typeof SIZE_CLASSES;
14
+
15
+ interface TrackProps {
16
+ readonly borderStartRadius?: number | string;
17
+ readonly borderEndRadius?: number | string;
18
+ readonly className?: string;
19
+ readonly style?: React.CSSProperties;
20
+ }
21
+
22
+ interface ProgressProps extends React.ComponentPropsWithoutRef<'div'> {
23
+ readonly value?: number | null;
24
+ readonly min?: number;
25
+ readonly max?: number;
26
+ readonly size?: Size;
27
+ readonly color?: string;
28
+ readonly trackProps?: TrackProps;
29
+
30
+ // Common Chakra layout shorthands that consumers currently pass.
31
+ readonly w?: string;
32
+ readonly flex?: string | number;
33
+ }
34
+
35
+ function normalizeValue(value: number | null | undefined, min: number, max: number): number {
36
+ if (value == null) return 0;
37
+ if (max <= min) return 0;
38
+ return Math.round(((value - min) / (max - min)) * 100);
39
+ }
40
+
41
+ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
42
+ function Progress(props, ref) {
43
+ const {
44
+ value,
45
+ min = 0,
46
+ max = 100,
47
+ size = 'md',
48
+ color,
49
+ trackProps,
50
+ className,
51
+ style,
52
+ w,
53
+ flex,
54
+ ...rest
55
+ } = props;
56
+
57
+ const percent = normalizeValue(value, min, max);
58
+
59
+ const trackStyle: React.CSSProperties = {
60
+ ...trackProps?.style,
61
+ ...(trackProps?.borderStartRadius !== undefined && {
62
+ borderStartStartRadius: trackProps.borderStartRadius,
63
+ borderEndStartRadius: trackProps.borderStartRadius,
64
+ }),
65
+ ...(trackProps?.borderEndRadius !== undefined && {
66
+ borderStartEndRadius: trackProps.borderEndRadius,
67
+ borderEndEndRadius: trackProps.borderEndRadius,
68
+ }),
69
+ };
70
+
71
+ const rootStyle: React.CSSProperties = {
72
+ ...style,
73
+ ...(w !== undefined && { width: w === 'full' ? '100%' : w }),
74
+ ...(flex !== undefined && { flex }),
75
+ };
76
+
77
+ return (
78
+ <div
79
+ ref={ ref }
80
+ className={ cn('relative', className) }
81
+ style={ rootStyle }
82
+ { ...rest }
83
+ >
84
+ <RadixProgress.Root
85
+ value={ percent }
86
+ max={ 100 }
87
+ className={ cn(
88
+ 'w-full overflow-hidden rounded-full',
89
+ 'bg-[var(--color-progress-track)]',
90
+ SIZE_CLASSES[size],
91
+ trackProps?.className,
92
+ ) }
93
+ style={ trackStyle }
94
+ >
95
+ <RadixProgress.Indicator
96
+ className={ cn(
97
+ 'h-full rounded-full transition-transform duration-500 ease-out',
98
+ !color && 'bg-[var(--color-progress-indicator)]',
99
+ ) }
100
+ style={{
101
+ width: `${ percent }%`,
102
+ ...(color && { backgroundColor: `var(--color-${ color.replace('.', '-') }, ${ color })` }),
103
+ }}
104
+ />
105
+ </RadixProgress.Root>
106
+ </div>
107
+ );
108
+ },
109
+ );
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import {
6
+ ColorModeProvider,
7
+ type ColorModeProviderProps,
8
+ } from './color-mode';
9
+
10
+ export function Provider(props: ColorModeProviderProps) {
11
+ return <ColorModeProvider { ...props }/>;
12
+ }