@adea-ai/ui 0.116.0 → 0.118.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 (222) hide show
  1. package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
  2. package/dist/components/composites/action-button/action-button.js +1 -0
  3. package/dist/components/composites/action-button/action-button.js.map +1 -1
  4. package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -1
  5. package/dist/components/composites/list-row/list-row-control.js +1 -0
  6. package/dist/components/composites/list-row/list-row-control.js.map +1 -1
  7. package/dist/components/composites/list-row/list-row.d.ts.map +1 -1
  8. package/dist/components/composites/list-row/list-row.js +16 -9
  9. package/dist/components/composites/list-row/list-row.js.map +1 -1
  10. package/dist/components/composites/settings/settings-navigation.d.ts.map +1 -1
  11. package/dist/components/composites/settings/settings-navigation.js +15 -6
  12. package/dist/components/composites/settings/settings-navigation.js.map +1 -1
  13. package/dist/components/composites/settings/settings.d.ts +3 -2
  14. package/dist/components/composites/settings/settings.d.ts.map +1 -1
  15. package/dist/components/composites/settings/settings.js +3 -2
  16. package/dist/components/composites/settings/settings.js.map +1 -1
  17. package/dist/components/conversation/transcript-composition.d.ts +6 -0
  18. package/dist/components/conversation/transcript-composition.d.ts.map +1 -1
  19. package/dist/components/conversation/transcript-composition.js +9 -4
  20. package/dist/components/conversation/transcript-composition.js.map +1 -1
  21. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +2 -0
  22. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -1
  23. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +3 -0
  24. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -1
  25. package/dist/components/layout/contextual-sidebar/mobile-contextual-sidebar.d.ts.map +1 -1
  26. package/dist/components/layout/contextual-sidebar/mobile-contextual-sidebar.js +1 -1
  27. package/dist/components/layout/contextual-sidebar/mobile-contextual-sidebar.js.map +1 -1
  28. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +2 -0
  29. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -1
  30. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +3 -1
  31. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -1
  32. package/dist/components/ui/alert/alert.d.ts +7 -0
  33. package/dist/components/ui/alert/alert.d.ts.map +1 -1
  34. package/dist/components/ui/alert/alert.js +18 -1
  35. package/dist/components/ui/alert/alert.js.map +1 -1
  36. package/dist/components/ui/alert/index.d.ts +1 -1
  37. package/dist/components/ui/alert/index.d.ts.map +1 -1
  38. package/dist/components/ui/alert/index.js +2 -2
  39. package/dist/components/ui/badge/badge.d.ts.map +1 -1
  40. package/dist/components/ui/badge/badge.js +1 -0
  41. package/dist/components/ui/badge/badge.js.map +1 -1
  42. package/dist/components/ui/breadcrumb/breadcrumb.d.ts +6 -0
  43. package/dist/components/ui/breadcrumb/breadcrumb.d.ts.map +1 -1
  44. package/dist/components/ui/breadcrumb/breadcrumb.js +19 -8
  45. package/dist/components/ui/breadcrumb/breadcrumb.js.map +1 -1
  46. package/dist/components/ui/breadcrumb/index.d.ts +1 -1
  47. package/dist/components/ui/breadcrumb/index.d.ts.map +1 -1
  48. package/dist/components/ui/breadcrumb/index.js +2 -2
  49. package/dist/components/ui/button/button.d.ts.map +1 -1
  50. package/dist/components/ui/button/button.js +1 -0
  51. package/dist/components/ui/button/button.js.map +1 -1
  52. package/dist/components/ui/card/card.d.ts +19 -9
  53. package/dist/components/ui/card/card.d.ts.map +1 -1
  54. package/dist/components/ui/card/card.js +21 -3
  55. package/dist/components/ui/card/card.js.map +1 -1
  56. package/dist/components/ui/card/index.d.ts +1 -1
  57. package/dist/components/ui/card/index.d.ts.map +1 -1
  58. package/dist/components/ui/card/index.js +2 -2
  59. package/dist/components/ui/checkbox/checkbox.d.ts.map +1 -1
  60. package/dist/components/ui/checkbox/checkbox.js +1 -0
  61. package/dist/components/ui/checkbox/checkbox.js.map +1 -1
  62. package/dist/components/ui/dialog/dialog.d.ts +15 -0
  63. package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
  64. package/dist/components/ui/dialog/dialog.js +11 -3
  65. package/dist/components/ui/dialog/dialog.js.map +1 -1
  66. package/dist/components/ui/dialog/index.d.ts +1 -1
  67. package/dist/components/ui/dialog/index.d.ts.map +1 -1
  68. package/dist/components/ui/entity-icon/entity-icon.d.ts +10 -0
  69. package/dist/components/ui/entity-icon/entity-icon.d.ts.map +1 -1
  70. package/dist/components/ui/entity-icon/entity-icon.js +11 -2
  71. package/dist/components/ui/entity-icon/entity-icon.js.map +1 -1
  72. package/dist/components/ui/field/field.d.ts +11 -1
  73. package/dist/components/ui/field/field.d.ts.map +1 -1
  74. package/dist/components/ui/field/field.js +2 -2
  75. package/dist/components/ui/field/field.js.map +1 -1
  76. package/dist/components/ui/field/index.d.ts +1 -1
  77. package/dist/components/ui/field/index.d.ts.map +1 -1
  78. package/dist/components/ui/input-group/index.d.ts +1 -1
  79. package/dist/components/ui/input-group/index.d.ts.map +1 -1
  80. package/dist/components/ui/input-group/input-group.d.ts +8 -1
  81. package/dist/components/ui/input-group/input-group.d.ts.map +1 -1
  82. package/dist/components/ui/input-group/input-group.js +16 -20
  83. package/dist/components/ui/input-group/input-group.js.map +1 -1
  84. package/dist/components/ui/native-select/native-select.d.ts +8 -1
  85. package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
  86. package/dist/components/ui/native-select/native-select.js +9 -2
  87. package/dist/components/ui/native-select/native-select.js.map +1 -1
  88. package/dist/components/ui/progress/progress.d.ts.map +1 -1
  89. package/dist/components/ui/progress/progress.js +6 -3
  90. package/dist/components/ui/progress/progress.js.map +1 -1
  91. package/dist/components/ui/radio-group/radio-group.d.ts +18 -3
  92. package/dist/components/ui/radio-group/radio-group.d.ts.map +1 -1
  93. package/dist/components/ui/radio-group/radio-group.js +32 -7
  94. package/dist/components/ui/radio-group/radio-group.js.map +1 -1
  95. package/dist/components/ui/resizable/resizable.d.ts +7 -2
  96. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  97. package/dist/components/ui/resizable/resizable.js +7 -6
  98. package/dist/components/ui/resizable/resizable.js.map +1 -1
  99. package/dist/components/ui/select/select.d.ts.map +1 -1
  100. package/dist/components/ui/select/select.js +10 -7
  101. package/dist/components/ui/select/select.js.map +1 -1
  102. package/dist/components/ui/sheet/sheet.d.ts +1 -0
  103. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  104. package/dist/components/ui/sheet/sheet.js +27 -2
  105. package/dist/components/ui/sheet/sheet.js.map +1 -1
  106. package/dist/components/ui/slider/slider.d.ts.map +1 -1
  107. package/dist/components/ui/slider/slider.js +6 -3
  108. package/dist/components/ui/slider/slider.js.map +1 -1
  109. package/dist/components/ui/status-chip/status-chip.d.ts.map +1 -1
  110. package/dist/components/ui/status-chip/status-chip.js +1 -1
  111. package/dist/components/ui/status-chip/status-chip.js.map +1 -1
  112. package/dist/components/ui/switch/switch.d.ts.map +1 -1
  113. package/dist/components/ui/switch/switch.js +1 -0
  114. package/dist/components/ui/switch/switch.js.map +1 -1
  115. package/dist/components/ui/tabs/tabs.d.ts +18 -0
  116. package/dist/components/ui/tabs/tabs.d.ts.map +1 -1
  117. package/dist/components/ui/tabs/tabs.js +22 -4
  118. package/dist/components/ui/tabs/tabs.js.map +1 -1
  119. package/dist/components/ui/toggle/toggle.d.ts.map +1 -1
  120. package/dist/components/ui/toggle/toggle.js +9 -6
  121. package/dist/components/ui/toggle/toggle.js.map +1 -1
  122. package/dist/components/ui/toggle-group/toggle-group.d.ts.map +1 -1
  123. package/dist/components/ui/toggle-group/toggle-group.js +2 -0
  124. package/dist/components/ui/toggle-group/toggle-group.js.map +1 -1
  125. package/dist/components/ui/typography/typography.d.ts +12 -1
  126. package/dist/components/ui/typography/typography.d.ts.map +1 -1
  127. package/dist/components/ui/typography/typography.js +16 -2
  128. package/dist/components/ui/typography/typography.js.map +1 -1
  129. package/dist/index.js +4 -4
  130. package/dist/lib/cn-tables.generated.js +5 -5
  131. package/dist/lib/cn-tables.generated.js.map +1 -1
  132. package/dist/lib/tokens.d.ts.map +1 -1
  133. package/dist/lib/tokens.js +7 -2
  134. package/dist/lib/tokens.js.map +1 -1
  135. package/dist/r/list-row.json +1 -0
  136. package/dist/r/registry.json +1 -0
  137. package/dist/r/src/components/composites/action-button/action-button.tsx +3 -0
  138. package/dist/r/src/components/composites/list-row/list-row-control.tsx +1 -0
  139. package/dist/r/src/components/composites/list-row/list-row.tsx +3 -2
  140. package/dist/r/src/components/composites/settings/settings-navigation.tsx +7 -3
  141. package/dist/r/src/components/composites/settings/settings.tsx +3 -2
  142. package/dist/r/src/components/conversation/transcript-composition.tsx +14 -2
  143. package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +3 -0
  144. package/dist/r/src/components/layout/contextual-sidebar/mobile-contextual-sidebar.tsx +6 -1
  145. package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +3 -1
  146. package/dist/r/src/components/ui/alert/alert.tsx +17 -0
  147. package/dist/r/src/components/ui/alert/index.ts +8 -1
  148. package/dist/r/src/components/ui/badge/badge.tsx +1 -0
  149. package/dist/r/src/components/ui/breadcrumb/breadcrumb.tsx +18 -8
  150. package/dist/r/src/components/ui/breadcrumb/index.ts +1 -0
  151. package/dist/r/src/components/ui/button/button.tsx +1 -0
  152. package/dist/r/src/components/ui/card/card.tsx +30 -13
  153. package/dist/r/src/components/ui/card/index.ts +1 -0
  154. package/dist/r/src/components/ui/checkbox/checkbox.tsx +1 -0
  155. package/dist/r/src/components/ui/dialog/dialog.tsx +26 -2
  156. package/dist/r/src/components/ui/dialog/index.ts +1 -0
  157. package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +15 -0
  158. package/dist/r/src/components/ui/field/field.tsx +23 -3
  159. package/dist/r/src/components/ui/field/index.ts +1 -0
  160. package/dist/r/src/components/ui/input-group/index.ts +1 -0
  161. package/dist/r/src/components/ui/input-group/input-group.tsx +28 -5
  162. package/dist/r/src/components/ui/native-select/native-select.tsx +20 -3
  163. package/dist/r/src/components/ui/progress/progress.tsx +5 -1
  164. package/dist/r/src/components/ui/radio-group/radio-group.tsx +40 -3
  165. package/dist/r/src/components/ui/resizable/resizable.tsx +15 -4
  166. package/dist/r/src/components/ui/select/select.tsx +1 -0
  167. package/dist/r/src/components/ui/sheet/sheet.tsx +20 -2
  168. package/dist/r/src/components/ui/slider/slider.tsx +1 -0
  169. package/dist/r/src/components/ui/status-chip/status-chip.tsx +1 -0
  170. package/dist/r/src/components/ui/switch/switch.tsx +1 -0
  171. package/dist/r/src/components/ui/tabs/tabs.tsx +33 -4
  172. package/dist/r/src/components/ui/toggle/toggle.tsx +1 -0
  173. package/dist/r/src/components/ui/toggle-group/toggle-group.tsx +2 -0
  174. package/dist/r/src/components/ui/typography/typography.tsx +20 -1
  175. package/dist/r/src/lib/cn-tables.generated.ts +5 -5
  176. package/dist/r/src/lib/tokens.ts +7 -2
  177. package/dist/r/src/styles/base.css +4 -1
  178. package/dist/r/src/styles/theme.css +4 -0
  179. package/package.json +1 -1
  180. package/registry.json +1 -0
  181. package/src/components/composites/action-button/action-button.tsx +3 -0
  182. package/src/components/composites/list-row/list-row-control.tsx +1 -0
  183. package/src/components/composites/list-row/list-row.tsx +3 -2
  184. package/src/components/composites/settings/settings-navigation.tsx +7 -3
  185. package/src/components/composites/settings/settings.tsx +3 -2
  186. package/src/components/conversation/transcript-composition.tsx +14 -2
  187. package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +3 -0
  188. package/src/components/layout/contextual-sidebar/mobile-contextual-sidebar.tsx +6 -1
  189. package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +3 -1
  190. package/src/components/ui/alert/alert.tsx +17 -0
  191. package/src/components/ui/alert/index.ts +8 -1
  192. package/src/components/ui/badge/badge.tsx +1 -0
  193. package/src/components/ui/breadcrumb/breadcrumb.tsx +18 -8
  194. package/src/components/ui/breadcrumb/index.ts +1 -0
  195. package/src/components/ui/button/button.tsx +1 -0
  196. package/src/components/ui/card/card.tsx +30 -13
  197. package/src/components/ui/card/index.ts +1 -0
  198. package/src/components/ui/checkbox/checkbox.tsx +1 -0
  199. package/src/components/ui/dialog/dialog.tsx +26 -2
  200. package/src/components/ui/dialog/index.ts +1 -0
  201. package/src/components/ui/entity-icon/entity-icon.tsx +15 -0
  202. package/src/components/ui/field/field.tsx +23 -3
  203. package/src/components/ui/field/index.ts +1 -0
  204. package/src/components/ui/input-group/index.ts +1 -0
  205. package/src/components/ui/input-group/input-group.tsx +28 -5
  206. package/src/components/ui/native-select/native-select.tsx +20 -3
  207. package/src/components/ui/progress/progress.tsx +5 -1
  208. package/src/components/ui/radio-group/radio-group.tsx +40 -3
  209. package/src/components/ui/resizable/resizable.tsx +15 -4
  210. package/src/components/ui/select/select.tsx +1 -0
  211. package/src/components/ui/sheet/sheet.tsx +20 -2
  212. package/src/components/ui/slider/slider.tsx +1 -0
  213. package/src/components/ui/status-chip/status-chip.tsx +1 -0
  214. package/src/components/ui/switch/switch.tsx +1 -0
  215. package/src/components/ui/tabs/tabs.tsx +33 -4
  216. package/src/components/ui/toggle/toggle.tsx +1 -0
  217. package/src/components/ui/toggle-group/toggle-group.tsx +2 -0
  218. package/src/components/ui/typography/typography.tsx +20 -1
  219. package/src/lib/cn-tables.generated.ts +5 -5
  220. package/src/lib/tokens.ts +7 -2
  221. package/src/styles/base.css +4 -1
  222. package/src/styles/theme.css +4 -0
