@torch-ui/solid 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -0
- package/package.json +67 -0
- package/src/components/actions/Button.tsx +612 -0
- package/src/components/actions/ButtonGroup.tsx +728 -0
- package/src/components/actions/Copy.tsx +98 -0
- package/src/components/actions/DarkModeToggle.tsx +80 -0
- package/src/components/actions/Link.tsx +37 -0
- package/src/components/actions/index.ts +19 -0
- package/src/components/actions/useCopyToClipboard.ts +90 -0
- package/src/components/charts/Chart.tsx +331 -0
- package/src/components/charts/Sparkline.tsx +156 -0
- package/src/components/charts/index.ts +13 -0
- package/src/components/data-display/Avatar.tsx +208 -0
- package/src/components/data-display/AvatarGroup.tsx +228 -0
- package/src/components/data-display/Badge.tsx +70 -0
- package/src/components/data-display/Carousel.tsx +214 -0
- package/src/components/data-display/ColorSwatch.tsx +56 -0
- package/src/components/data-display/DataTable.tsx +886 -0
- package/src/components/data-display/EmptyState.tsx +61 -0
- package/src/components/data-display/Image.tsx +277 -0
- package/src/components/data-display/Kbd.tsx +114 -0
- package/src/components/data-display/Persona.tsx +78 -0
- package/src/components/data-display/StatCard.tsx +338 -0
- package/src/components/data-display/Table.tsx +147 -0
- package/src/components/data-display/Tag.tsx +91 -0
- package/src/components/data-display/Timeline.tsx +200 -0
- package/src/components/data-display/TreeView.tsx +172 -0
- package/src/components/data-display/Video.tsx +95 -0
- package/src/components/data-display/avatar-utils.ts +32 -0
- package/src/components/data-display/index.ts +81 -0
- package/src/components/feedback/Loading.tsx +159 -0
- package/src/components/feedback/Progress.tsx +321 -0
- package/src/components/feedback/Skeleton.tsx +62 -0
- package/src/components/feedback/SkeletonBlocks.tsx +222 -0
- package/src/components/feedback/Toast.tsx +648 -0
- package/src/components/feedback/index.ts +44 -0
- package/src/components/feedback/password/PasswordStrengthIndicator.tsx +232 -0
- package/src/components/feedback/password/password-strength.ts +115 -0
- package/src/components/feedback/password/password-validation-data.ts +66 -0
- package/src/components/feedback/password/password-validation.ts +93 -0
- package/src/components/forms/Autocomplete.tsx +268 -0
- package/src/components/forms/Checkbox.tsx +155 -0
- package/src/components/forms/CodeInput.tsx +237 -0
- package/src/components/forms/ColorPicker/ColorPicker.tsx +469 -0
- package/src/components/forms/ColorPicker/color-utils.ts +75 -0
- package/src/components/forms/ColorPicker/index.ts +2 -0
- package/src/components/forms/DatePicker.tsx +516 -0
- package/src/components/forms/DateRangePicker.tsx +464 -0
- package/src/components/forms/FieldPicker.tsx +64 -0
- package/src/components/forms/FileUpload.tsx +614 -0
- package/src/components/forms/FilterBuilder/FilterGroupBlock.ts +6 -0
- package/src/components/forms/FilterBuilder.tsx +16 -0
- package/src/components/forms/FilterRuleRow.tsx +68 -0
- package/src/components/forms/Input.tsx +200 -0
- package/src/components/forms/MultiSelect.tsx +361 -0
- package/src/components/forms/NumberField.tsx +145 -0
- package/src/components/forms/RadioGroup.tsx +135 -0
- package/src/components/forms/RelativeDateDefaultInput.tsx +62 -0
- package/src/components/forms/ReorderableList.tsx +163 -0
- package/src/components/forms/Select.tsx +268 -0
- package/src/components/forms/Slider.tsx +260 -0
- package/src/components/forms/Switch.tsx +135 -0
- package/src/components/forms/TextArea.tsx +202 -0
- package/src/components/forms/ViewCustomizer.tsx +44 -0
- package/src/components/forms/index.ts +43 -0
- package/src/components/layout/Accordion.tsx +110 -0
- package/src/components/layout/Alert.tsx +156 -0
- package/src/components/layout/BlockQuote.tsx +70 -0
- package/src/components/layout/Card.tsx +166 -0
- package/src/components/layout/CodeBlock/CodeBlock.tsx +477 -0
- package/src/components/layout/CodeBlock/code-block-tokens.css +104 -0
- package/src/components/layout/CodeBlock/prism.ts +81 -0
- package/src/components/layout/Collapsible.tsx +84 -0
- package/src/components/layout/Container.tsx +55 -0
- package/src/components/layout/Divider.tsx +64 -0
- package/src/components/layout/Form.tsx +39 -0
- package/src/components/layout/FormActions.tsx +50 -0
- package/src/components/layout/Grid.tsx +53 -0
- package/src/components/layout/PageHeading.tsx +46 -0
- package/src/components/layout/PromptWithAction.tsx +49 -0
- package/src/components/layout/Section.tsx +60 -0
- package/src/components/layout/TablePanel.tsx +24 -0
- package/src/components/layout/TableView/TableView.tsx +1018 -0
- package/src/components/layout/TableView/index.ts +3 -0
- package/src/components/layout/TableView/types.ts +51 -0
- package/src/components/layout/WizardStep.tsx +40 -0
- package/src/components/layout/WizardStepper.tsx +173 -0
- package/src/components/layout/index.ts +96 -0
- package/src/components/navigation/Breadcrumbs.tsx +66 -0
- package/src/components/navigation/DropdownMenu.tsx +86 -0
- package/src/components/navigation/MegaMenu.tsx +480 -0
- package/src/components/navigation/NavigationMenu.tsx +305 -0
- package/src/components/navigation/Pagination.tsx +298 -0
- package/src/components/navigation/Sidebar.tsx +280 -0
- package/src/components/navigation/Tabs.tsx +122 -0
- package/src/components/navigation/ViewSwitcher.tsx +314 -0
- package/src/components/navigation/index.ts +66 -0
- package/src/components/overlays/AlertDialog.tsx +174 -0
- package/src/components/overlays/ContextMenu.tsx +65 -0
- package/src/components/overlays/Dialog.tsx +279 -0
- package/src/components/overlays/Drawer.tsx +370 -0
- package/src/components/overlays/HoverCard.tsx +107 -0
- package/src/components/overlays/Popover.tsx +73 -0
- package/src/components/overlays/Tooltip.tsx +31 -0
- package/src/components/overlays/index.ts +71 -0
- package/src/components/typography/Code.tsx +72 -0
- package/src/components/typography/Icon.tsx +36 -0
- package/src/components/typography/index.ts +10 -0
- package/src/env.d.ts +9 -0
- package/src/index.ts +13 -0
- package/src/styles/theme.css +226 -0
- package/src/types/avatar-types.ts +11 -0
- package/src/types/filter-types.ts +35 -0
- package/src/utilities/classNames.ts +6 -0
- package/src/utilities/componentSize.ts +46 -0
- package/src/utilities/i18n.tsx +60 -0
- package/src/utilities/mergeRefs.ts +12 -0
- package/src/utilities/relativeDateDefault.ts +14 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { type JSX, children, createEffect, splitProps } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
import { Button as KobalteButton } from '@kobalte/core/button'
|
|
4
|
+
|
|
5
|
+
import { ToggleButton as KobalteToggleButton } from '@kobalte/core/toggle-button'
|
|
6
|
+
|
|
7
|
+
import { LoaderCircle } from 'lucide-solid'
|
|
8
|
+
|
|
9
|
+
import { cn } from '../../utilities/classNames'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export type ButtonVariant =
|
|
14
|
+
|
|
15
|
+
| 'primary'
|
|
16
|
+
|
|
17
|
+
| 'primary-outline'
|
|
18
|
+
|
|
19
|
+
| 'secondary'
|
|
20
|
+
|
|
21
|
+
| 'outlined'
|
|
22
|
+
|
|
23
|
+
| 'ghost'
|
|
24
|
+
|
|
25
|
+
| 'link'
|
|
26
|
+
|
|
27
|
+
| 'danger'
|
|
28
|
+
|
|
29
|
+
| 'danger-outline'
|
|
30
|
+
|
|
31
|
+
| 'danger-link'
|
|
32
|
+
|
|
33
|
+
| 'success'
|
|
34
|
+
|
|
35
|
+
| 'success-outline'
|
|
36
|
+
|
|
37
|
+
| 'warning'
|
|
38
|
+
|
|
39
|
+
| 'warning-outline'
|
|
40
|
+
|
|
41
|
+
| 'info'
|
|
42
|
+
|
|
43
|
+
| 'info-outline'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export interface ButtonProps
|
|
52
|
+
|
|
53
|
+
extends Omit<
|
|
54
|
+
|
|
55
|
+
JSX.ButtonHTMLAttributes<HTMLButtonElement> & JSX.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
56
|
+
|
|
57
|
+
'children' | 'onChange' | 'shape'
|
|
58
|
+
|
|
59
|
+
> {
|
|
60
|
+
|
|
61
|
+
/** Visual style of the button. Default: "primary" */
|
|
62
|
+
|
|
63
|
+
variant?: ButtonVariant
|
|
64
|
+
|
|
65
|
+
/** Button size. Default: "md" */
|
|
66
|
+
|
|
67
|
+
size?: ButtonSize
|
|
68
|
+
|
|
69
|
+
/** Stretch to fill the parent width. */
|
|
70
|
+
|
|
71
|
+
fullWidth?: boolean
|
|
72
|
+
|
|
73
|
+
/** Show a spinner and disable interaction. */
|
|
74
|
+
|
|
75
|
+
loading?: boolean
|
|
76
|
+
|
|
77
|
+
/** Remove the subtle box-shadow on filled variants. */
|
|
78
|
+
|
|
79
|
+
disableElevation?: boolean
|
|
80
|
+
|
|
81
|
+
/** Render as a square icon-only button (uses `icon` or `startIcon`). */
|
|
82
|
+
|
|
83
|
+
iconOnly?: boolean
|
|
84
|
+
|
|
85
|
+
/** Corner radius. Default: "rounded" (or "circle" when iconOnly). */
|
|
86
|
+
|
|
87
|
+
radius?: 'circle' | 'rounded' | 'square'
|
|
88
|
+
|
|
89
|
+
/** Icon element for icon-only mode. Falls back to `startIcon`. */
|
|
90
|
+
|
|
91
|
+
icon?: JSX.Element
|
|
92
|
+
|
|
93
|
+
/** Icon placed before the label. */
|
|
94
|
+
|
|
95
|
+
startIcon?: JSX.Element
|
|
96
|
+
|
|
97
|
+
/** Icon placed after the label. */
|
|
98
|
+
|
|
99
|
+
endIcon?: JSX.Element
|
|
100
|
+
|
|
101
|
+
/** Text label. When set, takes priority over `children`. */
|
|
102
|
+
|
|
103
|
+
label?: string
|
|
104
|
+
|
|
105
|
+
/** When set, renders as an anchor (`<a>`) with button styling. */
|
|
106
|
+
|
|
107
|
+
href?: string
|
|
108
|
+
|
|
109
|
+
/** Controlled pressed state for toggle mode (requires `onChange`). */
|
|
110
|
+
|
|
111
|
+
pressed?: boolean
|
|
112
|
+
|
|
113
|
+
/** Toggle callback. Setting both `pressed` and `onChange` enables toggle mode. */
|
|
114
|
+
|
|
115
|
+
onChange?: (pressed: boolean) => void
|
|
116
|
+
|
|
117
|
+
children?: JSX.Element
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const filledVariants: ButtonVariant[] = ['primary', 'danger', 'success', 'warning', 'info']
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
const buttonVariants: Record<ButtonVariant, string> = {
|
|
128
|
+
|
|
129
|
+
primary:
|
|
130
|
+
|
|
131
|
+
'border-2 border-transparent bg-primary-500 text-white hover:bg-primary-600 active:bg-primary-700 focus:ring-primary-500/50',
|
|
132
|
+
|
|
133
|
+
'primary-outline':
|
|
134
|
+
|
|
135
|
+
'bg-transparent text-primary-500 border-2 border-primary-500 hover:bg-primary-500/10 active:bg-primary-500/20 focus:ring-primary-500/50',
|
|
136
|
+
|
|
137
|
+
secondary:
|
|
138
|
+
|
|
139
|
+
'border-2 border-surface-border bg-surface-raised text-ink-700 hover:bg-surface-overlay active:bg-surface-dim focus:ring-ink-500/30',
|
|
140
|
+
|
|
141
|
+
outlined:
|
|
142
|
+
|
|
143
|
+
'bg-transparent text-ink-700 border-2 border-ink-300 hover:bg-surface-overlay active:bg-surface-dim focus:ring-ink-500/30',
|
|
144
|
+
|
|
145
|
+
link: 'text-primary-500 hover:text-primary-600 hover:underline underline-offset-4 focus:ring-primary-500/50',
|
|
146
|
+
|
|
147
|
+
ghost:
|
|
148
|
+
|
|
149
|
+
'bg-transparent text-primary-500 border-2 border-transparent hover:bg-primary-500/10 active:bg-primary-500/20 focus:ring-primary-500/50',
|
|
150
|
+
|
|
151
|
+
danger:
|
|
152
|
+
|
|
153
|
+
'border-2 border-transparent bg-danger-500 text-white hover:bg-danger-600 active:bg-danger-700 focus:ring-danger-500/50',
|
|
154
|
+
|
|
155
|
+
'danger-outline':
|
|
156
|
+
|
|
157
|
+
'bg-transparent text-danger-500 border-2 border-danger-500 hover:bg-danger-500/10 active:bg-danger-500/20 focus:ring-danger-500/50',
|
|
158
|
+
|
|
159
|
+
'danger-link':
|
|
160
|
+
|
|
161
|
+
'text-danger-500 hover:text-danger-600 hover:underline underline-offset-4 focus:ring-danger-500/50',
|
|
162
|
+
|
|
163
|
+
success:
|
|
164
|
+
|
|
165
|
+
'border-2 border-transparent bg-success-500 text-white hover:bg-success-600 active:bg-success-700 focus:ring-success-500/50',
|
|
166
|
+
|
|
167
|
+
'success-outline':
|
|
168
|
+
|
|
169
|
+
'bg-transparent text-success-500 border-2 border-success-500 hover:bg-success-500/10 active:bg-success-500/20 focus:ring-success-500/50',
|
|
170
|
+
|
|
171
|
+
warning:
|
|
172
|
+
|
|
173
|
+
'border-2 border-transparent bg-warning-500 text-white hover:bg-warning-600 active:bg-warning-700 focus:ring-warning-500/50',
|
|
174
|
+
|
|
175
|
+
'warning-outline':
|
|
176
|
+
|
|
177
|
+
'bg-transparent text-warning-500 border-2 border-warning-500 hover:bg-warning-500/10 active:bg-warning-500/20 focus:ring-warning-500/50',
|
|
178
|
+
|
|
179
|
+
info:
|
|
180
|
+
|
|
181
|
+
'border-2 border-transparent bg-sky-600 text-white hover:bg-sky-700 active:bg-sky-800 focus:ring-sky-500/50 dark:bg-sky-500 dark:hover:bg-sky-600 dark:active:bg-sky-700',
|
|
182
|
+
|
|
183
|
+
'info-outline':
|
|
184
|
+
|
|
185
|
+
'bg-transparent text-sky-700 border-2 border-sky-500 hover:bg-sky-500/10 active:bg-sky-500/20 focus:ring-sky-500/50 dark:text-sky-300 dark:border-sky-400 dark:hover:bg-sky-400/10 dark:active:bg-sky-400/20',
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
/* Fixed height per size — matches inputSizeConfig heights so same size name aligns in toolbars. */
|
|
192
|
+
|
|
193
|
+
const buttonSizes: Record<ButtonSize, string> = {
|
|
194
|
+
|
|
195
|
+
xs: 'h-7 min-h-7 px-2.5 text-xs', // 28px
|
|
196
|
+
|
|
197
|
+
sm: 'h-8 min-h-8 px-3 text-sm', // 32px
|
|
198
|
+
|
|
199
|
+
md: 'h-9 min-h-9 px-4 text-sm', // 36px — default
|
|
200
|
+
|
|
201
|
+
lg: 'h-10 min-h-10 px-5 text-sm', // 40px
|
|
202
|
+
|
|
203
|
+
xl: 'h-11 min-h-11 px-6 text-base', // 44px
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/* Square dimensions for icon-only buttons so radius="circle" renders as a circle. */
|
|
210
|
+
|
|
211
|
+
const iconOnlySizes: Record<ButtonSize, string> = {
|
|
212
|
+
|
|
213
|
+
xs: 'h-7 w-7 p-0',
|
|
214
|
+
|
|
215
|
+
sm: 'h-8 w-8 p-0',
|
|
216
|
+
|
|
217
|
+
md: 'h-9 w-9 p-0',
|
|
218
|
+
|
|
219
|
+
lg: 'h-10 w-10 p-0',
|
|
220
|
+
|
|
221
|
+
xl: 'h-11 w-11 p-0',
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
/* Corner radius for radius prop: applied to all buttons (text and icon-only). */
|
|
228
|
+
|
|
229
|
+
const radiusClasses: Record<'circle' | 'rounded' | 'square', string> = {
|
|
230
|
+
|
|
231
|
+
circle: 'rounded-full',
|
|
232
|
+
|
|
233
|
+
rounded: 'rounded-lg',
|
|
234
|
+
|
|
235
|
+
square: 'rounded-none',
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
const sharedButtonClass =
|
|
242
|
+
|
|
243
|
+
'box-border inline-flex items-center justify-center gap-2 font-medium transition-all shrink-0 focus-visible:outline-none focus-visible:ring-4'
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/* Toggle mode: variant styles (outlined/ghost) with data-[pressed] state. Used when pressed + onChange are set. */
|
|
248
|
+
|
|
249
|
+
const toggleModeVariants: Record<'outlined' | 'ghost', string> = {
|
|
250
|
+
|
|
251
|
+
outlined:
|
|
252
|
+
|
|
253
|
+
'border-2 bg-surface-raised text-ink-700 border-surface-border hover:bg-surface-overlay ' +
|
|
254
|
+
|
|
255
|
+
'data-[pressed]:bg-primary-500 data-[pressed]:text-white data-[pressed]:border-primary-500 ' +
|
|
256
|
+
|
|
257
|
+
'data-[pressed]:hover:bg-primary-600 data-[pressed]:hover:border-primary-600',
|
|
258
|
+
|
|
259
|
+
ghost:
|
|
260
|
+
|
|
261
|
+
'border-2 border-transparent text-ink-700 hover:bg-surface-overlay ' +
|
|
262
|
+
|
|
263
|
+
'data-[pressed]:bg-primary-100 data-[pressed]:text-primary-800 ' +
|
|
264
|
+
|
|
265
|
+
'dark:data-[pressed]:bg-primary-500/20 dark:data-[pressed]:text-primary-200 ' +
|
|
266
|
+
|
|
267
|
+
'data-[pressed]:hover:bg-primary-200 dark:data-[pressed]:hover:bg-primary-500/30',
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
|
|
275
|
+
* Polymorphic button: renders as a standard button, anchor link, or toggle
|
|
276
|
+
|
|
277
|
+
* depending on props. Supports loading spinners, icons, and multiple variants.
|
|
278
|
+
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
export function Button(props: ButtonProps) {
|
|
282
|
+
|
|
283
|
+
const [local, others] = splitProps(props, [
|
|
284
|
+
|
|
285
|
+
'variant',
|
|
286
|
+
|
|
287
|
+
'size',
|
|
288
|
+
|
|
289
|
+
'fullWidth',
|
|
290
|
+
|
|
291
|
+
'loading',
|
|
292
|
+
|
|
293
|
+
'disabled',
|
|
294
|
+
|
|
295
|
+
'disableElevation',
|
|
296
|
+
|
|
297
|
+
'iconOnly',
|
|
298
|
+
|
|
299
|
+
'radius',
|
|
300
|
+
|
|
301
|
+
'icon',
|
|
302
|
+
|
|
303
|
+
'startIcon',
|
|
304
|
+
|
|
305
|
+
'endIcon',
|
|
306
|
+
|
|
307
|
+
'class',
|
|
308
|
+
|
|
309
|
+
'style',
|
|
310
|
+
|
|
311
|
+
'children',
|
|
312
|
+
|
|
313
|
+
'label',
|
|
314
|
+
|
|
315
|
+
'onClick',
|
|
316
|
+
|
|
317
|
+
'href',
|
|
318
|
+
|
|
319
|
+
'pressed',
|
|
320
|
+
|
|
321
|
+
'onChange',
|
|
322
|
+
|
|
323
|
+
'ref',
|
|
324
|
+
|
|
325
|
+
'type',
|
|
326
|
+
|
|
327
|
+
])
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
const resolvedChildren = children(() => local.children)
|
|
332
|
+
|
|
333
|
+
const variant = () => local.variant ?? 'primary'
|
|
334
|
+
|
|
335
|
+
const size = () => local.size ?? 'md'
|
|
336
|
+
|
|
337
|
+
const isDisabled = () => local.disabled || local.loading
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
// Mode determined once at creation. Switching after mount is not supported.
|
|
342
|
+
|
|
343
|
+
const mode: 'toggle' | 'link' | 'button' =
|
|
344
|
+
|
|
345
|
+
local.pressed !== undefined && local.onChange != null ? 'toggle'
|
|
346
|
+
|
|
347
|
+
: local.href != null && local.href !== '' ? 'link'
|
|
348
|
+
|
|
349
|
+
: 'button'
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
/* In toggle mode, only outlined and ghost are supported; default outlined. */
|
|
354
|
+
|
|
355
|
+
const toggleVariant = (): 'outlined' | 'ghost' =>
|
|
356
|
+
|
|
357
|
+
local.variant === 'ghost' ? 'ghost' : 'outlined'
|
|
358
|
+
|
|
359
|
+
if (import.meta.env.DEV) {
|
|
360
|
+
|
|
361
|
+
if (local.iconOnly) {
|
|
362
|
+
|
|
363
|
+
const a = others as Record<string, unknown>
|
|
364
|
+
|
|
365
|
+
if (local.label == null && a['aria-label'] == null && a['aria-labelledby'] == null && a['title'] == null) {
|
|
366
|
+
|
|
367
|
+
console.warn('Button: iconOnly requires label, aria-label, aria-labelledby, or title for accessibility.')
|
|
368
|
+
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
createEffect(() => {
|
|
374
|
+
|
|
375
|
+
const currentMode =
|
|
376
|
+
|
|
377
|
+
local.pressed !== undefined && local.onChange != null ? 'toggle'
|
|
378
|
+
|
|
379
|
+
: local.href != null && local.href !== '' ? 'link'
|
|
380
|
+
|
|
381
|
+
: 'button'
|
|
382
|
+
|
|
383
|
+
if (currentMode !== mode) {
|
|
384
|
+
|
|
385
|
+
console.warn(`Button: mode changed from "${mode}" to "${currentMode}" after mount. This is not supported.`)
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (mode === 'toggle' && local.variant != null && !['outlined', 'ghost'].includes(local.variant)) {
|
|
390
|
+
|
|
391
|
+
console.warn('Button: toggle mode only supports outlined and ghost variants.')
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const resolvedRadius = (): 'circle' | 'rounded' | 'square' =>
|
|
400
|
+
|
|
401
|
+
local.radius ?? (local.iconOnly ? 'circle' : 'rounded')
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
const hasElevation = () =>
|
|
406
|
+
|
|
407
|
+
!local.disableElevation && filledVariants.includes(variant())
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
const baseClass = () =>
|
|
412
|
+
|
|
413
|
+
cn(
|
|
414
|
+
|
|
415
|
+
sharedButtonClass,
|
|
416
|
+
|
|
417
|
+
buttonVariants[variant()],
|
|
418
|
+
|
|
419
|
+
local.iconOnly ? iconOnlySizes[size()] : buttonSizes[size()],
|
|
420
|
+
|
|
421
|
+
radiusClasses[resolvedRadius()],
|
|
422
|
+
|
|
423
|
+
local.fullWidth && 'w-full',
|
|
424
|
+
|
|
425
|
+
hasElevation() && 'shadow-sm hover:shadow',
|
|
426
|
+
|
|
427
|
+
isDisabled() && 'opacity-50 cursor-not-allowed pointer-events-none',
|
|
428
|
+
|
|
429
|
+
local.class
|
|
430
|
+
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
const toggleClass = () =>
|
|
436
|
+
|
|
437
|
+
cn(
|
|
438
|
+
|
|
439
|
+
sharedButtonClass,
|
|
440
|
+
|
|
441
|
+
'focus-visible:ring-primary-500/50 disabled:opacity-50 disabled:cursor-not-allowed',
|
|
442
|
+
|
|
443
|
+
toggleModeVariants[toggleVariant()],
|
|
444
|
+
|
|
445
|
+
local.iconOnly ? iconOnlySizes[size()] : buttonSizes[size()],
|
|
446
|
+
|
|
447
|
+
radiusClasses[resolvedRadius()],
|
|
448
|
+
|
|
449
|
+
local.fullWidth && 'w-full',
|
|
450
|
+
|
|
451
|
+
isDisabled() && 'opacity-50 cursor-not-allowed pointer-events-none',
|
|
452
|
+
|
|
453
|
+
local.class
|
|
454
|
+
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
// Icon props are pre-evaluated JSX at the call site — <Show> gates DOM
|
|
460
|
+
|
|
461
|
+
// insertion but cannot prevent element creation. This is a Solid design
|
|
462
|
+
|
|
463
|
+
// reality for JSX-typed props. Gating creation would require an API change
|
|
464
|
+
|
|
465
|
+
// to accept icon factories (() => JSX.Element) instead of JSX.Element.
|
|
466
|
+
|
|
467
|
+
const content = () => {
|
|
468
|
+
|
|
469
|
+
if (local.iconOnly) {
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
|
|
473
|
+
<>
|
|
474
|
+
|
|
475
|
+
{local.loading && <LoaderCircle class="h-4 w-4 shrink-0 animate-spin" />}
|
|
476
|
+
|
|
477
|
+
{!local.loading && (local.icon ?? local.startIcon)}
|
|
478
|
+
|
|
479
|
+
</>
|
|
480
|
+
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
|
|
487
|
+
<>
|
|
488
|
+
|
|
489
|
+
{local.loading && <LoaderCircle class="h-4 w-4 shrink-0 animate-spin" />}
|
|
490
|
+
|
|
491
|
+
{!local.loading && local.startIcon}
|
|
492
|
+
|
|
493
|
+
{local.label != null ? local.label : resolvedChildren()}
|
|
494
|
+
|
|
495
|
+
{local.endIcon}
|
|
496
|
+
|
|
497
|
+
</>
|
|
498
|
+
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
if (mode === 'toggle') {
|
|
506
|
+
|
|
507
|
+
return (
|
|
508
|
+
|
|
509
|
+
<KobalteToggleButton
|
|
510
|
+
|
|
511
|
+
ref={local.ref}
|
|
512
|
+
|
|
513
|
+
pressed={local.pressed}
|
|
514
|
+
|
|
515
|
+
onChange={local.onChange!}
|
|
516
|
+
|
|
517
|
+
disabled={isDisabled()}
|
|
518
|
+
|
|
519
|
+
class={toggleClass()}
|
|
520
|
+
|
|
521
|
+
style={local.style}
|
|
522
|
+
|
|
523
|
+
{...others}
|
|
524
|
+
|
|
525
|
+
>
|
|
526
|
+
|
|
527
|
+
{content()}
|
|
528
|
+
|
|
529
|
+
</KobalteToggleButton>
|
|
530
|
+
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
if (mode === 'link') {
|
|
538
|
+
|
|
539
|
+
return (
|
|
540
|
+
|
|
541
|
+
<KobalteButton
|
|
542
|
+
|
|
543
|
+
ref={local.ref}
|
|
544
|
+
|
|
545
|
+
as="a"
|
|
546
|
+
|
|
547
|
+
href={local.href}
|
|
548
|
+
|
|
549
|
+
aria-disabled={isDisabled()}
|
|
550
|
+
|
|
551
|
+
tabIndex={isDisabled() ? -1 : undefined}
|
|
552
|
+
|
|
553
|
+
class={baseClass()}
|
|
554
|
+
|
|
555
|
+
style={local.style}
|
|
556
|
+
|
|
557
|
+
onClick={(e: MouseEvent) => {
|
|
558
|
+
|
|
559
|
+
if (isDisabled()) e.preventDefault()
|
|
560
|
+
|
|
561
|
+
;(local.onClick as ((e: MouseEvent) => void) | undefined)?.(e)
|
|
562
|
+
|
|
563
|
+
}}
|
|
564
|
+
|
|
565
|
+
{...others}
|
|
566
|
+
|
|
567
|
+
>
|
|
568
|
+
|
|
569
|
+
{content()}
|
|
570
|
+
|
|
571
|
+
</KobalteButton>
|
|
572
|
+
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
return (
|
|
580
|
+
|
|
581
|
+
<KobalteButton
|
|
582
|
+
|
|
583
|
+
ref={local.ref}
|
|
584
|
+
|
|
585
|
+
as="button"
|
|
586
|
+
|
|
587
|
+
type={local.type ?? 'button'}
|
|
588
|
+
|
|
589
|
+
disabled={isDisabled()}
|
|
590
|
+
|
|
591
|
+
class={baseClass()}
|
|
592
|
+
|
|
593
|
+
style={local.style}
|
|
594
|
+
|
|
595
|
+
onClick={(e: MouseEvent) =>
|
|
596
|
+
|
|
597
|
+
(local.onClick as ((e: MouseEvent) => void) | undefined)?.(e)
|
|
598
|
+
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
{...others}
|
|
602
|
+
|
|
603
|
+
>
|
|
604
|
+
|
|
605
|
+
{content()}
|
|
606
|
+
|
|
607
|
+
</KobalteButton>
|
|
608
|
+
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
}
|
|
612
|
+
|