@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.
Files changed (118) hide show
  1. package/README.md +166 -0
  2. package/package.json +67 -0
  3. package/src/components/actions/Button.tsx +612 -0
  4. package/src/components/actions/ButtonGroup.tsx +728 -0
  5. package/src/components/actions/Copy.tsx +98 -0
  6. package/src/components/actions/DarkModeToggle.tsx +80 -0
  7. package/src/components/actions/Link.tsx +37 -0
  8. package/src/components/actions/index.ts +19 -0
  9. package/src/components/actions/useCopyToClipboard.ts +90 -0
  10. package/src/components/charts/Chart.tsx +331 -0
  11. package/src/components/charts/Sparkline.tsx +156 -0
  12. package/src/components/charts/index.ts +13 -0
  13. package/src/components/data-display/Avatar.tsx +208 -0
  14. package/src/components/data-display/AvatarGroup.tsx +228 -0
  15. package/src/components/data-display/Badge.tsx +70 -0
  16. package/src/components/data-display/Carousel.tsx +214 -0
  17. package/src/components/data-display/ColorSwatch.tsx +56 -0
  18. package/src/components/data-display/DataTable.tsx +886 -0
  19. package/src/components/data-display/EmptyState.tsx +61 -0
  20. package/src/components/data-display/Image.tsx +277 -0
  21. package/src/components/data-display/Kbd.tsx +114 -0
  22. package/src/components/data-display/Persona.tsx +78 -0
  23. package/src/components/data-display/StatCard.tsx +338 -0
  24. package/src/components/data-display/Table.tsx +147 -0
  25. package/src/components/data-display/Tag.tsx +91 -0
  26. package/src/components/data-display/Timeline.tsx +200 -0
  27. package/src/components/data-display/TreeView.tsx +172 -0
  28. package/src/components/data-display/Video.tsx +95 -0
  29. package/src/components/data-display/avatar-utils.ts +32 -0
  30. package/src/components/data-display/index.ts +81 -0
  31. package/src/components/feedback/Loading.tsx +159 -0
  32. package/src/components/feedback/Progress.tsx +321 -0
  33. package/src/components/feedback/Skeleton.tsx +62 -0
  34. package/src/components/feedback/SkeletonBlocks.tsx +222 -0
  35. package/src/components/feedback/Toast.tsx +648 -0
  36. package/src/components/feedback/index.ts +44 -0
  37. package/src/components/feedback/password/PasswordStrengthIndicator.tsx +232 -0
  38. package/src/components/feedback/password/password-strength.ts +115 -0
  39. package/src/components/feedback/password/password-validation-data.ts +66 -0
  40. package/src/components/feedback/password/password-validation.ts +93 -0
  41. package/src/components/forms/Autocomplete.tsx +268 -0
  42. package/src/components/forms/Checkbox.tsx +155 -0
  43. package/src/components/forms/CodeInput.tsx +237 -0
  44. package/src/components/forms/ColorPicker/ColorPicker.tsx +469 -0
  45. package/src/components/forms/ColorPicker/color-utils.ts +75 -0
  46. package/src/components/forms/ColorPicker/index.ts +2 -0
  47. package/src/components/forms/DatePicker.tsx +516 -0
  48. package/src/components/forms/DateRangePicker.tsx +464 -0
  49. package/src/components/forms/FieldPicker.tsx +64 -0
  50. package/src/components/forms/FileUpload.tsx +614 -0
  51. package/src/components/forms/FilterBuilder/FilterGroupBlock.ts +6 -0
  52. package/src/components/forms/FilterBuilder.tsx +16 -0
  53. package/src/components/forms/FilterRuleRow.tsx +68 -0
  54. package/src/components/forms/Input.tsx +200 -0
  55. package/src/components/forms/MultiSelect.tsx +361 -0
  56. package/src/components/forms/NumberField.tsx +145 -0
  57. package/src/components/forms/RadioGroup.tsx +135 -0
  58. package/src/components/forms/RelativeDateDefaultInput.tsx +62 -0
  59. package/src/components/forms/ReorderableList.tsx +163 -0
  60. package/src/components/forms/Select.tsx +268 -0
  61. package/src/components/forms/Slider.tsx +260 -0
  62. package/src/components/forms/Switch.tsx +135 -0
  63. package/src/components/forms/TextArea.tsx +202 -0
  64. package/src/components/forms/ViewCustomizer.tsx +44 -0
  65. package/src/components/forms/index.ts +43 -0
  66. package/src/components/layout/Accordion.tsx +110 -0
  67. package/src/components/layout/Alert.tsx +156 -0
  68. package/src/components/layout/BlockQuote.tsx +70 -0
  69. package/src/components/layout/Card.tsx +166 -0
  70. package/src/components/layout/CodeBlock/CodeBlock.tsx +477 -0
  71. package/src/components/layout/CodeBlock/code-block-tokens.css +104 -0
  72. package/src/components/layout/CodeBlock/prism.ts +81 -0
  73. package/src/components/layout/Collapsible.tsx +84 -0
  74. package/src/components/layout/Container.tsx +55 -0
  75. package/src/components/layout/Divider.tsx +64 -0
  76. package/src/components/layout/Form.tsx +39 -0
  77. package/src/components/layout/FormActions.tsx +50 -0
  78. package/src/components/layout/Grid.tsx +53 -0
  79. package/src/components/layout/PageHeading.tsx +46 -0
  80. package/src/components/layout/PromptWithAction.tsx +49 -0
  81. package/src/components/layout/Section.tsx +60 -0
  82. package/src/components/layout/TablePanel.tsx +24 -0
  83. package/src/components/layout/TableView/TableView.tsx +1018 -0
  84. package/src/components/layout/TableView/index.ts +3 -0
  85. package/src/components/layout/TableView/types.ts +51 -0
  86. package/src/components/layout/WizardStep.tsx +40 -0
  87. package/src/components/layout/WizardStepper.tsx +173 -0
  88. package/src/components/layout/index.ts +96 -0
  89. package/src/components/navigation/Breadcrumbs.tsx +66 -0
  90. package/src/components/navigation/DropdownMenu.tsx +86 -0
  91. package/src/components/navigation/MegaMenu.tsx +480 -0
  92. package/src/components/navigation/NavigationMenu.tsx +305 -0
  93. package/src/components/navigation/Pagination.tsx +298 -0
  94. package/src/components/navigation/Sidebar.tsx +280 -0
  95. package/src/components/navigation/Tabs.tsx +122 -0
  96. package/src/components/navigation/ViewSwitcher.tsx +314 -0
  97. package/src/components/navigation/index.ts +66 -0
  98. package/src/components/overlays/AlertDialog.tsx +174 -0
  99. package/src/components/overlays/ContextMenu.tsx +65 -0
  100. package/src/components/overlays/Dialog.tsx +279 -0
  101. package/src/components/overlays/Drawer.tsx +370 -0
  102. package/src/components/overlays/HoverCard.tsx +107 -0
  103. package/src/components/overlays/Popover.tsx +73 -0
  104. package/src/components/overlays/Tooltip.tsx +31 -0
  105. package/src/components/overlays/index.ts +71 -0
  106. package/src/components/typography/Code.tsx +72 -0
  107. package/src/components/typography/Icon.tsx +36 -0
  108. package/src/components/typography/index.ts +10 -0
  109. package/src/env.d.ts +9 -0
  110. package/src/index.ts +13 -0
  111. package/src/styles/theme.css +226 -0
  112. package/src/types/avatar-types.ts +11 -0
  113. package/src/types/filter-types.ts +35 -0
  114. package/src/utilities/classNames.ts +6 -0
  115. package/src/utilities/componentSize.ts +46 -0
  116. package/src/utilities/i18n.tsx +60 -0
  117. package/src/utilities/mergeRefs.ts +12 -0
  118. package/src/utilities/relativeDateDefault.ts +14 -0
