@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
package/src/button.tsx ADDED
@@ -0,0 +1,477 @@
1
+ import { styled, View } from '@hanzogui/core';
2
+ import * as React from 'react';
3
+
4
+ import { Skeleton } from './skeleton';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Spinner (CSS-only, no react-native ActivityIndicator)
8
+ // ---------------------------------------------------------------------------
9
+
10
+ const SpinnerFrame = styled(View, {
11
+ name: 'ButtonSpinner',
12
+ render: <span />,
13
+ width: 16,
14
+ height: 16,
15
+ borderRadius: 1000,
16
+ borderWidth: 2,
17
+ borderColor: 'currentColor' as any,
18
+ borderBottomColor: 'transparent' as any,
19
+ borderLeftColor: 'transparent' as any,
20
+ });
21
+
22
+ function Spinner({ className }: { readonly className?: string }): React.ReactElement {
23
+ return (
24
+ <SpinnerFrame
25
+ role="status"
26
+ aria-label="Loading"
27
+ className={[
28
+ 'animate-spin',
29
+ className,
30
+ ].filter(Boolean).join(' ')}
31
+ />
32
+ );
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // ButtonFrame — styled base
37
+ // ---------------------------------------------------------------------------
38
+
39
+ const ButtonFrame = styled(View, {
40
+ name: 'LuxButton',
41
+ render: <button type="button" />,
42
+ role: 'button',
43
+ tabIndex: 0,
44
+ cursor: 'pointer',
45
+ userSelect: 'none' as any,
46
+ display: 'inline-flex',
47
+ alignItems: 'center',
48
+ justifyContent: 'center',
49
+ gap: 0,
50
+ overflow: 'hidden',
51
+ borderRadius: 6,
52
+ // Whitespace nowrap via className since hanzogui doesn't have a direct token
53
+ // Transition via className
54
+
55
+ variants: {
56
+ size: {
57
+ '2xs': { paddingHorizontal: 8, height: 20, minWidth: 20, fontSize: 12, borderRadius: 4, gap: 4 },
58
+ xs: { paddingHorizontal: 8, height: 24, minWidth: 24, fontSize: 14, borderRadius: 4, gap: 4 },
59
+ sm: { paddingHorizontal: 12, height: 32, minWidth: 32, fontSize: 14, borderRadius: 6, gap: 4 },
60
+ md: { paddingHorizontal: 12, height: 40, minWidth: 40, fontSize: 16, borderRadius: 6, gap: 8 },
61
+ },
62
+
63
+ disabled: {
64
+ true: {
65
+ opacity: 0.2,
66
+ cursor: 'not-allowed',
67
+ pointerEvents: 'none',
68
+ },
69
+ },
70
+ } as const,
71
+
72
+ defaultVariants: {
73
+ size: 'md',
74
+ },
75
+ });
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Variant class maps — CSS custom properties referenced via Tailwind classes.
79
+ // hanzogui styled() handles layout/sizing; className handles color theming
80
+ // so the existing tokens.css contract is honored without duplication.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ const VARIANT_CLASSES: Record<string, string> = {
84
+ solid: [
85
+ 'bg-[var(--color-button-solid-bg)] text-[var(--color-button-solid-text)]',
86
+ 'hover:bg-[var(--color-hover)]',
87
+ 'data-[expanded]:bg-[var(--color-hover)]',
88
+ ].join(' '),
89
+
90
+ solid_danger: [
91
+ 'bg-red-600 text-[var(--color-button-solid-text)]',
92
+ 'hover:bg-red-500',
93
+ 'data-[expanded]:bg-red-500',
94
+ ].join(' '),
95
+
96
+ outline: [
97
+ 'border-2 border-solid bg-transparent',
98
+ 'text-[var(--color-button-outline-fg)] border-[var(--color-button-outline-fg)]',
99
+ 'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
100
+ ].join(' '),
101
+
102
+ outline_danger: [
103
+ 'border-2 border-solid bg-transparent',
104
+ 'text-red-600 border-red-600',
105
+ 'hover:bg-transparent hover:text-red-500 hover:border-red-500',
106
+ ].join(' '),
107
+
108
+ dropdown: [
109
+ 'border-2 border-solid bg-transparent',
110
+ 'text-[var(--color-button-dropdown-fg)] border-[var(--color-button-dropdown-border)]',
111
+ 'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
112
+ 'data-[expanded]:bg-transparent data-[expanded]:text-[var(--color-hover)] data-[expanded]:border-[var(--color-hover)]',
113
+ 'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
114
+ 'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
115
+ 'data-[selected]:hover:text-[var(--color-hover)] data-[selected]:hover:border-transparent',
116
+ 'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
117
+ ].join(' '),
118
+
119
+ header: [
120
+ 'bg-transparent border-2 border-solid',
121
+ 'text-[var(--color-button-header-fg)] border-[var(--color-button-header-border)]',
122
+ 'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
123
+ 'data-[selected]:bg-[var(--color-button-header-bg-selected)]',
124
+ 'data-[selected]:text-[var(--color-button-header-fg-selected)] data-[selected]:border-transparent data-[selected]:border-0',
125
+ 'data-[selected]:hover:bg-[var(--color-button-header-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
126
+ 'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
127
+ 'data-[selected]:data-[highlighted]:bg-[var(--color-button-header-bg-highlighted)]',
128
+ 'data-[selected]:data-[highlighted]:text-[var(--color-button-header-fg-highlighted)]',
129
+ 'data-[selected]:data-[highlighted]:border-transparent data-[selected]:data-[highlighted]:border-0',
130
+ 'data-[selected]:data-[highlighted]:hover:bg-[var(--color-button-header-bg-highlighted)]',
131
+ 'data-[selected]:data-[highlighted]:hover:text-[var(--color-hover)]',
132
+ 'data-[selected]:data-[highlighted]:data-[expanded]:text-[var(--color-hover)]',
133
+ ].join(' '),
134
+
135
+ hero: [
136
+ 'bg-[var(--color-button-hero-bg)] text-[var(--color-button-hero-fg)]',
137
+ 'hover:bg-[var(--color-button-hero-bg-hover)] hover:text-[var(--color-button-hero-fg-hover)]',
138
+ 'data-[selected]:bg-[var(--color-button-hero-bg-selected)] data-[selected]:text-[var(--color-button-hero-fg-selected)]',
139
+ 'data-[selected]:hover:bg-[var(--color-button-hero-bg-selected)] data-[selected]:hover:text-[var(--color-hover)]',
140
+ 'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
141
+ ].join(' '),
142
+
143
+ segmented: [
144
+ 'bg-transparent border-2 border-solid rounded-none',
145
+ 'text-[var(--color-button-segmented-fg)] border-[var(--color-selected-control-bg)]',
146
+ 'hover:text-[var(--color-hover)]',
147
+ 'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
148
+ 'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-selected-control-text)]',
149
+ '[&:not(:last-child)]:border-r-0 data-[selected]:[&:not(:last-child)]:border-r-2',
150
+ 'data-[selected]+*:border-l-0',
151
+ 'first:rounded-l-md last:rounded-r-md',
152
+ ].join(' '),
153
+
154
+ plain: 'bg-transparent text-inherit border-none hover:bg-transparent',
155
+
156
+ subtle: [
157
+ 'bg-[var(--color-button-subtle-bg)] text-[var(--color-button-subtle-fg)]',
158
+ 'hover:bg-[var(--color-button-subtle-bg)] hover:text-[var(--color-hover)]',
159
+ 'disabled:bg-[var(--color-button-subtle-bg)] disabled:text-[var(--color-button-subtle-fg)]',
160
+ ].join(' '),
161
+
162
+ link: [
163
+ 'bg-transparent text-[var(--color-link-primary)] border-none font-normal',
164
+ 'px-0 h-auto',
165
+ 'hover:bg-transparent hover:text-[var(--color-link-primary-hover)]',
166
+ 'disabled:text-[var(--color-text-secondary)]',
167
+ ].join(' '),
168
+
169
+ icon_secondary: [
170
+ 'bg-transparent text-[var(--color-icon-secondary)] border-none',
171
+ 'hover:text-[var(--color-hover)]',
172
+ 'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
173
+ 'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
174
+ 'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
175
+ 'data-[expanded]:text-[var(--color-hover)]',
176
+ ].join(' '),
177
+
178
+ icon_background: [
179
+ 'bg-[var(--color-button-icon-background-bg)] text-[var(--color-icon-secondary)] border-none',
180
+ 'hover:text-[var(--color-hover)]',
181
+ 'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)]',
182
+ 'data-[selected]:hover:bg-[var(--color-selected-control-bg)] data-[selected]:hover:text-[var(--color-hover)]',
183
+ 'data-[selected]:data-[expanded]:text-[var(--color-hover)]',
184
+ 'data-[expanded]:text-[var(--color-hover)]',
185
+ ].join(' '),
186
+
187
+ pagination: [
188
+ 'border-2 border-solid bg-transparent',
189
+ 'text-[var(--color-button-pagination-fg)] border-[var(--color-button-pagination-border)]',
190
+ 'hover:bg-transparent hover:text-[var(--color-hover)] hover:border-[var(--color-hover)]',
191
+ 'data-[selected]:bg-[var(--color-selected-control-bg)] data-[selected]:text-[var(--color-selected-control-text)] data-[selected]:border-transparent',
192
+ 'data-[selected]:hover:bg-[var(--color-selected-control-bg)]',
193
+ 'data-[selected]:hover:text-[var(--color-selected-control-text)] data-[selected]:hover:border-transparent',
194
+ ].join(' '),
195
+ };
196
+
197
+ const BASE_CLASSES = 'font-semibold whitespace-nowrap transition-colors duration-150';
198
+
199
+ type ButtonVariant = keyof typeof VARIANT_CLASSES;
200
+ type ButtonSize = '2xs' | 'xs' | 'sm' | 'md';
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // ButtonProps
204
+ // ---------------------------------------------------------------------------
205
+
206
+ export interface ButtonProps
207
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'size'> {
208
+ readonly variant?: ButtonVariant;
209
+ readonly size?: ButtonSize;
210
+ readonly asChild?: boolean;
211
+ readonly loading?: boolean;
212
+ readonly loadingText?: React.ReactNode;
213
+ readonly loadingSkeleton?: boolean;
214
+ readonly expanded?: boolean;
215
+ readonly selected?: boolean;
216
+ readonly highlighted?: boolean;
217
+ // Legacy Chakra style-prop shims
218
+ readonly mt?: number | string;
219
+ readonly ml?: number | string;
220
+ readonly mr?: number | string;
221
+ readonly px?: number | string;
222
+ readonly py?: number | string;
223
+ readonly fontWeight?: number | string;
224
+ readonly gap?: number | string;
225
+ readonly flexShrink?: number;
226
+ readonly columnGap?: number | string;
227
+ readonly gridColumn?: number | string;
228
+ readonly gridRow?: string;
229
+ readonly justifySelf?: string;
230
+ readonly alignSelf?: string;
231
+ readonly textStyle?: string;
232
+ readonly h?: string;
233
+ readonly w?: string;
234
+ readonly display?: string;
235
+ readonly borderBottomRightRadius?: number | string;
236
+ readonly borderTopRightRadius?: number | string;
237
+ readonly 'data-call-strategy'?: string;
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Chakra shim style builder
242
+ // ---------------------------------------------------------------------------
243
+
244
+ const SP = 4;
245
+
246
+ function buildShimStyle(props: {
247
+ style?: React.CSSProperties;
248
+ mt?: number | string;
249
+ ml?: number | string;
250
+ mr?: number | string;
251
+ px?: number | string;
252
+ py?: number | string;
253
+ fontWeight?: number | string;
254
+ gap?: number | string;
255
+ flexShrink?: number;
256
+ columnGap?: number | string;
257
+ gridColumn?: number | string;
258
+ gridRow?: string;
259
+ justifySelf?: string;
260
+ alignSelf?: string;
261
+ h?: string;
262
+ w?: string;
263
+ display?: string;
264
+ borderBottomRightRadius?: number | string;
265
+ borderTopRightRadius?: number | string;
266
+ }): React.CSSProperties | undefined {
267
+ const s: React.CSSProperties = { ...props.style };
268
+ if (props.mt !== undefined) s.marginTop = typeof props.mt === 'number' ? `${props.mt * SP}px` : props.mt;
269
+ if (props.ml !== undefined) s.marginLeft = typeof props.ml === 'number' ? `${props.ml * SP}px` : props.ml;
270
+ if (props.mr !== undefined) s.marginRight = typeof props.mr === 'number' ? `${props.mr * SP}px` : props.mr;
271
+ if (props.px !== undefined) { const v = typeof props.px === 'number' ? `${props.px * SP}px` : props.px; s.paddingLeft = v; s.paddingRight = v; }
272
+ if (props.py !== undefined) { const v = typeof props.py === 'number' ? `${props.py * SP}px` : props.py; s.paddingTop = v; s.paddingBottom = v; }
273
+ if (props.fontWeight !== undefined) s.fontWeight = props.fontWeight;
274
+ if (props.gap !== undefined) s.gap = typeof props.gap === 'number' ? `${props.gap * SP}px` : props.gap;
275
+ if (props.flexShrink !== undefined) s.flexShrink = props.flexShrink;
276
+ if (props.columnGap !== undefined) s.columnGap = typeof props.columnGap === 'number' ? `${props.columnGap * SP}px` : props.columnGap;
277
+ if (props.gridColumn !== undefined) s.gridColumn = typeof props.gridColumn === 'number' ? String(props.gridColumn) : props.gridColumn;
278
+ if (props.gridRow) s.gridRow = props.gridRow;
279
+ if (props.justifySelf) s.justifySelf = props.justifySelf;
280
+ if (props.alignSelf) s.alignSelf = props.alignSelf;
281
+ if (props.h) s.height = props.h;
282
+ if (props.w) s.width = props.w;
283
+ if (props.display) s.display = props.display;
284
+ if (props.borderBottomRightRadius !== undefined) s.borderBottomRightRadius = typeof props.borderBottomRightRadius === 'number' ? `${props.borderBottomRightRadius}px` : props.borderBottomRightRadius;
285
+ if (props.borderTopRightRadius !== undefined) s.borderTopRightRadius = typeof props.borderTopRightRadius === 'number' ? `${props.borderTopRightRadius}px` : props.borderTopRightRadius;
286
+ return Object.keys(s).length > 0 ? s : undefined;
287
+ }
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // Button
291
+ // ---------------------------------------------------------------------------
292
+
293
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
294
+ function Button(props, ref) {
295
+ const {
296
+ className,
297
+ variant = 'solid',
298
+ size = 'md',
299
+ asChild = false,
300
+ loading = false,
301
+ loadingText,
302
+ loadingSkeleton = false,
303
+ expanded,
304
+ selected,
305
+ highlighted,
306
+ disabled,
307
+ children,
308
+ style: styleProp,
309
+ // Strip Chakra style props
310
+ mt, ml, mr, px, py,
311
+ fontWeight, gap, flexShrink,
312
+ columnGap, gridColumn, gridRow,
313
+ justifySelf, alignSelf, textStyle: _textStyle,
314
+ h, w, display,
315
+ borderBottomRightRadius, borderTopRightRadius,
316
+ ...rest
317
+ } = props;
318
+
319
+ const mergedStyle = buildShimStyle({
320
+ style: styleProp, mt, ml, mr, px, py, fontWeight, gap, flexShrink,
321
+ columnGap, gridColumn, gridRow, justifySelf, alignSelf, h, w, display,
322
+ borderBottomRightRadius, borderTopRightRadius,
323
+ });
324
+
325
+ const dataAttrs: Record<string, true> = {};
326
+ if (expanded) dataAttrs['data-expanded'] = true;
327
+ if (selected) dataAttrs['data-selected'] = true;
328
+ if (highlighted) dataAttrs['data-highlighted'] = true;
329
+ if (loadingSkeleton) dataAttrs['data-loading-skeleton'] = true;
330
+
331
+ const isDisabled = !loadingSkeleton && (loading || disabled);
332
+
333
+ const inner = loading ? (
334
+ <>
335
+ <Spinner />
336
+ {loadingText != null && <span style={{ marginLeft: 8 }}>{loadingText}</span>}
337
+ </>
338
+ ) : (
339
+ children
340
+ );
341
+
342
+ const variantClass = VARIANT_CLASSES[variant] ?? VARIANT_CLASSES.solid;
343
+ const combinedClassName = [BASE_CLASSES, variantClass, className].filter(Boolean).join(' ');
344
+
345
+ const button = (
346
+ <ButtonFrame
347
+ ref={ref as any}
348
+ size={size as any}
349
+ disabled={isDisabled || undefined}
350
+ asChild={asChild || undefined}
351
+ className={combinedClassName}
352
+ style={mergedStyle}
353
+ {...dataAttrs}
354
+ {...(rest as any)}
355
+ >
356
+ {inner}
357
+ </ButtonFrame>
358
+ );
359
+
360
+ if (loadingSkeleton) {
361
+ return (
362
+ <Skeleton loading={loadingSkeleton} asChild ref={ref as React.ForwardedRef<HTMLDivElement>}>
363
+ {button}
364
+ </Skeleton>
365
+ );
366
+ }
367
+
368
+ return button;
369
+ },
370
+ );
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // buttonVariants — compatibility shim for consumers that call buttonVariants()
374
+ // Returns the className string for a given variant + size combo.
375
+ // ---------------------------------------------------------------------------
376
+
377
+ const SIZE_CLASSES: Record<string, string> = {
378
+ '2xs': 'px-2 h-5 min-w-5 text-xs rounded-sm gap-1',
379
+ xs: 'px-2 h-6 min-w-6 text-sm rounded-sm gap-1',
380
+ sm: 'px-3 h-8 min-w-8 text-sm rounded-md gap-1',
381
+ md: 'px-3 h-10 min-w-10 text-base rounded-md gap-2',
382
+ };
383
+
384
+ export function buttonVariants(opts?: { variant?: string; size?: string }): string {
385
+ const v = opts?.variant ?? 'solid';
386
+ const s = opts?.size ?? 'md';
387
+ return [BASE_CLASSES, VARIANT_CLASSES[v] ?? '', SIZE_CLASSES[s] ?? ''].filter(Boolean).join(' ');
388
+ }
389
+
390
+ // ---------------------------------------------------------------------------
391
+ // ButtonGroup
392
+ // ---------------------------------------------------------------------------
393
+
394
+ export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
395
+
396
+ export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
397
+ function ButtonGroup(props, ref) {
398
+ const { className, ...rest } = props;
399
+ return (
400
+ <div
401
+ ref={ref}
402
+ className={['inline-flex', className].filter(Boolean).join(' ')}
403
+ role="group"
404
+ {...rest}
405
+ />
406
+ );
407
+ },
408
+ );
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // ButtonGroupRadio
412
+ // ---------------------------------------------------------------------------
413
+
414
+ export interface ButtonGroupRadioProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
415
+ children: Array<React.ReactElement<ButtonProps>>;
416
+ onChange?: (value: string) => void;
417
+ defaultValue?: string;
418
+ loading?: boolean;
419
+ equalWidth?: boolean;
420
+ variant?: ButtonProps['variant'];
421
+ }
422
+
423
+ export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadioProps>(
424
+ function ButtonGroupRadio(props, ref) {
425
+ const {
426
+ children,
427
+ onChange,
428
+ variant = 'segmented',
429
+ defaultValue,
430
+ loading = false,
431
+ equalWidth = false,
432
+ className,
433
+ ...rest
434
+ } = props;
435
+
436
+ const firstChildValue = React.useMemo(() => {
437
+ const firstChild = Array.isArray(children) ? children[0] : undefined;
438
+ return typeof firstChild?.props.value === 'string' ? firstChild.props.value : undefined;
439
+ }, [children]);
440
+
441
+ const [value, setValue] = React.useState<string | undefined>(defaultValue ?? firstChildValue);
442
+
443
+ const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
444
+ const v = event.currentTarget.value;
445
+ setValue(v);
446
+ onChange?.(v);
447
+ }, [onChange]);
448
+
449
+ const clonedChildren = React.Children.map(children, (child: React.ReactElement<ButtonProps>) => {
450
+ return React.cloneElement(child, {
451
+ onClick: handleItemClick,
452
+ selected: value === child.props.value,
453
+ variant,
454
+ });
455
+ });
456
+
457
+ const childrenLength = React.Children.count(children);
458
+
459
+ return (
460
+ <Skeleton loading={loading}>
461
+ <div
462
+ ref={ref}
463
+ className={[
464
+ 'inline-flex gap-0',
465
+ equalWidth ? 'grid' : '',
466
+ className,
467
+ ].filter(Boolean).join(' ')}
468
+ style={equalWidth ? { gridTemplateColumns: `repeat(${childrenLength}, 1fr)` } : undefined}
469
+ role="group"
470
+ {...rest}
471
+ >
472
+ {clonedChildren}
473
+ </div>
474
+ </Skeleton>
475
+ );
476
+ },
477
+ );