dowel-ui 0.15.0 → 0.16.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.
- package/README.md +8 -7
- package/dist/registry.json +107 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A dowel is the hidden peg that joins two boards so the seam does not show. That
|
|
|
11
11
|
**[Documentation](https://lacodda.github.io/dowel/)** — what everything is and why it is that way.
|
|
12
12
|
**[The stand](https://lacodda.github.io/dowel/stand/)** — every component, live, in either theme and in the accent of any product of the line.
|
|
13
13
|
|
|
14
|
-
**Status:** v0.
|
|
14
|
+
**Status:** v0.16.0 - the theme, the scales, an accent per product, thirty components - overlays, menus, the command palette and the three ways of saying something happened - and the gates each one passes: axe, the keyboard, a dependency budget and a picture in both themes. Components install from a versioned registry, the docs are served in the form an agent reads, and `dowel check` tells a project what stands between it and the vocabulary. Two products of the line live on it. See the [roadmap](#roadmap).
|
|
15
15
|
|
|
16
16
|
## The theme
|
|
17
17
|
|
|
@@ -98,12 +98,13 @@ snapshot the cross-references point into the same snapshot, so a component and
|
|
|
98
98
|
the sibling it reuses are the pair that shipped together. See
|
|
99
99
|
[installing from the registry](https://lacodda.github.io/dowel/guides/registry/).
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
Badge, Chip, Kbd, Spinner, Truncate and Copyable;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
three for
|
|
101
|
+
Thirty of them so far. The everyday ones - Button, Input, Textarea, Panel,
|
|
102
|
+
Badge, Chip, Kbd, Spinner, Truncate and Copyable; Field and the three controls
|
|
103
|
+
that answer a question - Checkbox, RadioGroup and Switch; the six that float
|
|
104
|
+
above the page - Dialog, ConfirmDialog, Drawer, Popover, PreviewCard and
|
|
105
|
+
Tooltip; four for choosing something - Menu, ContextMenu, Select and Combobox;
|
|
106
|
+
three for finding it - SearchField, CommandPalette and the `useShortcut` behind
|
|
107
|
+
them; and three for saying that something happened: Toast, Alert and Banner.
|
|
107
108
|
|
|
108
109
|
Those last three are the ones products keep confusing, so each page names all
|
|
109
110
|
four options: a **toast** goes away, an **alert** is still true after a reload,
|
package/dist/registry.json
CHANGED
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
"description": "A message that stays on the screen, in the flow of the page, about the thing next to it: this field could not be saved, this profile has no axes yet, this export is out of date.",
|
|
223
223
|
"dependencies": [
|
|
224
224
|
"class-variance-authority",
|
|
225
|
-
"dowel-ui@^0.
|
|
225
|
+
"dowel-ui@^0.16.0"
|
|
226
226
|
],
|
|
227
227
|
"registryDependencies": [],
|
|
228
228
|
"files": [
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
"description": "A small piece of state attached to something else: a count, a status, a label. It is not a button and never was - if it can be clicked it is a Chip.",
|
|
242
242
|
"dependencies": [
|
|
243
243
|
"class-variance-authority",
|
|
244
|
-
"dowel-ui@^0.
|
|
244
|
+
"dowel-ui@^0.16.0"
|
|
245
245
|
],
|
|
246
246
|
"registryDependencies": [],
|
|
247
247
|
"files": [
|
|
@@ -260,7 +260,7 @@
|
|
|
260
260
|
"description": "A strip across the top of the application, about the application: you are offline, this build is a preview, your licence expires on Friday, a new version is ready to install.",
|
|
261
261
|
"dependencies": [
|
|
262
262
|
"class-variance-authority",
|
|
263
|
-
"dowel-ui@^0.
|
|
263
|
+
"dowel-ui@^0.16.0"
|
|
264
264
|
],
|
|
265
265
|
"registryDependencies": [],
|
|
266
266
|
"files": [
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
"dependencies": [
|
|
281
281
|
"@base-ui/react",
|
|
282
282
|
"class-variance-authority",
|
|
283
|
-
"dowel-ui@^0.
|
|
283
|
+
"dowel-ui@^0.16.0"
|
|
284
284
|
],
|
|
285
285
|
"registryDependencies": [],
|
|
286
286
|
"files": [
|
|
@@ -292,6 +292,25 @@
|
|
|
292
292
|
}
|
|
293
293
|
]
|
|
294
294
|
},
|
|
295
|
+
{
|
|
296
|
+
"name": "checkbox",
|
|
297
|
+
"type": "registry:ui",
|
|
298
|
+
"title": "Checkbox",
|
|
299
|
+
"description": "The interesting part is the words. A checkbox on its own is a nine-pixel target that says nothing; wired to a label it is the whole row, and the row is what a finger and a pointer both aim at. So the label is part of the component rather than something a caller remembers to add - the commonest bug in a hand-rolled checkbox is a `<label>` that is next to the input instead of tied to it, which looks identical and does nothing.",
|
|
300
|
+
"dependencies": [
|
|
301
|
+
"@base-ui/react",
|
|
302
|
+
"dowel-ui@^0.16.0"
|
|
303
|
+
],
|
|
304
|
+
"registryDependencies": [],
|
|
305
|
+
"files": [
|
|
306
|
+
{
|
|
307
|
+
"path": "ui/checkbox.tsx",
|
|
308
|
+
"target": "@ui/checkbox.tsx",
|
|
309
|
+
"type": "registry:ui",
|
|
310
|
+
"content": "import type { ReactNode } from 'react'\nimport { Checkbox as Base } from '@base-ui/react/checkbox'\nimport { CheckboxGroup as BaseGroup } from '@base-ui/react/checkbox-group'\nimport { cn } from 'dowel-ui'\n\n/*\n * Checkbox - a box, its tick, and the words next to it.\n *\n * The interesting part is the words. A checkbox on its own is a nine-pixel\n * target that says nothing; wired to a label it is the whole row, and the row\n * is what a finger and a pointer both aim at. So the label is part of the\n * component rather than something a caller remembers to add - the commonest\n * bug in a hand-rolled checkbox is a `<label>` that is next to the input\n * instead of tied to it, which looks identical and does nothing.\n *\n * The box is drawn rather than native. `appearance: none` on a real\n * `<input type=checkbox>` is the other way, and it takes the indeterminate\n * state with it: the dash is not a character the input can be told to draw.\n * Base UI renders a button with the right role and state, and the tick and\n * the dash are ours, in the accent, sized to the text next to them.\n *\n * `indeterminate` is a real state, not a third value: it says \"some of the\n * things below are checked\", and clicking still means check-all. A tri-state\n * value would make every caller handle a case that does not exist.\n */\n\nexport interface CheckboxProps {\n /** The words next to the box. Omit only for a checkbox in a table cell,\n * and then give `aria-label` instead. */\n children?: ReactNode\n checked?: boolean\n defaultChecked?: boolean\n onCheckedChange?: (checked: boolean) => void\n /** Some but not all of what this stands for is checked. Draws a dash;\n * clicking still checks everything. */\n indeterminate?: boolean\n /** This is the box that stands for the whole group. Inside a\n * `CheckboxGroup` with `allValues`, its state follows the children - checked,\n * unchecked, or the dash in between - and clicking it sets all of them.\n * Working that out by hand is where the indeterminate state usually goes\n * wrong, so the group does it. */\n parent?: boolean\n disabled?: boolean\n required?: boolean\n name?: string\n value?: string\n 'aria-label'?: string\n className?: string\n}\n\nexport function Checkbox({\n children,\n indeterminate = false,\n className,\n ...props\n}: CheckboxProps) {\n const box = (\n <Base.Root\n indeterminate={indeterminate}\n className={cn(\n // `group` is what the two marks below hang their state off: the\n // indeterminate one belongs to the element, not to a prop.\n 'group flex size-4 shrink-0 items-center justify-center rounded-xs border border-line-2 bg-transparent',\n 'transition-colors',\n 'hover:border-accent',\n 'data-[checked]:border-accent data-[checked]:bg-accent',\n 'data-[indeterminate]:border-accent data-[indeterminate]:bg-accent',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n // Without a label the box is the whole control, so the caller's\n // classes land on it.\n children === undefined && className,\n )}\n {...props}\n >\n {/* Both marks are rendered and the state picks one, rather than the\n * `indeterminate` prop picking it. A parent checkbox never receives\n * that prop - the group works its state out from the children and says\n * so on the element - so choosing in JavaScript would leave the parent\n * drawing a tick while announcing `mixed`. */}\n <Base.Indicator\n className=\"flex text-on-accent group-data-[indeterminate]:hidden\"\n render={<span />}\n >\n <svg viewBox=\"0 0 16 16\" className=\"size-3\" aria-hidden>\n <path\n d=\"M3.5 8.5l3 3 6-6.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </Base.Indicator>\n <span className=\"hidden text-on-accent group-data-[indeterminate]:flex\" aria-hidden>\n <svg viewBox=\"0 0 16 16\" className=\"size-3\">\n <path d=\"M4 8h8\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" />\n </svg>\n </span>\n </Base.Root>\n )\n\n if (children === undefined) return box\n\n return (\n <label\n className={cn(\n 'flex cursor-pointer items-center gap-2 text-sm text-text',\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50',\n className,\n )}\n >\n {box}\n {children}\n </label>\n )\n}\n\n/*\n * A group of checkboxes that share a name and a value.\n *\n * It exists for the parent checkbox: given `allValues`, Base UI works out\n * whether the parent is checked, unchecked or indeterminate, and clicking it\n * sets all of them. Doing that by hand is where the indeterminate state\n * usually goes wrong.\n */\nexport interface CheckboxGroupProps {\n children: ReactNode\n value?: string[]\n defaultValue?: string[]\n onValueChange?: (value: string[]) => void\n /** Every value in the group, which is what makes a parent checkbox work. */\n allValues?: string[]\n disabled?: boolean\n className?: string\n}\n\nexport function CheckboxGroup({ children, className, ...props }: CheckboxGroupProps) {\n return (\n <BaseGroup className={cn('flex flex-col gap-2', className)} {...props}>\n {children}\n </BaseGroup>\n )\n}\n"
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
},
|
|
295
314
|
{
|
|
296
315
|
"name": "chip",
|
|
297
316
|
"type": "registry:ui",
|
|
@@ -299,7 +318,7 @@
|
|
|
299
318
|
"description": "A badge you can act on: a filter that can be removed, a tag with a count, a selected value in a field. The difference from a Badge is entirely about whether something happens when you click it - and if something does, that part is a real `<button>` with a real label, not a decorative cross.",
|
|
300
319
|
"dependencies": [
|
|
301
320
|
"class-variance-authority",
|
|
302
|
-
"dowel-ui@^0.
|
|
321
|
+
"dowel-ui@^0.16.0"
|
|
303
322
|
],
|
|
304
323
|
"registryDependencies": [],
|
|
305
324
|
"files": [
|
|
@@ -319,7 +338,7 @@
|
|
|
319
338
|
"dependencies": [
|
|
320
339
|
"@base-ui/react",
|
|
321
340
|
"class-variance-authority",
|
|
322
|
-
"dowel-ui@^0.
|
|
341
|
+
"dowel-ui@^0.16.0"
|
|
323
342
|
],
|
|
324
343
|
"registryDependencies": [
|
|
325
344
|
"https://lacodda.github.io/dowel/r/input.json",
|
|
@@ -342,7 +361,7 @@
|
|
|
342
361
|
"dependencies": [
|
|
343
362
|
"@base-ui/react",
|
|
344
363
|
"class-variance-authority",
|
|
345
|
-
"dowel-ui@^0.
|
|
364
|
+
"dowel-ui@^0.16.0"
|
|
346
365
|
],
|
|
347
366
|
"registryDependencies": [
|
|
348
367
|
"https://lacodda.github.io/dowel/r/combobox.json",
|
|
@@ -365,7 +384,7 @@
|
|
|
365
384
|
"dependencies": [
|
|
366
385
|
"@base-ui/react",
|
|
367
386
|
"class-variance-authority",
|
|
368
|
-
"dowel-ui@^0.
|
|
387
|
+
"dowel-ui@^0.16.0"
|
|
369
388
|
],
|
|
370
389
|
"registryDependencies": [],
|
|
371
390
|
"files": [
|
|
@@ -384,7 +403,7 @@
|
|
|
384
403
|
"description": "The same list of actions as Menu, opened the other way round: by right click, or by a long press on a touch screen, over an *area* rather than from a button. So the trigger is not a control - it is the region the menu belongs to, a row, a canvas, a file tile - and it renders a `<div>`.",
|
|
385
404
|
"dependencies": [
|
|
386
405
|
"@base-ui/react",
|
|
387
|
-
"dowel-ui@^0.
|
|
406
|
+
"dowel-ui@^0.16.0"
|
|
388
407
|
],
|
|
389
408
|
"registryDependencies": [
|
|
390
409
|
"https://lacodda.github.io/dowel/r/menu.json"
|
|
@@ -404,7 +423,7 @@
|
|
|
404
423
|
"title": "Copyable",
|
|
405
424
|
"description": "Any text that someone will eventually want to copy - an id, a path, a hash, a token - copied with one click. The rule comes from nitid: if a value is worth showing, it is worth being able to take away, and selecting a monospaced id by hand is a small daily tax.",
|
|
406
425
|
"dependencies": [
|
|
407
|
-
"dowel-ui@^0.
|
|
426
|
+
"dowel-ui@^0.16.0"
|
|
408
427
|
],
|
|
409
428
|
"registryDependencies": [],
|
|
410
429
|
"files": [
|
|
@@ -424,7 +443,7 @@
|
|
|
424
443
|
"dependencies": [
|
|
425
444
|
"@base-ui/react",
|
|
426
445
|
"class-variance-authority",
|
|
427
|
-
"dowel-ui@^0.
|
|
446
|
+
"dowel-ui@^0.16.0"
|
|
428
447
|
],
|
|
429
448
|
"registryDependencies": [],
|
|
430
449
|
"files": [
|
|
@@ -444,7 +463,7 @@
|
|
|
444
463
|
"dependencies": [
|
|
445
464
|
"@base-ui/react",
|
|
446
465
|
"class-variance-authority",
|
|
447
|
-
"dowel-ui@^0.
|
|
466
|
+
"dowel-ui@^0.16.0"
|
|
448
467
|
],
|
|
449
468
|
"registryDependencies": [],
|
|
450
469
|
"files": [
|
|
@@ -456,13 +475,32 @@
|
|
|
456
475
|
}
|
|
457
476
|
]
|
|
458
477
|
},
|
|
478
|
+
{
|
|
479
|
+
"name": "field",
|
|
480
|
+
"type": "registry:ui",
|
|
481
|
+
"title": "Field",
|
|
482
|
+
"description": "Every form is the same four parts repeated: a name for the control, the control, sometimes a hint, and sometimes an error. Written by hand each time, they drift - the label loses its `htmlFor`, the hint is a `<div>` no screen reader mentions, the error appears in red and is announced by nothing at all. This is that arrangement, once.",
|
|
483
|
+
"dependencies": [
|
|
484
|
+
"@base-ui/react",
|
|
485
|
+
"dowel-ui@^0.16.0"
|
|
486
|
+
],
|
|
487
|
+
"registryDependencies": [],
|
|
488
|
+
"files": [
|
|
489
|
+
{
|
|
490
|
+
"path": "ui/field.tsx",
|
|
491
|
+
"target": "@ui/field.tsx",
|
|
492
|
+
"type": "registry:ui",
|
|
493
|
+
"content": "import type { ReactElement, ReactNode } from 'react'\nimport { Field as Base } from '@base-ui/react/field'\nimport { cn } from 'dowel-ui'\n\n/*\n * Field - the label, the control, and what the form has to say about it.\n *\n * Every form is the same four parts repeated: a name for the control, the\n * control, sometimes a hint, and sometimes an error. Written by hand each\n * time, they drift - the label loses its `htmlFor`, the hint is a `<div>` no\n * screen reader mentions, the error appears in red and is announced by\n * nothing at all. This is that arrangement, once.\n *\n * The wiring is the point, and Base UI does it: the label points at the\n * control, the description and the error are named by `aria-describedby`, and\n * the control is marked invalid while an error is showing. None of that is\n * visible when it works, which is exactly why a product stops doing it.\n *\n * `error` is a string this is given, not a rule this enforces. dowel has no\n * opinion about where the string came from - Base UI's own `validate`, a\n * schema, react-hook-form, or a server that just said no - because a design\n * system that picked a form library would be choosing for products that\n * already chose. The one thing it insists on is that an error, once it\n * exists, is announced and points at the field it belongs to.\n *\n * The label is always rendered. A field whose name is only a placeholder\n * loses that name the moment someone types, and a placeholder is not a label\n * to anything that reads the page aloud; `labelHidden` takes it off the\n * screen and leaves it in the accessibility tree.\n */\n\nexport interface FieldProps {\n /** What the control is called. Always rendered; `labelHidden` only takes it\n * off the screen. */\n label: ReactNode\n /** The control itself - Input, Textarea, Select, Checkbox, anything.\n *\n * A single element rather than arbitrary nodes, because it is handed to\n * Base UI to carry the field's id and its `aria-*` wiring. Two children, or\n * a bare string, would leave the label naming nothing. */\n children: ReactElement\n /** A hint under the control. Hidden while an error is showing: two lines of\n * small print under one field is one line too many, and the error is the\n * one that matters. */\n help?: ReactNode\n /** What is wrong, if anything. Its presence is what marks the control\n * invalid - there is no separate `invalid` prop to keep in step. */\n error?: ReactNode\n /** Keep the label for screen readers but take it off the screen. For a\n * field whose meaning is obvious in context - a search box in a toolbar. */\n labelHidden?: boolean\n className?: string\n /** Marks the field required, which is a statement about the form rather\n * than about validation: the label gets the mark a reader looks for. */\n required?: boolean\n /** The control's `name`, forwarded so a `Form` can attach a server error to\n * this field by name. */\n name?: string\n disabled?: boolean\n}\n\nexport function Field({\n label,\n children,\n help,\n error,\n labelHidden = false,\n className,\n required = false,\n name,\n disabled = false,\n}: FieldProps) {\n const invalid = error !== undefined && error !== null && error !== false\n\n return (\n <Base.Root\n name={name}\n disabled={disabled}\n // Base UI marks the control invalid from its own validity state; this\n // says so for an error that arrived from anywhere else.\n invalid={invalid || undefined}\n className={cn('flex flex-col gap-1.5', className)}\n >\n <Base.Label\n className={cn(\n 'text-2xs font-medium uppercase tracking-caption text-faint',\n // Off the screen, still in the accessibility tree. Not\n // `display: none`, which would take it out of both.\n labelHidden && 'sr-only',\n )}\n >\n {label}\n {required && (\n <span aria-hidden className=\"ml-0.5 text-bad\">\n *\n </span>\n )}\n </Base.Label>\n\n {/* The control is handed to Base UI rather than merely nested inside\n * it. This is the whole component: a plain child renders a label whose\n * `for` points at an id nothing carries, so the label looks wired and\n * names nothing - which is exactly the bug this exists to prevent, and\n * it is invisible in a screenshot. `render` gives the child the id,\n * `aria-labelledby` and `aria-describedby` instead. */}\n <Base.Control render={children} />\n\n {/* The error wins the one line under the control. `match` is not used:\n * the string is already the decision - this renders whatever it was\n * handed rather than asking the browser what is wrong. */}\n {invalid ? (\n <Base.Error className=\"text-xs text-bad\" match>\n {error}\n </Base.Error>\n ) : (\n help !== undefined && <Base.Description className=\"text-xs text-dim\">{help}</Base.Description>\n )}\n </Base.Root>\n )\n}\n"
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
},
|
|
459
497
|
{
|
|
460
498
|
"name": "input",
|
|
461
499
|
"type": "registry:ui",
|
|
462
500
|
"title": "Input",
|
|
463
501
|
"description": "A single-line field. It is a plain `<input>` with the line's clothes on, so everything a browser gives an input for free - autofill, spellcheck, the right keyboard on a phone, `type=\"email\"` validation - still works.",
|
|
464
502
|
"dependencies": [
|
|
465
|
-
"dowel-ui@^0.
|
|
503
|
+
"dowel-ui@^0.16.0"
|
|
466
504
|
],
|
|
467
505
|
"registryDependencies": [],
|
|
468
506
|
"files": [
|
|
@@ -480,7 +518,7 @@
|
|
|
480
518
|
"title": "Kbd",
|
|
481
519
|
"description": "A key, as printed in a menu or a hint: `Ctrl` `K`. It is a `<kbd>` element because that is what the element is for - a screen reader announces it as keyboard input rather than reading a stray capital letter.",
|
|
482
520
|
"dependencies": [
|
|
483
|
-
"dowel-ui@^0.
|
|
521
|
+
"dowel-ui@^0.16.0"
|
|
484
522
|
],
|
|
485
523
|
"registryDependencies": [],
|
|
486
524
|
"files": [
|
|
@@ -500,7 +538,7 @@
|
|
|
500
538
|
"dependencies": [
|
|
501
539
|
"@base-ui/react",
|
|
502
540
|
"class-variance-authority",
|
|
503
|
-
"dowel-ui@^0.
|
|
541
|
+
"dowel-ui@^0.16.0"
|
|
504
542
|
],
|
|
505
543
|
"registryDependencies": [],
|
|
506
544
|
"files": [
|
|
@@ -519,7 +557,7 @@
|
|
|
519
557
|
"description": "The raised surface everything else sits on. It is the one place a screen gets its structure from, so it stays deliberately plain: a ground, a hairline, a corner.",
|
|
520
558
|
"dependencies": [
|
|
521
559
|
"class-variance-authority",
|
|
522
|
-
"dowel-ui@^0.
|
|
560
|
+
"dowel-ui@^0.16.0"
|
|
523
561
|
],
|
|
524
562
|
"registryDependencies": [],
|
|
525
563
|
"files": [
|
|
@@ -539,7 +577,7 @@
|
|
|
539
577
|
"dependencies": [
|
|
540
578
|
"@base-ui/react",
|
|
541
579
|
"class-variance-authority",
|
|
542
|
-
"dowel-ui@^0.
|
|
580
|
+
"dowel-ui@^0.16.0"
|
|
543
581
|
],
|
|
544
582
|
"registryDependencies": [],
|
|
545
583
|
"files": [
|
|
@@ -559,7 +597,7 @@
|
|
|
559
597
|
"dependencies": [
|
|
560
598
|
"@base-ui/react",
|
|
561
599
|
"class-variance-authority",
|
|
562
|
-
"dowel-ui@^0.
|
|
600
|
+
"dowel-ui@^0.16.0"
|
|
563
601
|
],
|
|
564
602
|
"registryDependencies": [],
|
|
565
603
|
"files": [
|
|
@@ -571,13 +609,33 @@
|
|
|
571
609
|
}
|
|
572
610
|
]
|
|
573
611
|
},
|
|
612
|
+
{
|
|
613
|
+
"name": "radio-group",
|
|
614
|
+
"type": "registry:ui",
|
|
615
|
+
"title": "Radio-group",
|
|
616
|
+
"description": "The rule for reaching for this rather than a Select is whether the options are worth the space: a handful of short choices read faster laid out than hidden behind a trigger, and each one becomes a target rather than a step.",
|
|
617
|
+
"dependencies": [
|
|
618
|
+
"@base-ui/react",
|
|
619
|
+
"class-variance-authority",
|
|
620
|
+
"dowel-ui@^0.16.0"
|
|
621
|
+
],
|
|
622
|
+
"registryDependencies": [],
|
|
623
|
+
"files": [
|
|
624
|
+
{
|
|
625
|
+
"path": "ui/radio-group.tsx",
|
|
626
|
+
"target": "@ui/radio-group.tsx",
|
|
627
|
+
"type": "registry:ui",
|
|
628
|
+
"content": "import type { ReactNode } from 'react'\nimport { Radio as Base } from '@base-ui/react/radio'\nimport { RadioGroup as BaseGroup } from '@base-ui/react/radio-group'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from 'dowel-ui'\n\n/*\n * RadioGroup - one of a few, all of them visible.\n *\n * The rule for reaching for this rather than a Select is whether the options\n * are worth the space: a handful of short choices read faster laid out than\n * hidden behind a trigger, and each one becomes a target rather than a step.\n * Past about five, or when the labels are long, a Select is the honest\n * choice - this is not a Select with more pixels.\n *\n * The group is the control, not the button. That is what the arrow keys\n * follow, what a screen reader announces as one thing with a position in it,\n * and why `Radio` on its own is not exported: a radio outside a group is a\n * checkbox that cannot be unchecked.\n *\n * The dot is drawn rather than native, for the same reason as the tick in\n * Checkbox: a real `<input type=radio>` cannot be styled without\n * `appearance: none`, and after that the dot has to be drawn anyway.\n */\n\nexport interface RadioGroupProps {\n children: ReactNode\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n disabled?: boolean\n readOnly?: boolean\n required?: boolean\n name?: string\n /** Lay the options out in a row. For two or three short ones; a column is\n * the default because it stays readable as labels grow. */\n orientation?: VariantProps<typeof radioGroupVariants>['orientation']\n 'aria-label'?: string\n className?: string\n}\n\n/* The layout lives in `cva` rather than in a destructured default, which is\n * how every other primitive here states one. It also keeps the word out of\n * the component's signature: a string default in a props list is how a\n * primitive ends up shipping English, and the gate that watches for that\n * cannot tell an enum value from a label. */\nexport const radioGroupVariants = cva('flex', {\n variants: {\n orientation: {\n vertical: 'flex-col gap-2',\n horizontal: 'flex-row flex-wrap gap-4',\n },\n },\n defaultVariants: { orientation: 'vertical' },\n})\n\nexport function RadioGroup({ children, orientation, className, ...props }: RadioGroupProps) {\n return (\n <BaseGroup className={cn(radioGroupVariants({ orientation }), className)} {...props}>\n {children}\n </BaseGroup>\n )\n}\n\nexport interface RadioProps {\n value: string\n /** The words next to the dot. */\n children?: ReactNode\n disabled?: boolean\n className?: string\n}\n\nexport function Radio({ value, children, disabled, className }: RadioProps) {\n const dot = (\n <Base.Root\n value={value}\n disabled={disabled}\n className={cn(\n 'flex size-4 shrink-0 items-center justify-center rounded-full border border-line-2 bg-transparent',\n 'transition-colors',\n 'hover:border-accent',\n 'data-[checked]:border-accent',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n children === undefined && className,\n )}\n >\n <Base.Indicator className=\"size-2 rounded-full bg-accent\" render={<span />} />\n </Base.Root>\n )\n\n if (children === undefined) return dot\n\n return (\n <label\n className={cn(\n 'flex cursor-pointer items-center gap-2 text-sm text-text',\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50',\n className,\n )}\n >\n {dot}\n {children}\n </label>\n )\n}\n"
|
|
629
|
+
}
|
|
630
|
+
]
|
|
631
|
+
},
|
|
574
632
|
{
|
|
575
633
|
"name": "search-field",
|
|
576
634
|
"type": "registry:ui",
|
|
577
635
|
"title": "Search-field",
|
|
578
636
|
"description": "An Input that knows it is a search box, which is three small things the products kept not doing:\n * - a magnifier, so the field is recognisable before it is read; - a way to clear it that is not \"select all and delete\" - and one that a keyboard can reach, which a decorative `<span>` cannot; - the shortcut that focuses it, shown in the field rather than learned.",
|
|
579
637
|
"dependencies": [
|
|
580
|
-
"dowel-ui@^0.
|
|
638
|
+
"dowel-ui@^0.16.0"
|
|
581
639
|
],
|
|
582
640
|
"registryDependencies": [
|
|
583
641
|
"https://lacodda.github.io/dowel/r/input.json",
|
|
@@ -601,7 +659,7 @@
|
|
|
601
659
|
"dependencies": [
|
|
602
660
|
"@base-ui/react",
|
|
603
661
|
"class-variance-authority",
|
|
604
|
-
"dowel-ui@^0.
|
|
662
|
+
"dowel-ui@^0.16.0"
|
|
605
663
|
],
|
|
606
664
|
"registryDependencies": [
|
|
607
665
|
"https://lacodda.github.io/dowel/r/input.json"
|
|
@@ -638,7 +696,7 @@
|
|
|
638
696
|
"description": "Something is happening and the answer has not arrived. It carries no text of its own - what is loading is the product's word, not the system's - but it does have to say *something* to a screen reader, or a page that is busy is silently identical to a page that is empty.",
|
|
639
697
|
"dependencies": [
|
|
640
698
|
"class-variance-authority",
|
|
641
|
-
"dowel-ui@^0.
|
|
699
|
+
"dowel-ui@^0.16.0"
|
|
642
700
|
],
|
|
643
701
|
"registryDependencies": [],
|
|
644
702
|
"files": [
|
|
@@ -650,13 +708,32 @@
|
|
|
650
708
|
}
|
|
651
709
|
]
|
|
652
710
|
},
|
|
711
|
+
{
|
|
712
|
+
"name": "switch",
|
|
713
|
+
"type": "registry:ui",
|
|
714
|
+
"title": "Switch",
|
|
715
|
+
"description": "The difference from Checkbox is not how it looks, and getting it wrong is the commonest mistake in the pair. A checkbox is an answer collected now and submitted later, with the rest of the form; a switch is a setting that applies the moment it moves. Put a switch in a form with a Save button and the reader cannot tell whether anything happened - they flipped it, and nothing said so.",
|
|
716
|
+
"dependencies": [
|
|
717
|
+
"@base-ui/react",
|
|
718
|
+
"dowel-ui@^0.16.0"
|
|
719
|
+
],
|
|
720
|
+
"registryDependencies": [],
|
|
721
|
+
"files": [
|
|
722
|
+
{
|
|
723
|
+
"path": "ui/switch.tsx",
|
|
724
|
+
"target": "@ui/switch.tsx",
|
|
725
|
+
"type": "registry:ui",
|
|
726
|
+
"content": "import type { ReactNode } from 'react'\nimport { Switch as Base } from '@base-ui/react/switch'\nimport { cn } from 'dowel-ui'\n\n/*\n * Switch - a setting that takes effect when you flip it.\n *\n * The difference from Checkbox is not how it looks, and getting it wrong is\n * the commonest mistake in the pair. A checkbox is an answer collected now\n * and submitted later, with the rest of the form; a switch is a setting that\n * applies the moment it moves. Put a switch in a form with a Save button and\n * the reader cannot tell whether anything happened - they flipped it, and\n * nothing said so.\n *\n * The rule, then: if there is a Save button, it is a Checkbox. If the change\n * is the action, it is a Switch.\n *\n * The thumb slides with `--duration-quick`, and the track carries the accent\n * when on - the only colour in the control, so a row of settings reads as a\n * column of on-and-off rather than a field of decoration. Under reduced\n * motion the theme drops the transition; the position still changes, which is\n * the part that carries the meaning.\n */\n\nexport interface SwitchProps {\n /** The words next to the switch. A switch with no label is a light with no\n * caption - give `aria-label` if the meaning is genuinely in the context. */\n children?: ReactNode\n checked?: boolean\n defaultChecked?: boolean\n onCheckedChange?: (checked: boolean) => void\n disabled?: boolean\n required?: boolean\n readOnly?: boolean\n name?: string\n 'aria-label'?: string\n className?: string\n}\n\nexport function Switch({ children, className, ...props }: SwitchProps) {\n const control = (\n <Base.Root\n className={cn(\n 'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-line-2 bg-soft',\n 'transition-colors duration-quick ease-out',\n 'hover:border-accent',\n 'data-[checked]:border-accent data-[checked]:bg-accent',\n 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n children === undefined && className,\n )}\n {...props}\n >\n <Base.Thumb\n className={cn(\n 'size-3.5 rounded-full bg-dim shadow-lift',\n 'transition-[transform,background-color] duration-quick ease-out',\n 'translate-x-0.5 data-[checked]:translate-x-[1.125rem]',\n 'data-[checked]:bg-on-accent',\n )}\n />\n </Base.Root>\n )\n\n if (children === undefined) return control\n\n return (\n <label\n className={cn(\n 'flex cursor-pointer items-center gap-2.5 text-sm text-text',\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50',\n className,\n )}\n >\n {control}\n {children}\n </label>\n )\n}\n"
|
|
727
|
+
}
|
|
728
|
+
]
|
|
729
|
+
},
|
|
653
730
|
{
|
|
654
731
|
"name": "textarea",
|
|
655
732
|
"type": "registry:ui",
|
|
656
733
|
"title": "Textarea",
|
|
657
734
|
"description": "A multi-line field that can grow with what is typed into it, which is the only interesting part: a fixed box makes someone scroll inside a scroll, and a box that grows without limit pushes the button they are trying to reach off the screen. `autoResize` grows it; `maxRows` says when to stop and let it scroll after all.",
|
|
658
735
|
"dependencies": [
|
|
659
|
-
"dowel-ui@^0.
|
|
736
|
+
"dowel-ui@^0.16.0"
|
|
660
737
|
],
|
|
661
738
|
"registryDependencies": [
|
|
662
739
|
"https://lacodda.github.io/dowel/r/input.json"
|
|
@@ -678,7 +755,7 @@
|
|
|
678
755
|
"dependencies": [
|
|
679
756
|
"@base-ui/react",
|
|
680
757
|
"class-variance-authority",
|
|
681
|
-
"dowel-ui@^0.
|
|
758
|
+
"dowel-ui@^0.16.0"
|
|
682
759
|
],
|
|
683
760
|
"registryDependencies": [],
|
|
684
761
|
"files": [
|
|
@@ -698,7 +775,7 @@
|
|
|
698
775
|
"dependencies": [
|
|
699
776
|
"@base-ui/react",
|
|
700
777
|
"class-variance-authority",
|
|
701
|
-
"dowel-ui@^0.
|
|
778
|
+
"dowel-ui@^0.16.0"
|
|
702
779
|
],
|
|
703
780
|
"registryDependencies": [],
|
|
704
781
|
"files": [
|
|
@@ -716,7 +793,7 @@
|
|
|
716
793
|
"title": "Truncate",
|
|
717
794
|
"description": "Text that does not fit, cut with an ellipsis - and, importantly, still readable in full: the element carries its own text as a `title`, so hovering shows what was cut. Every product wrote the one-line version of this and none of them remembered the title.",
|
|
718
795
|
"dependencies": [
|
|
719
|
-
"dowel-ui@^0.
|
|
796
|
+
"dowel-ui@^0.16.0"
|
|
720
797
|
],
|
|
721
798
|
"registryDependencies": [],
|
|
722
799
|
"files": [
|
|
@@ -759,13 +836,17 @@
|
|
|
759
836
|
"description": "What a form is made of: the fields, the two ways of choosing from a list, and the button that submits it.",
|
|
760
837
|
"registryDependencies": [
|
|
761
838
|
"https://lacodda.github.io/dowel/r/button.json",
|
|
839
|
+
"https://lacodda.github.io/dowel/r/checkbox.json",
|
|
762
840
|
"https://lacodda.github.io/dowel/r/chip.json",
|
|
763
841
|
"https://lacodda.github.io/dowel/r/combobox.json",
|
|
842
|
+
"https://lacodda.github.io/dowel/r/field.json",
|
|
764
843
|
"https://lacodda.github.io/dowel/r/input.json",
|
|
844
|
+
"https://lacodda.github.io/dowel/r/radio-group.json",
|
|
765
845
|
"https://lacodda.github.io/dowel/r/select.json",
|
|
846
|
+
"https://lacodda.github.io/dowel/r/switch.json",
|
|
766
847
|
"https://lacodda.github.io/dowel/r/textarea.json"
|
|
767
848
|
],
|
|
768
|
-
"docs": "Field
|
|
849
|
+
"docs": "Field wraps any of the controls: it is what ties a label, a hint and an error to the thing they belong to."
|
|
769
850
|
},
|
|
770
851
|
{
|
|
771
852
|
"extends": "none",
|
package/package.json
CHANGED