@jovertical/mise-design-system 1.0.0 → 1.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.
@@ -0,0 +1,712 @@
1
+ <!-- Generated by pnpm build:skill. Do not edit by hand. -->
2
+
3
+ # Forms
4
+
5
+ ## ButtonGroup
6
+
7
+ `import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants } from '@jovertical/mise-design-system'`
8
+
9
+ Adjacent controls that share one edge instead of each carrying its own radius and gap: a segmented set of buttons, or two buttons and the input they act on, read as one control.
10
+
11
+ | Prop | Type | Default | Description |
12
+ | --- | --- | --- | --- |
13
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | |
14
+
15
+ Also takes: every `<div>` attribute.
16
+
17
+ ### `ButtonGroupSeparator`
18
+
19
+ A rule between two parts of a group. Vertical by default, for a row.
20
+
21
+ Also takes: everything `Separator` takes.
22
+
23
+ ### `ButtonGroupText`
24
+
25
+ A fixed label inside a group: a unit, a prefix, a caption ahead of an action.
26
+
27
+ | Prop | Type | Default | Description |
28
+ | --- | --- | --- | --- |
29
+ | `child` | `Snippet<[{ props: Record<string, unknown> }]>` | | |
30
+
31
+ Also takes: every `<div>` attribute.
32
+
33
+ Also exports: `buttonGroupVariants`.
34
+
35
+ Types: `ButtonGroupOrientation` (`'horizontal' | 'vertical'`).
36
+
37
+ ## Calendar
38
+
39
+ `import { Calendar, RangeCalendar } from '@jovertical/mise-design-system'`
40
+
41
+ A month grid to pick a day from. The one a `DatePicker` renders inside its popover, and the one most call sites want; `type="multiple"` picks several separate days, and `RangeCalendar` a contiguous span.
42
+
43
+ | Prop | Type | Default | Description |
44
+ | --- | --- | --- | --- |
45
+ | `captionLayout` | `'label' \| 'dropdown' \| 'dropdown-months' \| 'dropdown-years'` | `'label'` | `label` names the month; the dropdown layouts swap the month, the year or both for a native select, for a date far from today (a birth date, an expiry). |
46
+
47
+ Also takes: bits-ui `Calendar.Root` props except `children`, `child`.
48
+
49
+ ### `RangeCalendar`
50
+
51
+ A contiguous span of days, for reporting windows and stay lengths. The first click sets `start`; the second sets `end` and closes the span.
52
+
53
+ | Prop | Type | Default | Description |
54
+ | --- | --- | --- | --- |
55
+ | `captionLayout` | `'label' \| 'dropdown' \| 'dropdown-months' \| 'dropdown-years'` | `'label'` | `label` names the month; the dropdown layouts swap the month, the year or both for a native select, for a date far from today (a birth date, an expiry). |
56
+
57
+ Also takes: bits-ui `RangeCalendar.Root` props except `children`, `child`.
58
+
59
+ Types: `CalendarCaptionLayout` (`'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years'`).
60
+
61
+ ## Checkbox
62
+
63
+ `import { Checkbox } from '@jovertical/mise-design-system'`
64
+
65
+ Also takes: bits-ui `Checkbox.Root` props except `children`, `child`.
66
+
67
+ ## ChoiceRow
68
+
69
+ `import { ChoiceRow, ChoiceRowReadonly } from '@jovertical/mise-design-system'`
70
+
71
+ A bordered row that is entirely clickable and highlights when its control is checked.
72
+
73
+ | Prop | Type | Default | Description |
74
+ | --- | --- | --- | --- |
75
+ | `for` (required) | `string` | | Must match the control's own `id`, or the row will not forward its click. |
76
+ | `control` (required) | `Snippet` | | A `RadioGroupItem`, a `Checkbox`: anything bits-ui drives with `data-state`. |
77
+ | `media` | `Snippet` | | Sits between the control and the label: an avatar, a logo, a product thumbnail. For identifying *who or what* the row is about, where the control says whether it is chosen. Not an icon chip: a row of glyphs beside a row of radios reads as two controls. |
78
+ | `label` (required) | `string` | | |
79
+ | `description` | `string` | | |
80
+ | `badge` | `string` | | A short qualifier beside the label: "Coming soon", "Recommended". |
81
+ | `disabled` | `boolean` | `false` | |
82
+ | `children` | `Snippet` | | Revealed under the row, outside the label, inside the same border. |
83
+
84
+ Also takes: every `<div>` attribute except `children`.
85
+
86
+ ### `ChoiceRowReadonly`
87
+
88
+ A settled choice, in the shape of the choice it was.
89
+
90
+ | Prop | Type | Default | Description |
91
+ | --- | --- | --- | --- |
92
+ | `label` (required) | `string` | | |
93
+ | `description` | `string` | | |
94
+ | `selected` (required) | `boolean` | | |
95
+
96
+ Also takes: every `<div>` attribute except `children`.
97
+
98
+ ## Combobox
99
+
100
+ `import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator, ComboboxTrigger } from '@jovertical/mise-design-system'`
101
+
102
+ A searchable select: typing into the field narrows the list. Reach for it where the list is long enough that scanning it is slower than typing (a category picker over hundreds of rows), not a five-option status field, which stays a `Select`.
103
+
104
+ | Prop | Type | Default | Description |
105
+ | --- | --- | --- | --- |
106
+ | `type` | `'single'` | `'single'` | |
107
+
108
+ Also takes: bits-ui `Combobox.Root` props except `type`; bits-ui `Combobox.Root` props.
109
+
110
+ ### `ComboboxChip`
111
+
112
+ One chosen value in `ComboboxChips`.
113
+
114
+ | Prop | Type | Default | Description |
115
+ | --- | --- | --- | --- |
116
+ | `showRemove` | `boolean` | `true` | |
117
+ | `onRemove` | `() => void` | | Called by the remove button; take the value out of the combobox's. |
118
+ | `removeLabel` | `string` | `'Remove'` | Names the remove button: "Remove Valves". |
119
+ | `disabled` | `boolean` | `false` | |
120
+
121
+ Also takes: every `<span>` attribute.
122
+
123
+ ### `ComboboxChips`
124
+
125
+ The field for a multiple combobox: the chosen values as chips, then a `ComboboxChipsInput` to type into, wrapped as one Input-like box.
126
+
127
+ Also takes: every `<div>` attribute.
128
+
129
+ ### `ComboboxChipsInput`
130
+
131
+ The text a multiple combobox is typed into, inside `ComboboxChips`.
132
+
133
+ Also takes: bits-ui `Combobox.Input` props.
134
+
135
+ ### `ComboboxContent`
136
+
137
+ The floating panel. Put a `ComboboxList` inside, and a `ComboboxEmpty` for when nothing matches.
138
+
139
+ | Prop | Type | Default | Description |
140
+ | --- | --- | --- | --- |
141
+ | `portalProps` | `ComboboxPrimitive.PortalProps` | | |
142
+
143
+ Also takes: bits-ui `Combobox.Content` props.
144
+
145
+ ### `ComboboxEmpty`
146
+
147
+ Also takes: every `<div>` attribute.
148
+
149
+ ### `ComboboxGroup`
150
+
151
+ Also takes: bits-ui `Combobox.Group` props.
152
+
153
+ ### `ComboboxInput`
154
+
155
+ The field: an `InputGroup` with the typed text, the chevron that opens the list, and optionally a clear button that takes the chevron's place while there is a value. `children` render inside the group, after the addon.
156
+
157
+ | Prop | Type | Default | Description |
158
+ | --- | --- | --- | --- |
159
+ | `showTrigger` | `boolean` | `true` | |
160
+ | `showClear` | `boolean` | `false` | |
161
+ | `children` | `Snippet` | | |
162
+
163
+ Also takes: bits-ui `Combobox.Input` props except `child` except `children`.
164
+
165
+ ### `ComboboxItem`
166
+
167
+ | Prop | Type | Default | Description |
168
+ | --- | --- | --- | --- |
169
+ | `children` | `Snippet` | | |
170
+
171
+ Also takes: bits-ui `Combobox.Item` props except `child` except `children`.
172
+
173
+ ### `ComboboxLabel`
174
+
175
+ Also takes: bits-ui `Combobox.GroupHeading` props.
176
+
177
+ ### `ComboboxList`
178
+
179
+ Also takes: bits-ui `Combobox.Viewport` props.
180
+
181
+ ### `ComboboxSeparator`
182
+
183
+ Also takes: bits-ui `Separator.Root` props.
184
+
185
+ ### `ComboboxTrigger`
186
+
187
+ The chevron that opens the list without typing. ComboboxInput draws its own.
188
+
189
+ Also takes: bits-ui `Combobox.Trigger` props.
190
+
191
+ ## DatePicker
192
+
193
+ `import { DatePicker } from '@jovertical/mise-design-system'`
194
+
195
+ A `Calendar` behind a trigger, for the case where the calendar itself would otherwise sit permanently on the page.
196
+
197
+ | Prop | Type | Default | Description |
198
+ | --- | --- | --- | --- |
199
+ | `mode` (required) | `'single' \| 'range'` | | |
200
+ | `value` | `DateValue \| DateRange` | | |
201
+ | `onValueChange` | `((value: DateValue \| undefined) => void) \| ((value: DateRange \| undefined) => void)` | | |
202
+ | `placeholder` | `string` | | |
203
+ | `disabled` | `boolean` | `false` | Disables the whole control: the trigger cannot be opened. |
204
+ | `'aria-invalid'` | `boolean` | | Marks the trigger invalid; `Button` draws the destructive border `Input` does. |
205
+ | `isDateDisabled` | `(date: DateValue) => boolean` | | Days the operator may not pick, inside an open calendar. Distinct from `disabled`, which closes the control. The rule is the caller's, not a `disablePast` flag: what counts as "past" is a decision the screen owns. |
206
+ | `minValue` | `DateValue` | | |
207
+ | `maxValue` | `DateValue` | | |
208
+ | `locale` | `string` | `'en-US'` | For the trigger's label and the calendar. `en-US` when absent. |
209
+ | `class` | `string` | | |
210
+
211
+ ## Field
212
+
213
+ `import { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle, fieldVariants } from '@jovertical/mise-design-system'`
214
+
215
+ One control with its label, description and error. `horizontal` puts the label beside the control; `responsive` does so from the field group's `md` container width.
216
+
217
+ | Prop | Type | Default | Description |
218
+ | --- | --- | --- | --- |
219
+ | `orientation` | `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | |
220
+
221
+ Also takes: every `<div>` attribute.
222
+
223
+ ### `FieldContent`
224
+
225
+ Also takes: every `<div>` attribute.
226
+
227
+ ### `FieldDescription`
228
+
229
+ Also takes: every `<p>` attribute.
230
+
231
+ ### `FieldError`
232
+
233
+ The field's error, announced as an alert. Children win; otherwise `errors` is de-duplicated by message, one shown as text and several as a list. Nothing renders when there is nothing to say.
234
+
235
+ | Prop | Type | Default | Description |
236
+ | --- | --- | --- | --- |
237
+ | `errors` | `Array<{ message?: string } \| undefined>` | | |
238
+
239
+ Also takes: every `<div>` attribute.
240
+
241
+ ### `FieldGroup`
242
+
243
+ Also takes: every `<div>` attribute.
244
+
245
+ ### `FieldLabel`
246
+
247
+ | Prop | Type | Default | Description |
248
+ | --- | --- | --- | --- |
249
+ | `required` | `boolean` | `false` | Marks the field as required: an `aria-hidden` asterisk beside a real sr-only "(required)", because a screen reader announcing "asterisk" tells you nothing. |
250
+
251
+ Also takes: everything `Label` takes.
252
+
253
+ ### `FieldLegend`
254
+
255
+ | Prop | Type | Default | Description |
256
+ | --- | --- | --- | --- |
257
+ | `variant` | `'legend' \| 'label'` | `'legend'` | |
258
+
259
+ Also takes: every `<legend>` attribute.
260
+
261
+ ### `FieldSeparator`
262
+
263
+ Also takes: every `<div>` attribute.
264
+
265
+ ### `FieldSet`
266
+
267
+ Also takes: every `<fieldset>` attribute.
268
+
269
+ ### `FieldTitle`
270
+
271
+ Also takes: every `<div>` attribute.
272
+
273
+ Also exports: `fieldVariants`.
274
+
275
+ Types: `FieldOrientation` (`'vertical' | 'horizontal' | 'responsive'`).
276
+
277
+ ## Form
278
+
279
+ `import { Form, FormButton, FormDescription, FormElementField, FormField, FormFieldErrors, FormFieldset, FormLabel, FormLegend, FormControl } from '@jovertical/mise-design-system/form'`
280
+
281
+ The `<form>` around a superforms form: `method="POST"` and superforms' `enhance`, so submitting validates on the client and posts to the page's action, or stays in the browser in SPA mode.
282
+
283
+ | Prop | Type | Default | Description |
284
+ | --- | --- | --- | --- |
285
+ | `form` (required) | `SuperForm<T>` | | |
286
+
287
+ Also takes: every `<form>` attribute.
288
+
289
+ ### `FormButton`
290
+
291
+ The form's submit button: a `Button` with `type="submit"`.
292
+
293
+ Also takes: everything `Button` takes.
294
+
295
+ ### `FormDescription`
296
+
297
+ The line under a field, which formsnap links to the control with `aria-describedby`.
298
+
299
+ Also takes: bits-ui `Form.Description` props except `child`.
300
+
301
+ ### `FormElementField`
302
+
303
+ One element of an array field (`tags[0]`), drawn as `FormField` is.
304
+
305
+ | Prop | Type | Default | Description |
306
+ | --- | --- | --- | --- |
307
+ | `form` (required) | `FsSuperForm<T>` | | |
308
+ | `name` (required) | `U` | | |
309
+ | `children` | `Snippet<[FormFieldState<T[U]>]>` | | |
310
+
311
+ Also takes: every `<div>` attribute except `children`.
312
+
313
+ ### `FormField`
314
+
315
+ One field of a superforms form: label, control, description and errors, wired to `name`, so the control is labelled, described and marked invalid from the form's validation. Reach for `Field` directly for a form with no superforms behind it; reach for this where one already is.
316
+
317
+ | Prop | Type | Default | Description |
318
+ | --- | --- | --- | --- |
319
+ | `form` (required) | `FsSuperForm<T>` | | |
320
+ | `name` (required) | `U` | | |
321
+ | `children` | `Snippet<[FormFieldState<T[U]>]>` | | |
322
+
323
+ Also takes: every `<div>` attribute except `children`.
324
+
325
+ ### `FormFieldErrors`
326
+
327
+ The field's validation errors, linked to the control, nothing while it is valid.
328
+
329
+ | Prop | Type | Default | Description |
330
+ | --- | --- | --- | --- |
331
+ | `errorClasses` | `string \| null` | | |
332
+
333
+ Also takes: bits-ui `Form.FieldErrors` props except `child`.
334
+
335
+ ### `FormFieldset`
336
+
337
+ A group of controls under one name, captioned by `FormLegend`.
338
+
339
+ Also takes: bits-ui `Form.Fieldset` props except `child`.
340
+
341
+ ### `FormLabel`
342
+
343
+ The field's label, pointed at its control and red while the field is invalid.
344
+
345
+ | Prop | Type | Default | Description |
346
+ | --- | --- | --- | --- |
347
+ | `required` | `boolean` | `false` | Adds the asterisk that marks a mandatory field. |
348
+
349
+ Also takes: bits-ui `Form.Label` props except `child`.
350
+
351
+ ### `FormLegend`
352
+
353
+ text-destructive:The caption of a `FormFieldset`, red while the group is invalid.
354
+
355
+ Also takes: bits-ui `Form.Legend` props except `child`.
356
+
357
+ Also exports: `FormControl`.
358
+
359
+ Types: `FormFieldState`.
360
+
361
+ ## Input
362
+
363
+ `import { Input } from '@jovertical/mise-design-system'`
364
+
365
+ | Prop | Type | Default | Description |
366
+ | --- | --- | --- | --- |
367
+ | `type` (required) | `'file' \| InputType` | | |
368
+ | `files` | `FileList \| undefined` | | |
369
+
370
+ Also takes: every `<input>` attribute except `type`.
371
+
372
+ ## InputGroup
373
+
374
+ `import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, inputGroupAddonVariants, inputGroupButtonVariants } from '@jovertical/mise-design-system'`
375
+
376
+ The surface matches `Input` exactly: filled, a transparent border, rounded-2xl. Stock shadcn draws a bordered white box at rounded-md, which put every grouped field out of step with the plain inputs beside it.
377
+
378
+ Also takes: every `<div>` attribute.
379
+
380
+ ### `InputGroupAddon`
381
+
382
+ A click on the addon focuses the group's input or textarea, unless it landed on a button.
383
+
384
+ | Prop | Type | Default | Description |
385
+ | --- | --- | --- | --- |
386
+ | `align` | `'inline-start' \| 'inline-end' \| 'block-start' \| 'block-end'` | `'inline-start'` | |
387
+
388
+ Also takes: every `<div>` attribute.
389
+
390
+ ### `InputGroupButton`
391
+
392
+ | Prop | Type | Default | Description |
393
+ | --- | --- | --- | --- |
394
+ | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | |
395
+
396
+ Also takes: everything `Button` takes except `size`.
397
+
398
+ ### `InputGroupInput`
399
+
400
+ Also takes: everything `Input` takes.
401
+
402
+ ### `InputGroupText`
403
+
404
+ A fixed unit or symbol rather than an interactive addon.
405
+
406
+ Also takes: every `<span>` attribute.
407
+
408
+ ### `InputGroupTextarea`
409
+
410
+ A bare `textarea`, not this package's `Textarea`. That one wraps itself in a `div` with a counter once `maxlength` is set, which breaks the group's `>textarea` selectors. A group that wants a count shows it in an addon.
411
+
412
+ Also takes: every `<textarea>` attribute.
413
+
414
+ Also exports: `inputGroupAddonVariants`, `inputGroupButtonVariants`.
415
+
416
+ Types: `InputGroupAddonAlign` (`'inline-start' | 'inline-end' | 'block-start' | 'block-end'`), `InputGroupButtonSize` (`'xs' | 'sm' | 'icon-xs' | 'icon-sm'`).
417
+
418
+ ## InputOTP
419
+
420
+ `import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@jovertical/mise-design-system'`
421
+
422
+ A one-time code or a PIN, typed into one hidden input and drawn as cells. Lay out `InputOTPGroup`s and `InputOTPSlot`s in the children snippet, one slot per cell it receives.
423
+
424
+ Also takes: bits-ui `InputOTP.Root` props.
425
+
426
+ ### `InputOTPGroup`
427
+
428
+ The field: one rounded box around its slots, drawn as `Input` is (rounded-2xl, `bg-input/50`, the same focus ring around the whole of it), so a code sits in a form of inputs as one of them. Pass `aria-invalid` here, not on the slots: the field is what turns red.
429
+
430
+ Also takes: every `<div>` attribute.
431
+
432
+ ### `InputOTPSeparator`
433
+
434
+ Also takes: every `<div>` attribute.
435
+
436
+ ### `InputOTPSlot`
437
+
438
+ | Prop | Type | Default | Description |
439
+ | --- | --- | --- | --- |
440
+ | `masked` | `boolean` | `false` | A dot for each character typed, never the character: a PIN being set. |
441
+
442
+ Also takes: bits-ui `InputOTP.Cell` props.
443
+
444
+ ## Label
445
+
446
+ `import { Label } from '@jovertical/mise-design-system'`
447
+
448
+ Also takes: bits-ui `Label.Root` props.
449
+
450
+ ## MediaDropzone
451
+
452
+ `import { MediaDropzone } from '@jovertical/mise-design-system'`
453
+
454
+ One image, on a zone that stays put: a logo, an avatar, a cover.
455
+
456
+ | Prop | Type | Default | Description |
457
+ | --- | --- | --- | --- |
458
+ | `value` | `MediaAsset \| null` | `null` | |
459
+ | `onValueChange` | `(value: MediaAsset \| null) => void` | | |
460
+ | `adapter` (required) | `MediaUploaderAdapter` | | The consumer's media service: the package makes no request of its own. |
461
+ | `linkMode` | `'immediate' \| 'deferred'` | `'deferred'` | |
462
+ | `accept` | `string` | `DEFAULT_ACCEPT` | What a native `<input accept>` takes. Images when absent. |
463
+ | `maxSize` | `number` | `DEFAULT_MAX_SIZE` | Bytes. 2MB when absent. |
464
+ | `disabled` | `boolean` | `false` | |
465
+ | `class` | `string` | | |
466
+ | `icon` | `Snippet` | | |
467
+ | `placeholder` | `string` | `'Drag and drop your image here'` | |
468
+ | `hint` | `string` | | |
469
+ | `recommendation` | `string` | | |
470
+ | `actionLabel` | `string` | `'Choose file'` | |
471
+ | `previewLabel` | `string` | | Heads the preview panel. Without it there is no panel. |
472
+ | `previewCaption` | `string` | | |
473
+ | `emptyLabel` | `string` | `'Nothing uploaded yet'` | |
474
+ | `onError` | `(error: Error) => void` | | Failures the zone recovers from on its own: an unlink that did not take, an adapter that was not ready. A failed upload keeps its message and a Retry instead. |
475
+
476
+ Types: `MediaAsset`, `MediaLinkMode` (`'immediate' | 'deferred'`), `MediaUploaderAdapter`, `MediaUploadOptions`.
477
+
478
+ ## NumberInput
479
+
480
+ `import { NumberInput, NumberInputField } from '@jovertical/mise-design-system'`
481
+
482
+ A number typed into a text field, never `type="number"`: the native one has 8px spinners drawn differently in every engine, a scroll wheel that changes a quantity under a passing pointer, "1e" accepted as a number in progress, and a selection API that throws.
483
+
484
+ | Prop | Type | Default | Description |
485
+ | --- | --- | --- | --- |
486
+ | `format` | `'integer' \| 'decimal' \| 'currency'` | `'decimal'` | `integer` takes whole numbers; `decimal` takes up to `precision` places; `currency` speaks minor units: `value` is an integer count of cents, shown with two places, and only two can be typed, because a third has no cent to land in. Everything crossing the boundary is then an integer, the units an API uses, and no price arrives as 19.99 and leaves as 1998.9999999999998. |
487
+ | `min` | `number` | `0` | The floor, in `value`'s units. A negative one lets a minus sign be typed. |
488
+ | `max` | `number` | | No ceiling when absent. |
489
+ | `step` | `number` | `1` | How far ↑, ↓ and the buttons move it, in `value`'s units. |
490
+ | `precision` | `number` | | The most decimal places `decimal` takes. No limit when absent. |
491
+ | `clearable` | `boolean` | `true` | An emptied field commits `null`. Off, it commits `min`: for a count where zero is a real answer and the field should never be blank. |
492
+ | `controls` | `boolean` | `false` | − and + either side, inside the field's own fill, so it reads as one control beside the plain inputs around it. Each disables at its end of the range. |
493
+ | `prefix` | `string \| Snippet` | | Ahead of the number: a currency symbol, a unit. |
494
+
495
+ Also takes: everything `NumberInputField` takes.
496
+
497
+ ### `NumberInputField`
498
+
499
+ The field alone: a number typed into a text field, inside an `InputGroup` the caller draws. `NumberInput` is this with its own group, buttons and prefix; reach for the field only when composing another control.
500
+
501
+ | Prop | Type | Default | Description |
502
+ | --- | --- | --- | --- |
503
+ | `format` | `'integer' \| 'decimal' \| 'currency'` | `'decimal'` | |
504
+ | `value` | `number \| null` | `null` | |
505
+ | `onValueChange` | `(value: number \| null) => void` | | |
506
+ | `min` | `number` | `0` | |
507
+ | `max` | `number` | | |
508
+ | `step` | `number` | `1` | |
509
+ | `precision` | `number` | | |
510
+ | `clearable` | `boolean` | `true` | |
511
+ | `ref` | `HTMLInputElement \| null` | `null` | |
512
+
513
+ Also takes: every `<input>` attribute except `type`, `value`, `min`, `max`, `step`, `prefix`, `children`, `files`.
514
+
515
+ Types: `NumberInputFormat` (`'integer' | 'decimal' | 'currency'`).
516
+
517
+ ## QuantityInput
518
+
519
+ `import { QuantityInput } from '@jovertical/mise-design-system'`
520
+
521
+ An amount and the unit it is measured in, as one control.
522
+
523
+ | Prop | Type | Default | Description |
524
+ | --- | --- | --- | --- |
525
+ | `units` (required) | `readonly QuantityUnit[]` | | The units on offer. A prop rather than a constant: a measurement enum is domain, and a package that shipped one would ship an application's contract. |
526
+ | `layout` | `'unit-first' \| 'quantity-first'` | `'unit-first'` | |
527
+ | `unit` | `string` | `''` | |
528
+ | `quantity` | `number \| null` | `null` | |
529
+ | `onUnitChange` | `(unit: string) => void` | | |
530
+ | `onQuantityChange` | `(quantity: number \| null) => void` | | |
531
+ | `onQuantityBlur` | `() => void` | | When the amount loses focus, for a caller that writes on commit rather than on keystroke: "2" on the way to "25" is not an amount anybody meant. |
532
+ | `groupHints` | `Readonly<Record<string, string>>` | | A muted note beside a group's heading, keyed by the group's name: "needs density" beside volume units for an item weighed by the gram. The units stay pickable. |
533
+ | `menuFooter` | `Snippet<[() => void]>` | | Something below the units in the open menu: a fact and an action, such as "Density 0.92 g per ml · Edit". Handed `closeMenu`, because an action there usually opens something else, and the menu has to go first. |
534
+ | `unitPlaceholder` | `string` | `'Unit'` | |
535
+ | `quantityPlaceholder` | `string` | `'1'` | |
536
+ | `quantityStep` | `number` | `0.01` | How far ↑ and ↓ move the amount. |
537
+ | `quantityMin` | `number` | `0` | |
538
+ | `quantityPrecision` | `number` | | The most decimal places the amount takes: `0` for a counted unit. No limit when absent. |
539
+ | `size` | `'sm' \| 'md'` | `'md'` | Matches `Input`'s own scale. `sm` for a control inside a list row. |
540
+ | `disabled` | `boolean` | `false` | |
541
+
542
+ Also takes: every `<div>` attribute except `children`.
543
+
544
+ Types: `QuantityInputLayout` (`'unit-first' | 'quantity-first'`), `QuantityUnit`.
545
+
546
+ ## RadioGroup
547
+
548
+ `import { RadioGroup, RadioGroupItem } from '@jovertical/mise-design-system'`
549
+
550
+ Also takes: bits-ui `RadioGroup.Root` props.
551
+
552
+ ### `RadioGroupItem`
553
+
554
+ Also takes: bits-ui `RadioGroup.Item` props except `children`, `child`.
555
+
556
+ ## Select
557
+
558
+ `import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue } from '@jovertical/mise-design-system'`
559
+
560
+ Pass `items` (`{ value, label }[]`) so the trigger can show the selected label while the list is closed. bits-ui learns an item's label only when the item renders, and the list renders only when open. Radix kept hidden items mounted for the same purpose.
561
+
562
+ | Prop | Type | Default | Description |
563
+ | --- | --- | --- | --- |
564
+ | `type` | `'single'` | `'single'` | |
565
+
566
+ Also takes: bits-ui `Select.Root` props except `type`; bits-ui `Select.Root` props.
567
+
568
+ ### `SelectContent`
569
+
570
+ A ring rather than a border, matching every other floating surface.
571
+
572
+ | Prop | Type | Default | Description |
573
+ | --- | --- | --- | --- |
574
+ | `portalProps` | `SelectPrimitive.PortalProps` | | |
575
+
576
+ Also takes: bits-ui `Select.Content` props except `child`.
577
+
578
+ ### `SelectGroup`
579
+
580
+ Also takes: bits-ui `Select.Group` props.
581
+
582
+ ### `SelectItem`
583
+
584
+ `focus:bg-muted` rather than `--accent`, which is a nav tint and never a hover.
585
+
586
+ | Prop | Type | Default | Description |
587
+ | --- | --- | --- | --- |
588
+ | `children` | `Snippet` | | |
589
+
590
+ Also takes: bits-ui `Select.Item` props except `child` except `children`.
591
+
592
+ ### `SelectLabel`
593
+
594
+ Also takes: bits-ui `Select.GroupHeading` props.
595
+
596
+ ### `SelectScrollDownButton`
597
+
598
+ Also takes: bits-ui `Select.ScrollDownButton` props except `children`, `child`.
599
+
600
+ ### `SelectScrollUpButton`
601
+
602
+ Also takes: bits-ui `Select.ScrollUpButton` props except `children`, `child`.
603
+
604
+ ### `SelectSeparator`
605
+
606
+ Also takes: every `<div>` attribute.
607
+
608
+ ### `SelectTrigger`
609
+
610
+ `w-full` by default: a select sits in a form field beside inputs that are already full width, and `w-fit` made it the one control in the row not to be. Set `class="w-fit"` back for the rarer case. `size="sm"` for a dense row, such as a table cell or a toolbar.
611
+
612
+ | Prop | Type | Default | Description |
613
+ | --- | --- | --- | --- |
614
+ | `size` | `'sm' \| 'md'` | `'md'` | |
615
+
616
+ Also takes: bits-ui `Select.Trigger` props except `child`.
617
+
618
+ ### `SelectValue`
619
+
620
+ Also takes: bits-ui `Select.Value` props.
621
+
622
+ ## Switch
623
+
624
+ `import { Switch } from '@jovertical/mise-design-system'`
625
+
626
+ Bare: pair it with a Label, or a field, when it needs words beside it.
627
+
628
+ Also takes: bits-ui `Switch.Root` props except `children`, `child`.
629
+
630
+ ## SwitchField
631
+
632
+ `import { SwitchField } from '@jovertical/mise-design-system'`
633
+
634
+ A bordered on/off row: what it does on the left, the switch on the right.
635
+
636
+ | Prop | Type | Default | Description |
637
+ | --- | --- | --- | --- |
638
+ | `id` (required) | `string` | | The switch's id, which the label points at. |
639
+ | `label` (required) | `string` | | |
640
+ | `description` | `string` | | |
641
+ | `checked` | `boolean` | `false` | |
642
+ | `onCheckedChange` | `(checked: boolean) => void` | | |
643
+ | `disabled` | `boolean` | `false` | |
644
+ | `variant` | `'default' \| 'flush'` | `'default'` | `default` draws its own bordered surface, for a switch that stands alone in a form. |
645
+ | `children` | `Snippet` | | Revealed below the row while the switch is on. |
646
+
647
+ Also takes: every `<div>` attribute except `children`.
648
+
649
+ Types: `SwitchFieldVariant` (`'default' | 'flush'`).
650
+
651
+ ## Textarea
652
+
653
+ `import { Textarea } from '@jovertical/mise-design-system'`
654
+
655
+ A counter appears only when `maxlength` is set: a bare number counts up towards nothing. `maxlength` stops typing at the ceiling, so falling under `minlength` is the only reachable invalid state, and only once something has been typed. An untouched optional field is not wrong.
656
+
657
+ Also takes: every `<textarea>` attribute.
658
+
659
+ ## TimePicker
660
+
661
+ `import { TimePicker } from '@jovertical/mise-design-system'`
662
+
663
+ Three selects standing in for a clock: hour, minute, and AM/PM.
664
+
665
+ | Prop | Type | Default | Description |
666
+ | --- | --- | --- | --- |
667
+ | `value` | `Time` | | |
668
+ | `onValueChange` | `(value: Time) => void` | | |
669
+ | `disabled` | `boolean` | `false` | |
670
+
671
+ Also takes: every `<div>` attribute except `children`.
672
+
673
+ ## Toggle
674
+
675
+ `import { Toggle, toggleVariants } from '@jovertical/mise-design-system'`
676
+
677
+ One on/off control, not a group: reach for `ToggleGroup` the moment a second option joins it. `variant="segmented"` only makes sense inside that group's track; a lone `Toggle` wants `default`, `outline` or `solid`.
678
+
679
+ | Prop | Type | Default | Description |
680
+ | --- | --- | --- | --- |
681
+ | `variant` | `'default' \| 'outline' \| 'segmented' \| 'solid'` | | |
682
+ | `size` | `'default' \| 'sm' \| 'lg'` | | |
683
+ | `iconOnly` | `boolean` | | One glyph and no label: a circle the size of the toggle's height. Needs an `aria-label`. |
684
+
685
+ Also takes: bits-ui `Toggle.Root` props.
686
+
687
+ Also exports: `toggleVariants`.
688
+
689
+ Types: `ToggleSize` (`'default' | 'sm' | 'lg'`), `ToggleVariant` (`'default' | 'outline' | 'segmented' | 'solid'`).
690
+
691
+ ## ToggleGroup
692
+
693
+ `import { ToggleGroup, ToggleGroupItem } from '@jovertical/mise-design-system'`
694
+
695
+ A segmented group is a track, not a row of joined buttons: no gap between options, no shared border to collapse, and the wash is the point. `size` sizes the track, because a segmented item measures itself against it.
696
+
697
+ | Prop | Type | Default | Description |
698
+ | --- | --- | --- | --- |
699
+ | `variant` | `'default' \| 'outline' \| 'segmented' \| 'solid'` | | |
700
+ | `size` | `'default' \| 'sm' \| 'lg'` | | |
701
+ | `spacing` | `number` | `0` | |
702
+
703
+ Also takes: bits-ui `ToggleGroup.Root` props.
704
+
705
+ ### `ToggleGroupItem`
706
+
707
+ | Prop | Type | Default | Description |
708
+ | --- | --- | --- | --- |
709
+ | `variant` | `'default' \| 'outline' \| 'segmented' \| 'solid'` | | |
710
+ | `size` | `'default' \| 'sm' \| 'lg'` | | |
711
+
712
+ Also takes: bits-ui `ToggleGroup.Item` props.