@@ -93,6 +93,7 @@ export function Button<T extends ValidComponent = 'button'>(props: ButtonProps<T
93
93
  <Polymorphic
94
94
  as="button"
95
95
  type="button"
96
+ data-slot="button"
96
97
  class={cn(
97
98
  buttonVariants({
98
99
  variant: local.variant,
@@ -1,5 +1,6 @@
1
- import type { ComponentProps, JSX } from 'solid-js'
1
+ import type { ComponentProps } from 'solid-js'
2
2
  import { splitProps } from 'solid-js'
3
+ import { cva, type VariantProps } from '#lib/variants'
3
4
  import { cn } from '#lib/utils'
4
5
  import { headingVariants } from '../typography'
5
6
 
@@ -15,18 +16,36 @@ import { headingVariants } from '../typography'
15
16
  * The parts are separate components rather than a template because a card's
16
17
  * header, content and footer have different padding on purpose: the header and
17
18
  * footer are tight, the content breathes.
19
+ *
20
+ * `size` is the card's padding rung, so a caller never sets `p-*` or `gap-*` on
21
+ * a card to make it denser. `sm` tightens the card and its header, content and
22
+ * footer together (the parts' inline padding follows the card, so they stay
23
+ * aligned with each other); `flush` removes the block padding and gap for a
24
+ * card whose body is a list of rows or a scroll area that runs edge to edge.
18
25
  */
19
- export function Card(props: ComponentProps<'div'>) {
20
- const [local, rest] = splitProps(props, ['class'])
26
+ export const cardVariants = cva(
27
+ 'bg-card text-card-foreground flex flex-col rounded-xl border border-border shadow-xs',
28
+ {
29
+ variants: {
30
+ size: {
31
+ /** 16px block padding and gap; the parts carry 16px inline padding. */
32
+ default: 'gap-4 py-4',
33
+ /** 12px everywhere: a dense settings or status card. */
34
+ sm: 'gap-3 py-3 *:data-[slot=card-content]:px-3 *:data-[slot=card-footer]:px-3 *:data-[slot=card-header]:px-3',
35
+ /** No block padding or gap: the card hosts a row list or a scroller. */
36
+ flush: 'gap-0 py-0',
37
+ },
38
+ },
39
+ defaultVariants: { size: 'default' },
40
+ }
41
+ )
42
+
43
+ export type CardProps = ComponentProps<'div'> & VariantProps<typeof cardVariants>
44
+
45
+ export function Card(props: CardProps) {
46
+ const [local, rest] = splitProps(props, ['class', 'size'])
21
47
  return (
22
- <div
23
- data-slot="card"
24
- class={cn(
25
- 'bg-card text-card-foreground flex flex-col gap-4 rounded-xl border border-border py-4 shadow-xs',
26
- local.class
27
- )}
28
- {...rest}
29
- />
48
+ <div data-slot="card" class={cn(cardVariants({ size: local.size }), local.class)} {...rest} />
30
49
  )
31
50
  }
32
51
 
@@ -79,5 +98,3 @@ export function CardFooter(props: ComponentProps<'div'>) {
79
98
  const [local, rest] = splitProps(props, ['class'])
80
99
  return <div data-slot="card-footer" class={cn('flex items-center px-4', local.class)} {...rest} />
81
100
  }
82
-
83
- export type CardProps = ComponentProps<'div'> & JSX.HTMLAttributes<HTMLDivElement>
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  Card,
3
+ cardVariants,
3
4
  CardAction,
4
5
  CardContent,
5
6
  CardDescription,
@@ -64,6 +64,7 @@ export function Checkbox(props: CheckboxProps) {
64
64
 
65
65
  return (
66
66
  <KobalteCheckbox
67
+ data-slot="checkbox"
67
68
  id={field ? undefined : local.id}
68
69
  title={local.title}
69
70
  class={cn('group/checkbox flex items-start gap-2.5', local.class)}
@@ -271,10 +271,33 @@ export type DialogContentProps = ComponentProps<typeof KobalteDialogContent> & {
271
271
  closeButton?: JSX.Element | false
272
272
  /** Use a 1rem inset as the positioner bounds for tall, viewport-filling surfaces. */
273
273
  positioner?: 'default' | 'inset'
274
+ /**
275
+ * The panel's maximum width. `md` (32rem) is the default and right for a
276
+ * confirmation or a short form; `sm` (28rem) for a single question; `lg`
277
+ * (42rem) for a picker or a form with two columns; `xl` (48rem) for a
278
+ * multi-step flow that needs a table or a preview beside its fields.
279
+ */
280
+ size?: DialogContentSize
274
281
  }
275
282
 
283
+ /** The dialog width ladder, so a caller picks a rung instead of a `max-w-*`. */
284
+ export const dialogContentSizes = {
285
+ sm: 'max-w-md',
286
+ md: 'max-w-lg',
287
+ lg: 'max-w-2xl',
288
+ xl: 'max-w-3xl',
289
+ } as const
290
+
291
+ export type DialogContentSize = keyof typeof dialogContentSizes
292
+
276
293
  export function DialogContent(props: DialogContentProps) {
277
- const [local, rest] = splitProps(props, ['class', 'children', 'closeButton', 'positioner'])
294
+ const [local, rest] = splitProps(props, [
295
+ 'class',
296
+ 'children',
297
+ 'closeButton',
298
+ 'positioner',
299
+ 'size',
300
+ ])
278
301
 
279
302
  return (
280
303
  <KobalteDialog.Portal>
@@ -284,7 +307,8 @@ export function DialogContent(props: DialogContentProps) {
284
307
  class={cn(
285
308
  dialogSurface,
286
309
  overlayMotion,
287
- 'relative grid w-full max-w-lg gap-4 p-5',
310
+ 'relative grid w-full gap-4 p-5',
311
+ dialogContentSizes[local.size ?? 'md'],
288
312
  local.class
289
313
  )}
290
314
  {...rest}
@@ -11,4 +11,5 @@ export {
11
11
  DialogTitle,
12
12
  DialogTrigger,
13
13
  type DialogContentProps,
14
+ type DialogContentSize,
14
15
  } from './dialog'
@@ -35,6 +35,14 @@ import { cn } from '#lib/utils'
35
35
  * `shape="rounded"` is the default because a rounded square reads as an object
36
36
  * and a circle reads as a person; use `circle` only when the tile really does
37
37
  * hold a face.
38
+ *
39
+ * `monochrome` is for a third-party brand mark — a vendor logo drawn with its
40
+ * own `fill`. A brand's hex is chosen for a white page: GitHub's near-black and
41
+ * Slack's aubergine all but vanish on a dark tile, and no tone can be picked
42
+ * that suits every brand in both themes. `monochrome` repaints any explicitly
43
+ * filled shape in the tile's glyph colour (the tone's), so the mark stays
44
+ * recognisable by its shape and legible in every theme. Stroke-drawn icons
45
+ * (Lucide, `fill="none"`) are left alone.
38
46
  */
39
47
  export const entityIconVariants = cva(
40
48
  'relative inline-flex shrink-0 items-center justify-center overflow-hidden border font-semibold select-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
@@ -90,6 +98,8 @@ export type EntityIconProps = Omit<ComponentProps<'span'>, 'children'> &
90
98
  badge?: JSX.Element
91
99
  /** A second edge, drawn outside the tile — an active or selected ring. */
92
100
  ring?: boolean
101
+ /** Repaint a brand mark's own fills in the tile's glyph colour. */
102
+ monochrome?: boolean
93
103
  }
94
104
 
95
105
  /**
@@ -143,6 +153,7 @@ export function EntityIcon(props: EntityIconProps) {
143
153
  'fallback',
144
154
  'badge',
145
155
  'ring',
156
+ 'monochrome',
146
157
  'shape',
147
158
  'size',
148
159
  'tone',
@@ -159,9 +170,13 @@ export function EntityIcon(props: EntityIconProps) {
159
170
 
160
171
  return (
161
172
  <span
173
+ data-slot="entity-icon"
162
174
  class={cn(
163
175
  entityIconVariants({ shape: local.shape, size: local.size, tone: local.tone }),
164
176
  local.ring && 'ring-2 ring-ring ring-offset-2 ring-offset-background',
177
+ // A presentation attribute (`fill="#4A154B"`) loses to any CSS rule, so
178
+ // this overrides the brand's hex without touching the caller's SVG.
179
+ local.monochrome && "[&_[fill]:not([fill='none'])]:fill-current",
165
180
  local.class
166
181
  )}
167
182
  role="img"
@@ -188,10 +188,30 @@ export function FieldSet(props: ComponentProps<'fieldset'>) {
188
188
  return <fieldset class={cn('flex flex-col gap-4', local.class)} {...rest} />
189
189
  }
190
190
 
191
+ export type FieldGroupProps = ComponentProps<'div'> & {
192
+ /**
193
+ * `2` lays the fields out in two columns from the `sm` breakpoint up — a
194
+ * short form of paired values (host and port, a name and a model). A field
195
+ * that needs the full width says `class="col-span-full"`. Below `sm` it is
196
+ * one column again, because two inputs side by side on a phone are each too
197
+ * narrow to read.
198
+ */
199
+ columns?: 1 | 2
200
+ }
201
+
191
202
  /** A visual stack for related controls that does not imply fieldset semantics. */
192
- export function FieldGroup(props: ComponentProps<'div'>) {
193
- const [local, rest] = splitProps(props, ['class'])
194
- return <div data-slot="field-group" class={cn('flex flex-col gap-4', local.class)} {...rest} />
203
+ export function FieldGroup(props: FieldGroupProps) {
204
+ const [local, rest] = splitProps(props, ['class', 'columns'])
205
+ return (
206
+ <div
207
+ data-slot="field-group"
208
+ class={cn(
209
+ local.columns === 2 ? 'grid gap-4 sm:grid-cols-2' : 'flex flex-col gap-4',
210
+ local.class
211
+ )}
212
+ {...rest}
213
+ />
214
+ )
195
215
  }
196
216
 
197
217
  export function FieldLegend(props: ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
@@ -10,6 +10,7 @@ export {
10
10
  FieldSeparator,
11
11
  FieldSet,
12
12
  FieldTextArea,
13
+ type FieldGroupProps,
13
14
  type FormFieldProps,
14
15
  } from './field'
15
16
  export { NumberField } from './number-field'
@@ -5,5 +5,6 @@ export {
5
5
  InputGroupInput,
6
6
  InputGroupText,
7
7
  type InputGroupAddonProps,
8
+ type InputGroupProps,
8
9
  } from './input-group'
9
10
  export { SecretInputGroup, type SecretInputGroupProps } from './secret-input-group'
@@ -15,15 +15,29 @@ import { cn } from '#lib/utils'
15
15
  * The focus ring therefore lives on the group and follows `focus-within`. A
16
16
  * group containing several focusable things still reads as focused once, which
17
17
  * is the correct affordance: the user is somewhere inside it.
18
+ *
19
+ * `size` is the control rung, matching `Input`: `sm` for a toolbar search beside
20
+ * `icon-sm` actions, `md` (the default) everywhere else. The group's input and
21
+ * text follow it.
18
22
  */
19
- export function InputGroup(props: ComponentProps<'div'>) {
20
- const [local, rest] = splitProps(props, ['class'])
23
+ export type InputGroupProps = ComponentProps<'div'> & {
24
+ size?: 'sm' | 'md' | 'lg'
25
+ }
26
+
27
+ export function InputGroup(props: InputGroupProps) {
28
+ const [local, rest] = splitProps(props, ['class', 'size'])
21
29
 
22
30
  return (
23
31
  <div
24
32
  data-slot="input-group"
33
+ data-size={local.size}
25
34
  class={cn(
26
- 'border-input flex h-control-md w-full min-w-0 items-center gap-1.5 rounded-md border bg-transparent px-control-sm',
35
+ 'group/input-group border-input flex w-full min-w-0 items-center gap-1.5 rounded-md border bg-transparent',
36
+ {
37
+ 'h-control-sm px-control-xs': local.size === 'sm',
38
+ 'h-control-md px-control-sm': local.size === 'md' || local.size === undefined,
39
+ 'h-control-lg px-control-md': local.size === 'lg',
40
+ },
27
41
  'transition-[color,box-shadow,border-color] ease-out',
28
42
  'focus-within:border-ring focus-within:ring-3 focus-within:ring-primary-subtle',
29
43
  'has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-3 has-[[aria-invalid=true]]:ring-destructive-subtle',
@@ -64,7 +78,7 @@ export function InputGroupInput(props: ComponentProps<'input'>) {
64
78
  aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
65
79
  aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
66
80
  class={cn(
67
- 'h-full min-w-0 flex-1 bg-transparent text-sm outline-none',
81
+ 'h-full min-w-0 flex-1 bg-transparent text-sm outline-none group-data-[size=sm]/input-group:text-xs',
68
82
  'placeholder:text-muted-foreground',
69
83
  'disabled:cursor-not-allowed',
70
84
  local.class
@@ -94,6 +108,7 @@ export function InputGroupAddon(props: InputGroupAddonProps) {
94
108
  class={cn(
95
109
  'text-muted-foreground flex shrink-0 items-center gap-1.5 text-sm select-none',
96
110
  '[&_svg]:pointer-events-none [&_svg]:size-4',
111
+ 'group-data-[size=sm]/input-group:text-xs group-data-[size=sm]/input-group:[&_svg]:size-3.5',
97
112
  { 'order-first': local.align !== 'end', 'order-last': local.align === 'end' },
98
113
  local.class
99
114
  )}
@@ -130,5 +145,13 @@ export function InputGroupButton(props: ComponentProps<'button'>) {
130
145
  /** Text that reads as a value but is not an input, e.g. a unit or a suffix. */
131
146
  export function InputGroupText(props: ComponentProps<'span'> & { children?: JSX.Element }) {
132
147
  const [local, rest] = splitProps(props, ['class'])
133
- return <span class={cn('text-muted-foreground text-sm', local.class)} {...rest} />
148
+ return (
149
+ <span
150
+ class={cn(
151
+ 'text-muted-foreground text-sm group-data-[size=sm]/input-group:text-xs',
152
+ local.class
153
+ )}
154
+ {...rest}
155
+ />
156
+ )
134
157
  }
@@ -4,7 +4,14 @@ import { createEffect, For, onMount, Show, splitProps } from 'solid-js'
4
4
  import { useFormFieldControl } from '#lib/form-field'
5
5
  import { cn } from '#lib/utils'
6
6
 
7
- export type NativeSelectProps = ComponentProps<'select'> & {
7
+ export type NativeSelectProps = Omit<ComponentProps<'select'>, 'size'> & {
8
+ /**
9
+ * The control rung, matching `Input` and `SelectTrigger`: `sm` for a dense
10
+ * toolbar or pane filter, `md` (the default) everywhere else. This replaces the
11
+ * native `size` attribute, which turns a select into a list box — a different
12
+ * control that this styled single-line select is not.
13
+ */
14
+ size?: 'sm' | 'md' | 'lg'
8
15
  /** Render the choices from data instead of raw option children. */
9
16
  options?: readonly NativeSelectOption[]
10
17
  /** Initialize an uncontrolled selection after its native options mount. */
@@ -62,6 +69,7 @@ export type NativeSelectOption = Readonly<{
62
69
  export function NativeSelect(props: NativeSelectProps) {
63
70
  const [local, rest] = splitProps(props, [
64
71
  'class',
72
+ 'size',
65
73
  'children',
66
74
  'defaultValue',
67
75
  'options',
@@ -108,7 +116,13 @@ export function NativeSelect(props: NativeSelectProps) {
108
116
  aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
109
117
  aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
110
118
  class={cn(
111
- 'h-control-md min-w-0 appearance-none rounded-md border border-input bg-transparent px-control-md pr-9 text-sm',
119
+ 'min-w-0 appearance-none rounded-md border border-input bg-transparent',
120
+ {
121
+ 'h-control-sm px-control-sm pr-8 text-xs': local.size === 'sm',
122
+ 'h-control-md px-control-md pr-9 text-sm':
123
+ local.size === 'md' || local.size === undefined,
124
+ 'h-control-lg px-control-lg pr-9 text-sm': local.size === 'lg',
125
+ },
112
126
  'transition-[color,box-shadow,border-color] ease-out outline-none',
113
127
  'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
114
128
  'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',
@@ -132,7 +146,10 @@ export function NativeSelect(props: NativeSelectProps) {
132
146
  <ChevronDownIcon
133
147
  data-slot="native-select-icon"
134
148
  aria-hidden="true"
135
- class="pointer-events-none absolute end-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
149
+ class={cn(
150
+ 'pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 text-muted-foreground',
151
+ local.size === 'sm' ? 'size-3.5' : 'size-4'
152
+ )}
136
153
  />
137
154
  </span>
138
155
  )
@@ -39,7 +39,11 @@ export function Progress(props: ProgressProps) {
39
39
  ])
40
40
 
41
41
  return (
42
- <KobalteProgress class={cn('flex w-full flex-col gap-1.5', local.class)} {...rest}>
42
+ <KobalteProgress
43
+ data-slot="progress"
44
+ class={cn('flex w-full flex-col gap-1.5', local.class)}
45
+ {...rest}
46
+ >
43
47
  <Show when={local.label || !local.hideValue}>
44
48
  <div class="flex items-center justify-between gap-3">
45
49
  <Show when={local.label}>
@@ -1,6 +1,6 @@
1
1
  import { RadioGroup as KobalteRadioGroup } from '@kobalte/core/radio-group'
2
2
  import { Circle } from 'lucide-solid'
3
- import type { ComponentProps } from 'solid-js'
3
+ import type { ComponentProps, JSX } from 'solid-js'
4
4
  import { splitProps } from 'solid-js'
5
5
  import { useFormFieldControl } from '#lib/form-field'
6
6
  import { cn } from '#lib/utils'
@@ -37,6 +37,7 @@ export function RadioGroup(props: ComponentProps<typeof KobalteRadioGroup>) {
37
37
  })
38
38
  return (
39
39
  <KobalteRadioGroup
40
+ data-slot="radio-group"
40
41
  id={field?.id ?? local.id}
41
42
  aria-label={local['aria-label']}
42
43
  aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}
@@ -58,14 +59,31 @@ export type RadioGroupItemProps = ComponentProps<typeof KobalteRadioGroup.Item>
58
59
  */
59
60
  label?: string
60
61
  description?: string
62
+ /**
63
+ * `card` draws the option as a bordered, selectable card — for a choice
64
+ * whose options need a sentence each (a source type, a sync budget), where a
65
+ * bare radio row reads as a form field rather than a decision. The whole card
66
+ * is the hit target, and the selected card takes the primary edge.
67
+ */
68
+ variant?: 'default' | 'card'
69
+ /** A leading icon or `EntityIcon` between the radio and the text, for `card`. */
70
+ media?: JSX.Element
61
71
  }
62
72
 
73
+ /**
74
+ * A card option's label stretches an invisible layer over the whole card, so a
75
+ * click anywhere on it — padding, media, description — selects the option
76
+ * through the label's native association with the radio input. Wrapping the
77
+ * card in a `<label>` would nest the item's own label inside another.
78
+ */
63
79
  export function RadioGroupItem(props: RadioGroupItemProps) {
64
80
  const [local, rest] = splitProps(props, [
65
81
  'class',
66
82
  'controlClass',
67
83
  'label',
68
84
  'description',
85
+ 'variant',
86
+ 'media',
69
87
  'children',
70
88
  'aria-label',
71
89
  'aria-labelledby',
@@ -74,11 +92,23 @@ export function RadioGroupItem(props: RadioGroupItemProps) {
74
92
  'aria-busy',
75
93
  'title',
76
94
  ])
95
+ const card = () => local.variant === 'card'
77
96
 
78
97
  return (
79
98
  <KobalteRadioGroup.Item
99
+ data-slot="radio-group-item"
100
+ data-variant={card() ? 'card' : undefined}
80
101
  title={local.title}
81
- class={cn('group/radio flex items-start gap-2.5', local.class)}
102
+ class={cn(
103
+ 'group/radio flex items-start gap-2.5',
104
+ {
105
+ 'relative rounded-lg border border-border bg-card px-3 py-2.5 transition-[color,box-shadow,border-color,background-color] ease-out hover:bg-surface-hover data-[checked]:border-primary data-[disabled]:cursor-not-allowed data-[disabled]:hover:bg-card':
106
+ card(),
107
+ 'has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-primary-subtle':
108
+ card(),
109
+ },
110
+ local.class
111
+ )}
82
112
  {...rest}
83
113
  >
84
114
  <KobalteRadioGroup.ItemInput
@@ -104,10 +134,17 @@ export function RadioGroupItem(props: RadioGroupItemProps) {
104
134
  <Circle class="size-2 fill-current" />
105
135
  </KobalteRadioGroup.ItemIndicator>
106
136
  </KobalteRadioGroup.ItemControl>
137
+ {local.media ? (
138
+ <span data-slot="radio-group-item-media" class="flex shrink-0 items-center">
139
+ {local.media}
140
+ </span>
141
+ ) : null}
107
142
  {local.children ?? (
108
143
  <div class="grid gap-0.5 leading-none">
109
144
  {local.label ? (
110
- <KobalteRadioGroup.ItemLabel class="text-sm font-medium">
145
+ <KobalteRadioGroup.ItemLabel
146
+ class={cn('text-sm font-medium', card() && 'after:absolute after:inset-0')}
147
+ >
111
148
  {local.label}
112
149
  </KobalteRadioGroup.ItemLabel>
113
150
  ) : null}
@@ -70,8 +70,13 @@ export function ResizablePanel(props: ComponentProps<typeof Resizable.Panel> & {
70
70
 
71
71
  export type ResizableHandleProps = ComponentProps<typeof Resizable.Handle> & {
72
72
  class?: string
73
- /** Draw a visible grip in the middle of the divider. */
74
- withHandle?: boolean
73
+ /**
74
+ * Draw a visible grip in the middle of the divider: `true` is the bordered
75
+ * chip with its grip icon; `'rung'` is a quieter translucent bar with no
76
+ * icon, for edges that should read as barely there. Either way the divider
77
+ * keeps its full 24px hit target.
78
+ */
79
+ withHandle?: boolean | 'rung'
75
80
  /**
76
81
  * The divider's accessible name. It is a focusable control — arrow keys move
77
82
  * the split — so it needs one; without it a screen reader announces an
@@ -97,7 +102,8 @@ export function ResizableHandle(props: ResizableHandleProps) {
97
102
  a hairline, but a hairline is far too small to grab. 24px on the
98
103
  drag axis meets WCAG 2.5.8's minimum target. */
99
104
  'after:absolute after:inset-y-0 after:-inset-x-3 after:w-6',
100
- 'transition-colors ease-out hover:bg-primary',
105
+ 'transition-colors ease-out',
106
+ local.withHandle === 'rung' ? 'hover:bg-primary/45' : 'hover:bg-primary',
101
107
  'focus-visible:bg-primary focus-visible:outline-none',
102
108
  'data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full',
103
109
  'data-[orientation=vertical]:after:inset-x-0 data-[orientation=vertical]:after:-inset-y-3 data-[orientation=vertical]:after:h-6 data-[orientation=vertical]:after:w-full',
@@ -105,7 +111,12 @@ export function ResizableHandle(props: ResizableHandleProps) {
105
111
  )}
106
112
  {...rest}
107
113
  >
108
- {local.withHandle ? (
114
+ {local.withHandle === 'rung' ? (
115
+ <div
116
+ data-slot="resizable-grip"
117
+ class="z-(--z-docked) h-11 w-1.5 flex-none rounded-full bg-border/55 group-data-[orientation=vertical]/resize:h-1.5 group-data-[orientation=vertical]/resize:w-11"
118
+ />
119
+ ) : local.withHandle ? (
109
120
  <div class="bg-border z-(--z-docked) flex h-4 w-2.5 items-center justify-center rounded-sm border border-border">
110
121
  <GripVertical class="size-2.5 text-muted-foreground" />
111
122
  </div>
@@ -46,6 +46,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
46
46
 
47
47
  return (
48
48
  <KobalteSelect.Trigger
49
+ data-slot="select-trigger"
49
50
  class={cn(
50
51
  'border-input bg-transparent flex w-fit items-center justify-between gap-2 rounded-md border px-control-md',
51
52
  'whitespace-nowrap transition-[color,box-shadow,border-color] ease-out outline-none',
@@ -60,6 +60,16 @@ const sheetVariants = cva(
60
60
  edge: '',
61
61
  inset: 'panel-dialog-inset w-lg rounded-xl border',
62
62
  },
63
+ /**
64
+ * `panel` gives a start or end sheet the shell's panel width
65
+ * (`--panel-width`, 390px) — the width a pane that collapses into a sheet
66
+ * on a narrow window already had beside the work, so its content does not
67
+ * reflow when it moves. Top and bottom sheets span the window either way.
68
+ */
69
+ size: {
70
+ default: '',
71
+ panel: '',
72
+ },
63
73
  },
64
74
  compoundVariants: [
65
75
  {
@@ -82,8 +92,15 @@ const sheetVariants = cva(
82
92
  variant: 'edge',
83
93
  class: 'inset-y-0 end-0 h-full w-80 max-w-[85vw] rounded-s-xl border-s',
84
94
  },
95
+ {
96
+ side: ['start', 'end'],
97
+ variant: 'edge',
98
+ size: 'panel',
99
+ class: 'w-panel max-w-[92vw]',
100
+ },
101
+ { side: ['start', 'end'], variant: 'inset', size: 'panel', class: 'w-panel' },
85
102
  ],
86
- defaultVariants: { side: 'end', variant: 'edge' },
103
+ defaultVariants: { side: 'end', variant: 'edge', size: 'default' },
87
104
  }
88
105
  )
89
106
 
@@ -107,6 +124,7 @@ export function SheetContent(props: SheetContentProps) {
107
124
  'class',
108
125
  'side',
109
126
  'variant',
127
+ 'size',
110
128
  'children',
111
129
  'closeButton',
112
130
  'closeLabel',
@@ -134,7 +152,7 @@ export function SheetContent(props: SheetContentProps) {
134
152
  <KobalteDialogContent
135
153
  data-variant={variant()}
136
154
  class={cn(
137
- sheetVariants({ side: local.side, variant: variant() }),
155
+ sheetVariants({ side: local.side, variant: variant(), size: local.size }),
138
156
  'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:duration-200',
139
157
  'data-closed:animate-out data-closed:fade-out-0 data-closed:duration-150',
140
158
  local.class
@@ -42,6 +42,7 @@ export function Slider(props: SliderProps) {
42
42
 
43
43
  return (
44
44
  <KobalteSlider
45
+ data-slot="slider"
45
46
  class={cn(
46
47
  'relative flex touch-none flex-col justify-center select-none',
47
48
  'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-5 data-[orientation=horizontal]:w-full',
@@ -90,6 +90,7 @@ export function StatusChip(props: StatusChipProps) {
90
90
 
91
91
  const chip = (
92
92
  <span
93
+ data-slot="status-chip"
93
94
  class={cn(
94
95
  'inline-flex w-fit items-center gap-1.5 text-xs font-medium whitespace-nowrap',
95
96
  labelTone[local.tone ?? 'neutral'],
@@ -64,6 +64,7 @@ export function Switch(props: SwitchProps) {
64
64
 
65
65
  return (
66
66
  <KobalteSwitch
67
+ data-slot="switch"
67
68
  id={field ? undefined : local.id}
68
69
  title={local.title}
69
70
  class={cn('group/switch flex items-center gap-2.5', local.class)}