@@ -0,0 +1,728 @@
1
+ import {
2
+
3
+ type JSX,
4
+
5
+ children,
6
+
7
+ createContext,
8
+
9
+ createEffect,
10
+
11
+ createSignal,
12
+
13
+ splitProps,
14
+
15
+ useContext,
16
+
17
+ For,
18
+
19
+ } from 'solid-js'
20
+
21
+ import { DropdownMenu as KobalteDropdownMenu, type DropdownMenuRootProps as KobalteDropdownMenuRootProps } from '@kobalte/core/dropdown-menu'
22
+
23
+ import { ToggleGroup as KobalteToggleGroup } from '@kobalte/core/toggle-group'
24
+
25
+ import { ChevronDown } from 'lucide-solid'
26
+
27
+ import { cn } from '../../utilities/classNames'
28
+
29
+ import { Button } from './Button'
30
+
31
+ import type { ButtonProps, ButtonSize, ButtonVariant } from './Button'
32
+
33
+
34
+
35
+ export interface ToggleGroupOption {
36
+
37
+ /** Value passed to `onChange` when this option is selected. */
38
+
39
+ value: string
40
+
41
+ /** Display text rendered inside the toggle item. */
42
+
43
+ label: string
44
+
45
+ }
46
+
47
+
48
+
49
+ /* open/onOpenChange are Partial<Pick<...>> rather than declared directly so that
50
+
51
+ the types stay in sync with Kobalte's DropdownMenuRootProps if the API changes. */
52
+
53
+ type ButtonGroupPropsBase = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onChange'> &
54
+
55
+ Partial<Pick<KobalteDropdownMenuRootProps, 'open' | 'onOpenChange'>> & {
56
+
57
+ class?: string
58
+
59
+ children?: JSX.Element
60
+
61
+ /** Enable split-button mode (main action + dropdown trigger). */
62
+
63
+ split?: boolean
64
+
65
+ /** Button size inherited by split-mode children. Default: "md" */
66
+
67
+ size?: ButtonSize
68
+
69
+ /** Button variant inherited by split-mode children. Default: "primary" */
70
+
71
+ variant?: ButtonVariant
72
+
73
+ /** Use filled-variant child dividers (white/20 instead of ink borders). */
74
+
75
+ filled?: boolean
76
+
77
+ /** Toggle-group mode: array of selectable options. Requires `value` and `onChange`. */
78
+
79
+ options?: ToggleGroupOption[]
80
+
81
+ /** Toggle-group layout direction. Default: "horizontal" */
82
+
83
+ orientation?: 'horizontal' | 'vertical'
84
+
85
+ /** Disable all buttons in the group. */
86
+
87
+ disabled?: boolean
88
+
89
+ /** Split mode: aria-label for the role="group" wrapper. Default: "Split button" */
90
+
91
+ splitButtonAriaLabel?: string
92
+
93
+ }
94
+
95
+
96
+
97
+ export type ButtonGroupProps =
98
+
99
+ | (ButtonGroupPropsBase & { multiple?: false; value?: string; onChange?: (value: string) => void })
100
+
101
+ | (ButtonGroupPropsBase & { multiple: true; value?: string[]; onChange?: (value: string[]) => void })
102
+
103
+
104
+
105
+ const ButtonGroupSplitContext = createContext<{ size: ButtonSize; variant: ButtonVariant; disabled: boolean }>({
106
+
107
+ size: 'md',
108
+
109
+ variant: 'primary',
110
+
111
+ disabled: false,
112
+
113
+ })
114
+
115
+
116
+
117
+ /* Match Button iconOnlySizes so split trigger is same height as main button. */
118
+
119
+ const splitTriggerSizes: Record<ButtonSize, string> = {
120
+
121
+ xs: 'h-8 w-8 p-0',
122
+
123
+ sm: 'h-10 w-10 p-0',
124
+
125
+ md: 'h-11 w-11 p-0',
126
+
127
+ lg: 'h-14 w-14 p-0',
128
+
129
+ }
130
+
131
+
132
+
133
+ /* Split trigger variant styles. These intentionally mirror buttonVariants in Button.tsx
134
+
135
+ for the subset of properties relevant to the dropdown chevron. If a variant's hover/active
136
+
137
+ colors change in Button, update the corresponding entry here as well. */
138
+
139
+ const splitTriggerVariants: Record<ButtonVariant, string> = {
140
+
141
+ primary:
142
+
143
+ 'bg-primary-500 text-white border-white/20 hover:bg-primary-600 active:bg-primary-700',
144
+
145
+ 'primary-outline':
146
+
147
+ 'bg-transparent text-primary-500 border-surface-border hover:bg-primary-500/10 active:bg-primary-500/20',
148
+
149
+ secondary:
150
+
151
+ 'bg-ink-200 text-ink-800 border-ink-300 hover:bg-ink-300 dark:hover:bg-ink-600',
152
+
153
+ outlined:
154
+
155
+ 'bg-transparent text-primary-500 border-surface-border hover:bg-primary-500/10 active:bg-primary-500/20',
156
+
157
+ ghost:
158
+
159
+ 'bg-transparent text-primary-500 border-surface-border hover:bg-primary-500/10 active:bg-primary-500/20',
160
+
161
+ link: 'bg-transparent text-primary-500 border-surface-border hover:bg-primary-500/10',
162
+
163
+ danger:
164
+
165
+ 'bg-danger-500 text-white border-white/20 hover:bg-danger-600 active:bg-danger-700',
166
+
167
+ 'danger-outline':
168
+
169
+ 'bg-transparent text-danger-500 border-surface-border hover:bg-danger-500/10 active:bg-danger-500/20',
170
+
171
+ 'danger-link':
172
+
173
+ 'bg-transparent text-danger-500 border-surface-border hover:bg-danger-500/10',
174
+
175
+ success:
176
+
177
+ 'bg-success-500 text-white border-white/20 hover:bg-success-600 active:bg-success-700',
178
+
179
+ 'success-outline':
180
+
181
+ 'bg-transparent text-success-500 border-surface-border hover:bg-success-500/10 active:bg-success-500/20',
182
+
183
+ warning:
184
+
185
+ 'bg-warning-500 text-white border-white/20 hover:bg-warning-600 active:bg-warning-700',
186
+
187
+ 'warning-outline':
188
+
189
+ 'bg-transparent text-warning-500 border-surface-border hover:bg-warning-500/10 active:bg-warning-500/20',
190
+
191
+ info:
192
+
193
+ 'bg-sky-600 text-white border-white/20 hover:bg-sky-700 active:bg-sky-800 dark:bg-sky-500 dark:hover:bg-sky-600 dark:active:bg-sky-700',
194
+
195
+ 'info-outline':
196
+
197
+ 'bg-transparent text-sky-700 border-surface-border hover:bg-sky-500/10 active:bg-sky-500/20 dark:text-sky-300 dark:hover:bg-sky-400/10 dark:active:bg-sky-400/20',
198
+
199
+ }
200
+
201
+
202
+
203
+ /* Slot pattern for split-button menu content. ButtonGroup.Menu returns a plain
204
+
205
+ object (not a component); the root detects it via the symbol and calls
206
+
207
+ render() when rendering the dropdown. Opaque to Solid's children() so the
208
+
209
+ menu render function isn't invoked during child resolution. */
210
+
211
+ const BUTTON_GROUP_MENU_SYMBOL = Symbol.for('ButtonGroup.Menu')
212
+
213
+ export interface ButtonGroupMenuSlot {
214
+
215
+ [BUTTON_GROUP_MENU_SYMBOL]: true
216
+
217
+ render: () => JSX.Element
218
+
219
+ }
220
+
221
+
222
+
223
+ function isMenuSlot(v: unknown): v is ButtonGroupMenuSlot {
224
+
225
+ return typeof v === 'object' && v !== null && BUTTON_GROUP_MENU_SYMBOL in v
226
+
227
+ }
228
+
229
+
230
+
231
+ /** Resolve a menu slot, render function, or plain JSX element. */
232
+
233
+ function resolveSlot(value: unknown): JSX.Element {
234
+
235
+ if (isMenuSlot(value)) return value.render()
236
+
237
+ if (typeof value === 'function') return (value as () => JSX.Element)()
238
+
239
+ return value as JSX.Element
240
+
241
+ }
242
+
243
+
244
+
245
+ function ButtonGroupMenuRenderer(props: { content: unknown }) {
246
+
247
+ return <>{resolveSlot(props.content)}</>
248
+
249
+ }
250
+
251
+
252
+
253
+ const groupBaseClasses =
254
+
255
+ 'inline-flex rounded-lg border border-surface-border overflow-hidden [&>*]:!shadow-none'
256
+
257
+
258
+
259
+ const groupChildClasses =
260
+
261
+ '[&>*]:!rounded-none [&>*]:!border-0 [&>*]:!border-r [&>*]:!border-surface-border [&>*:last-child]:!border-r-0 [&>*:first-child]:!rounded-l-lg [&>*:last-child]:!rounded-r-lg'
262
+
263
+
264
+
265
+ const groupChildClassesFilled =
266
+
267
+ '[&>*]:!rounded-none [&>*]:!border-0 [&>*]:!border-r [&>*]:!border-white/20 [&>*:last-child]:!border-r-0 [&>*:first-child]:!rounded-l-lg [&>*:last-child]:!rounded-r-lg'
268
+
269
+
270
+
271
+ const groupChildClassesVertical =
272
+
273
+ 'flex-col [&>*]:!rounded-none [&>*]:!border-0 [&>*]:!border-b [&>*]:!border-surface-border [&>*:last-child]:!border-b-0 [&>*:first-child]:!rounded-t-lg [&>*:last-child]:!rounded-b-lg'
274
+
275
+
276
+
277
+ const toggleItemClass = cn(
278
+
279
+ 'inline-flex items-center justify-center px-4 py-2 text-sm font-medium transition-colors outline-none',
280
+
281
+ 'bg-transparent text-ink-700 hover:bg-surface-overlay',
282
+
283
+ 'data-[pressed]:bg-primary-500 data-[pressed]:text-white',
284
+
285
+ 'data-[pressed]:hover:bg-primary-600 data-[pressed]:hover:text-white',
286
+
287
+ 'data-[pressed]:!border-white/20',
288
+
289
+ 'focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-inset',
290
+
291
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
292
+
293
+ 'data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed'
294
+
295
+ )
296
+
297
+
298
+
299
+ /**
300
+
301
+ * Groups buttons into a single visual unit. Supports three modes:
302
+
303
+ * default (inline group), split (primary action + dropdown), and
304
+
305
+ * toggle (single/multi-select option bar via `options`).
306
+
307
+ */
308
+
309
+ export function ButtonGroupRoot(props: ButtonGroupProps) {
310
+
311
+ const [local, others] = splitProps(props, [
312
+
313
+ 'class',
314
+
315
+ 'children',
316
+
317
+ 'split',
318
+
319
+ 'size',
320
+
321
+ 'variant',
322
+
323
+ 'filled',
324
+
325
+ 'options',
326
+
327
+ 'value',
328
+
329
+ 'onChange',
330
+
331
+ 'multiple',
332
+
333
+ 'orientation',
334
+
335
+ 'disabled',
336
+
337
+ 'splitButtonAriaLabel',
338
+
339
+ 'open',
340
+
341
+ 'onOpenChange',
342
+
343
+ ])
344
+
345
+
346
+
347
+ // Mode determined once at creation. Switching after mount is not supported.
348
+
349
+ const mode: 'toggle' | 'split' | 'default' =
350
+
351
+ local.options != null ? 'toggle'
352
+
353
+ : local.split === true ? 'split'
354
+
355
+ : 'default'
356
+
357
+
358
+
359
+ // All reactive primitives unconditional — Solid tracks ownership by call
360
+
361
+ // order, so conditional creation can cause issues with HMR / Suspense.
362
+
363
+ const [internalOpen, setInternalOpen] = createSignal(false)
364
+
365
+ const isControlled = () => local.open !== undefined
366
+
367
+ const open = () => (isControlled() ? (local.open as boolean) : internalOpen())
368
+
369
+ const setOpen = (next: boolean) => {
370
+
371
+ if (local.disabled && next) return
372
+
373
+ if (!isControlled()) setInternalOpen(next)
374
+
375
+ local.onOpenChange?.(next)
376
+
377
+ }
378
+
379
+ // children() resolves slot objects returned by ButtonGroupMenu. The slot
380
+
381
+ // pattern relies on Solid's children() returning raw component return values
382
+
383
+ // (including non-DOM objects) — this is documented Solid behavior but worth
384
+
385
+ // noting for future Solid version upgrades.
386
+
387
+ const resolved = children(() => local.children)
388
+
389
+ const list = () => {
390
+
391
+ const c = resolved()
392
+
393
+ return Array.isArray(c) ? c : c ? [c] : []
394
+
395
+ }
396
+
397
+ const main = () => list()[0]
398
+
399
+ const menuContent = () => list()[1]
400
+
401
+ const size = () => (local.size ?? 'md') as ButtonSize
402
+
403
+ const variant = () => (local.variant ?? 'primary') as ButtonVariant
404
+
405
+ const toggleOrientation = () => local.orientation ?? 'horizontal'
406
+
407
+ const toggleRootClass = () =>
408
+
409
+ cn(
410
+
411
+ groupBaseClasses,
412
+
413
+ toggleOrientation() === 'vertical' ? groupChildClassesVertical : groupChildClasses,
414
+
415
+ local.class
416
+
417
+ )
418
+
419
+
420
+
421
+ if (import.meta.env.DEV) {
422
+
423
+ createEffect(() => {
424
+
425
+ if (mode === 'split' && list().length < 2) {
426
+
427
+ console.warn(
428
+
429
+ 'ButtonGroup: split mode expects <ButtonGroup.Main> and <ButtonGroup.Menu> as children.'
430
+
431
+ )
432
+
433
+ }
434
+
435
+ })
436
+
437
+ }
438
+
439
+
440
+
441
+ if (mode === 'toggle') {
442
+
443
+ const toggleChildren = () => (
444
+
445
+ <For each={local.options}>
446
+
447
+ {(opt) => (
448
+
449
+ <KobalteToggleGroup.Item value={opt.value} aria-label={opt.label} class={toggleItemClass}>
450
+
451
+ {opt.label}
452
+
453
+ </KobalteToggleGroup.Item>
454
+
455
+ )}
456
+
457
+ </For>
458
+
459
+ )
460
+
461
+ if (local.multiple) {
462
+
463
+ return (
464
+
465
+ <KobalteToggleGroup
466
+
467
+ value={(local.value as string[] | undefined) ?? []}
468
+
469
+ onChange={local.onChange as (v: string[]) => void}
470
+
471
+ multiple
472
+
473
+ orientation={toggleOrientation()}
474
+
475
+ disabled={local.disabled}
476
+
477
+ class={toggleRootClass()}
478
+
479
+ >
480
+
481
+ {toggleChildren()}
482
+
483
+ </KobalteToggleGroup>
484
+
485
+ )
486
+
487
+ }
488
+
489
+ return (
490
+
491
+ <KobalteToggleGroup
492
+
493
+ value={(local.value as string | undefined) ?? null}
494
+
495
+ onChange={local.onChange as (v: string | null) => void}
496
+
497
+ orientation={toggleOrientation()}
498
+
499
+ disabled={local.disabled}
500
+
501
+ class={toggleRootClass()}
502
+
503
+ >
504
+
505
+ {toggleChildren()}
506
+
507
+ </KobalteToggleGroup>
508
+
509
+ )
510
+
511
+ }
512
+
513
+
514
+
515
+ if (mode === 'split') {
516
+
517
+ return (
518
+
519
+ <ButtonGroupSplitContext.Provider
520
+
521
+ value={{ get size() { return size() }, get variant() { return variant() }, get disabled() { return !!local.disabled } }}
522
+
523
+ >
524
+
525
+ <KobalteDropdownMenu open={open()} onOpenChange={setOpen}>
526
+
527
+ <div
528
+
529
+ data-torchui="button-group"
530
+
531
+ role="group"
532
+
533
+ aria-label={local.splitButtonAriaLabel ?? 'Split button'}
534
+
535
+ class={cn(groupBaseClasses, local.class)}
536
+
537
+ {...others}
538
+
539
+ >
540
+
541
+ {main()}
542
+
543
+ <KobalteDropdownMenu.Trigger
544
+
545
+ as="button"
546
+
547
+ type="button"
548
+
549
+ disabled={local.disabled}
550
+
551
+ class={cn(
552
+
553
+ 'inline-flex shrink-0 items-center justify-center rounded-none border-l',
554
+
555
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500',
556
+
557
+ splitTriggerVariants[variant()],
558
+
559
+ splitTriggerSizes[size()],
560
+
561
+ local.disabled && 'opacity-50 cursor-not-allowed'
562
+
563
+ )}
564
+
565
+ aria-label="Open menu"
566
+
567
+ >
568
+
569
+ <ChevronDown class="h-4 w-4" />
570
+
571
+ </KobalteDropdownMenu.Trigger>
572
+
573
+ </div>
574
+
575
+ <KobalteDropdownMenu.Portal>
576
+
577
+ <KobalteDropdownMenu.Content
578
+
579
+ class={cn(
580
+
581
+ 'z-50 min-w-[160px] rounded-lg border border-surface-border bg-surface-raised p-1 shadow-lg'
582
+
583
+ )}
584
+
585
+ >
586
+
587
+ <ButtonGroupMenuRenderer content={menuContent()} />
588
+
589
+ </KobalteDropdownMenu.Content>
590
+
591
+ </KobalteDropdownMenu.Portal>
592
+
593
+ </KobalteDropdownMenu>
594
+
595
+ </ButtonGroupSplitContext.Provider>
596
+
597
+ )
598
+
599
+ }
600
+
601
+
602
+
603
+ const childClasses = () =>
604
+
605
+ local.filled ? groupChildClassesFilled : groupChildClasses
606
+
607
+ return (
608
+
609
+ <div
610
+
611
+ data-torchui="button-group"
612
+
613
+ role="group"
614
+
615
+ class={cn(groupBaseClasses, childClasses(), local.class)}
616
+
617
+ {...others}
618
+
619
+ >
620
+
621
+ {local.children}
622
+
623
+ </div>
624
+
625
+ )
626
+
627
+ }
628
+
629
+
630
+
631
+ export interface ButtonGroupMainProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
632
+
633
+ /** Override the variant inherited from ButtonGroup. */
634
+
635
+ variant?: ButtonVariant
636
+
637
+ /** Override the size inherited from ButtonGroup. */
638
+
639
+ size?: ButtonSize
640
+
641
+ class?: string
642
+
643
+ children?: JSX.Element
644
+
645
+ }
646
+
647
+
648
+
649
+ /** Primary action button in split mode. Inherits size/variant from the parent ButtonGroup. */
650
+
651
+ export function ButtonGroupMain(props: ButtonGroupMainProps) {
652
+
653
+ const ctx = useContext(ButtonGroupSplitContext)
654
+
655
+ // onChange must be extracted here to prevent it from spreading into Button via
656
+
657
+ // {...others}. Button treats the presence of onChange + pressed as a signal to
658
+
659
+ // activate toggle mode, so leaking onChange would cause unintended behavior.
660
+
661
+ const [local, others] = splitProps(props, ['variant', 'size', 'class', 'children', 'onChange'])
662
+
663
+ return (
664
+
665
+ <Button
666
+
667
+ variant={local.variant ?? ctx.variant}
668
+
669
+ size={local.size ?? ctx.size}
670
+
671
+ disabled={others.disabled ?? ctx.disabled}
672
+
673
+ disableElevation
674
+
675
+ class={cn('!rounded-r-none !border-r-0', local.class)}
676
+
677
+ {...(others as ButtonProps)}
678
+
679
+ >
680
+
681
+ {local.children}
682
+
683
+ </Button>
684
+
685
+ )
686
+
687
+ }
688
+
689
+
690
+
691
+ export interface ButtonGroupMenuProps {
692
+
693
+ /** Dropdown content rendered when the split trigger is clicked. Accepts JSX or a render function. */
694
+
695
+ children?: JSX.Element | (() => JSX.Element)
696
+
697
+ }
698
+
699
+
700
+
701
+ /** Returns a plain object (slot), not JSX — used as second child of ButtonGroup when split. Cast to JSX.Element so TS accepts <ButtonGroup.Menu> in JSX. */
702
+
703
+ export function ButtonGroupMenu(props: ButtonGroupMenuProps): JSX.Element {
704
+
705
+ const child = props.children
706
+
707
+ const render =
708
+
709
+ typeof child === 'function'
710
+
711
+ ? (child as () => JSX.Element)
712
+
713
+ : () => (child as JSX.Element) ?? null
714
+
715
+ return { [BUTTON_GROUP_MENU_SYMBOL]: true, render } as unknown as JSX.Element
716
+
717
+ }
718
+
719
+
720
+
721
+ export const ButtonGroup = Object.assign(ButtonGroupRoot, {
722
+
723
+ Main: ButtonGroupMain,
724
+
725
+ Menu: ButtonGroupMenu,
726
+
727
+ })
728
+