@wtfalch/design 0.6.0 → 0.8.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 (59) hide show
  1. package/dist/components/Button.d.ts +19 -27
  2. package/dist/components/Button.js +61 -1
  3. package/dist/components/Callout.d.ts +0 -21
  4. package/dist/components/Callout.js +7 -0
  5. package/dist/components/Card.js +7 -0
  6. package/dist/components/Checkbox.d.ts +0 -31
  7. package/dist/components/Checkbox.js +7 -0
  8. package/dist/components/Command.js +10 -2
  9. package/dist/components/DangerZone.js +7 -0
  10. package/dist/components/Dialog.d.ts +0 -21
  11. package/dist/components/Dialog.js +7 -0
  12. package/dist/components/Field.d.ts +22 -50
  13. package/dist/components/Field.js +18 -9
  14. package/dist/components/Identity.d.ts +0 -25
  15. package/dist/components/Identity.js +7 -0
  16. package/dist/components/Input.d.ts +10 -0
  17. package/dist/components/Input.js +31 -3
  18. package/dist/components/Kbd.d.ts +7 -0
  19. package/dist/components/Kbd.js +34 -0
  20. package/dist/components/Markdown.js +7 -0
  21. package/dist/components/Menu.js +10 -2
  22. package/dist/components/Modal.d.ts +0 -33
  23. package/dist/components/Modal.js +7 -0
  24. package/dist/components/Pagination.d.ts +0 -33
  25. package/dist/components/Pagination.js +7 -0
  26. package/dist/components/Popover.js +7 -0
  27. package/dist/components/Rows.js +7 -0
  28. package/dist/components/ScrollArea.js +7 -0
  29. package/dist/components/Select.d.ts +8 -1
  30. package/dist/components/Select.js +34 -5
  31. package/dist/components/Shell.d.ts +35 -0
  32. package/dist/components/Shell.js +26 -0
  33. package/dist/components/SizeGrid.js +7 -0
  34. package/dist/components/Slider.js +7 -0
  35. package/dist/components/SplitPane.js +7 -0
  36. package/dist/components/Stat.d.ts +45 -0
  37. package/dist/components/Stat.js +31 -0
  38. package/dist/components/Tabs.d.ts +21 -0
  39. package/dist/components/Tabs.js +8 -1
  40. package/dist/components/Textarea.js +14 -4
  41. package/dist/components/ThemeSwitch.d.ts +19 -0
  42. package/dist/components/ThemeSwitch.js +65 -0
  43. package/dist/components/Toast.d.ts +24 -18
  44. package/dist/components/Toast.js +44 -3
  45. package/dist/components/Toggle.d.ts +0 -30
  46. package/dist/components/Toggle.js +7 -0
  47. package/dist/components/Tooltip.js +7 -0
  48. package/dist/components/Tour.d.ts +0 -23
  49. package/dist/components/Tour.js +7 -0
  50. package/dist/components/fieldWiring.d.ts +44 -0
  51. package/dist/components/fieldWiring.js +30 -0
  52. package/dist/index.d.ts +23 -3
  53. package/dist/index.js +17 -1
  54. package/dist/styles/index.css +259 -13
  55. package/dist/tf.css +259 -13
  56. package/dist/themes/choice.d.ts +71 -0
  57. package/dist/themes/choice.js +81 -0
  58. package/dist/valet.css +259 -13
  59. package/package.json +2 -1
@@ -1,27 +1,19 @@
1
- /**
2
- * Something happened, and it does not need answering.
3
- *
4
- * There is no transient feedback in this app at all. Every outcome is either a
5
- * `.callout` that stays until the page changes, or nothing -- so "saved",
6
- * "copied", "model removed" and "conversation deleted" are all silent, and the
7
- * only way to know a button worked is that something else moved.
8
- *
9
- * **A toast is for the outcome you would not have chased.** If the reader has to
10
- * act on it, it is a callout and belongs on the page. If they have to decide, it
11
- * is a dialog. This is the third case: it worked, you may carry on, and in four
12
- * seconds there will be no trace.
13
- *
14
- * **`role="status"`, not `alert`.** An alert interrupts whatever a screen reader
15
- * is saying, which is right for "the download failed" and rude for "copied".
16
- * The failing case passes `tone="bad"`, which is the one that promotes itself.
17
- */
18
1
  export interface Toast {
19
2
  text: string;
20
3
  /** The same four words `Callout` and `Pill` use. `warn` was missing here
21
4
  * until 2026-09-05 -- three tones where the rest of the system has four. */
22
5
  tone: 'info' | 'good' | 'warn' | 'bad';
23
6
  }
24
- export declare function useToast(): (text: string, tone?: Toast["tone"]) => void;
7
+ type Push = (text: string, tone?: Toast['tone']) => void;
8
+ export declare function useToast(): Push;
9
+ /**
10
+ * Whether anything above will actually show a toast.
11
+ *
12
+ * For a component that has a second way of saying something and would rather
13
+ * use it than push into the void. `useToast` outside a host is a no-op that
14
+ * typechecks, which is the failure this exists to let you avoid.
15
+ */
16
+ export declare function useHasToastHost(): boolean;
25
17
  /**
26
18
  * The queue and the region, once, at the root.
27
19
  *
@@ -37,7 +29,21 @@ export declare function useToast(): (text: string, tone?: Toast["tone"]) => void
37
29
  * reachable; React Aria makes each toast an `alertdialog` inside a live
38
30
  * `region` for that reason, so the announcement still happens and the button
39
31
  * can still be reached.
32
+ *
33
+ * **Nesting one inside another is a no-op, and that is what makes this usable
34
+ * by a package.** `@wtfalch/email`'s mail client wanted to say "Archived."
35
+ * and could not: `useToast` needs a host above it, so the component would
36
+ * have had to wrap itself in one -- and then an application that already had
37
+ * a host would carry two regions announcing into the same page. It wrote its
38
+ * own `role="status"` strip instead and said so in a docblock, which is a
39
+ * package working around a package.
40
+ *
41
+ * So a `ToastHost` that finds one above it renders its children and steps
42
+ * aside. A library wraps itself unconditionally and gets the right answer
43
+ * both ways: its own region in an app that has none, and the app's region --
44
+ * one queue, one landmark, one place messages pile up -- in an app that does.
40
45
  */
41
46
  export declare function ToastHost({ children }: {
42
47
  children: React.ReactNode;
43
48
  }): import("react").JSX.Element;
49
+ export {};
@@ -1,4 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /* Client, because this module's own JSX attaches handlers or calls hooks. A
4
+ server component may still import it -- that is the point -- it simply
5
+ renders on the client. The ones without this line (Brand, Empty, Icon,
6
+ Illustration, Pill, Progress, Skeleton, Stat, Textarea, Table) render on
7
+ the server, which is why the directive is per component rather than one
8
+ line at the package's front door. */
2
9
  /**
3
10
  * Something happened, and it does not need answering.
4
11
  *
@@ -29,9 +36,23 @@ const MARK = {
29
36
  * them does not pile up -- and paused while the pointer or focus is on it,
30
37
  * because a message that expires while you are reading it was never shown. */
31
38
  const LINGER = 4000;
32
- const Ctx = createContext(() => { });
39
+ /* `null`, not a no-op, so `ToastHost` can tell "nobody above me" from "a host
40
+ above me whose push happens to do nothing". `useToast` still hands back a
41
+ no-op, which is the old behaviour. */
42
+ const Ctx = createContext(null);
43
+ const NOOP = () => { };
33
44
  export function useToast() {
34
- return useContext(Ctx);
45
+ return useContext(Ctx) ?? NOOP;
46
+ }
47
+ /**
48
+ * Whether anything above will actually show a toast.
49
+ *
50
+ * For a component that has a second way of saying something and would rather
51
+ * use it than push into the void. `useToast` outside a host is a no-op that
52
+ * typechecks, which is the failure this exists to let you avoid.
53
+ */
54
+ export function useHasToastHost() {
55
+ return useContext(Ctx) !== null;
35
56
  }
36
57
  /**
37
58
  * The queue and the region, once, at the root.
@@ -48,8 +69,22 @@ export function useToast() {
48
69
  * reachable; React Aria makes each toast an `alertdialog` inside a live
49
70
  * `region` for that reason, so the announcement still happens and the button
50
71
  * can still be reached.
72
+ *
73
+ * **Nesting one inside another is a no-op, and that is what makes this usable
74
+ * by a package.** `@wtfalch/email`'s mail client wanted to say "Archived."
75
+ * and could not: `useToast` needs a host above it, so the component would
76
+ * have had to wrap itself in one -- and then an application that already had
77
+ * a host would carry two regions announcing into the same page. It wrote its
78
+ * own `role="status"` strip instead and said so in a docblock, which is a
79
+ * package working around a package.
80
+ *
81
+ * So a `ToastHost` that finds one above it renders its children and steps
82
+ * aside. A library wraps itself unconditionally and gets the right answer
83
+ * both ways: its own region in an app that has none, and the app's region --
84
+ * one queue, one landmark, one place messages pile up -- in an app that does.
51
85
  */
52
86
  export function ToastHost({ children }) {
87
+ const outer = useContext(Ctx);
53
88
  const queue = useMemo(() => new ToastQueue({ maxVisibleToasts: 4 }), []);
54
89
  /* What gets read aloud, and it is not the toast.
55
90
 
@@ -74,5 +109,11 @@ export function ToastHost({ children }) {
74
109
  say('');
75
110
  requestAnimationFrame(() => say(text));
76
111
  }, [queue]);
112
+ /* Somebody above owns the region. Render through: pushing into the outer
113
+ queue is what keeps one landmark and one pile of messages on the page.
114
+ After every hook, because a hook count that changes with context is a
115
+ React error. */
116
+ if (outer)
117
+ return _jsx(_Fragment, { children: children });
77
118
  return (_jsxs(Ctx.Provider, { value: push, children: [children, _jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: polite }), _jsx("div", { className: "sr-only", "aria-live": "assertive", "aria-atomic": "true", children: assertive }), _jsx(ToastRegion, { queue: queue, className: "toasts", children: ({ toast }) => (_jsxs(AriaToast, { toast: toast, className: `toast toast-${toast.content.tone}`, children: [_jsx(Icon, { name: MARK[toast.content.tone], size: 16, className: "toast-mark" }), _jsx(ToastContent, { className: "grow", children: _jsx(Text, { slot: "title", children: toast.content.text }) }), _jsx(Button, { slot: "close", className: "ghost size-sm", "aria-label": "Dismiss", children: _jsx(Icon, { name: "close", size: 14 }) })] })) })] }));
78
119
  }
@@ -1,33 +1,3 @@
1
- /**
2
- * A setting that takes effect as it moves.
3
- *
4
- * **A checkbox and a switch are not the same control, and the difference is not
5
- * visual.** A checkbox collects an answer: you tick it, and it applies when you
6
- * press Save. A switch *is* the action — flipping it changes the thing, now,
7
- * and there is nothing to confirm afterwards. That is why a switch reads as on
8
- * or off rather than ticked or blank: the two states are both settled, and
9
- * neither is a draft.
10
- *
11
- * Six settings in this app saved the moment they changed and were all drawn as
12
- * tick boxes: whether an applet is enabled, whether it reaches the internet,
13
- * whether a tool is granted, whether a server is trusted. Every one of them
14
- * promised a Save button that does not
15
- * exist. The header's status panel (gone since 2026-09-03) was the only one
16
- * that had it right, in a comment nobody else read.
17
- *
18
- * **`role="switch"`, so it is announced as one.** A screen reader says "on" and
19
- * "off" instead of "checked" and "not checked" — which is the same distinction
20
- * in words, and the only signal a non-visual reader gets that pressing it does
21
- * something immediately.
22
- *
23
- * **The label is the hit area.** A 30×18 target fails the minimum on its own,
24
- * and reaching for the words is what people do anyway.
25
- *
26
- * **One switch, or several checkboxes.** A lone binary setting is a switch. A
27
- * set you pick from is a list of checkboxes — fifteen switches in a column read
28
- * as fifteen unrelated settings rather than one choice with fifteen parts. See
29
- * `Checkbox`.
30
- */
31
1
  export default function Toggle({ label, hint, checked, onChange, disabled, said, labelHidden, size, className, }: {
32
2
  label: React.ReactNode;
33
3
  /** What it does, or what turning it off costs. Under the label, in the same
@@ -1,4 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /* Client, because this module's own JSX attaches handlers or calls hooks. A
4
+ server component may still import it -- that is the point -- it simply
5
+ renders on the client. The ones without this line (Brand, Empty, Icon,
6
+ Illustration, Pill, Progress, Skeleton, Stat, Textarea, Table) render on
7
+ the server, which is why the directive is per component rather than one
8
+ line at the package's front door. */
2
9
  /**
3
10
  * A setting that takes effect as it moves.
4
11
  *
@@ -1,4 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /* Client, because this module's own JSX attaches handlers or calls hooks. A
4
+ server component may still import it -- that is the point -- it simply
5
+ renders on the client. The ones without this line (Brand, Empty, Icon,
6
+ Illustration, Pill, Progress, Skeleton, Stat, Textarea, Table) render on
7
+ the server, which is why the directive is per component rather than one
8
+ line at the package's front door. */
2
9
  /**
3
10
  * A tooltip. It was called `Explain`, which named the intention rather than the
4
11
  * thing -- so it sat in a catalogue beside Button and Callout as though it were
@@ -1,26 +1,3 @@
1
- /**
2
- * A short walk round the chrome, once, after onboarding.
3
- *
4
- * Onboarding answers "what does this need to run". It says nothing about the
5
- * four controls somebody is then left alone with: two floating buttons in one
6
- * corner, a cog in another, and a `+` beside the board name. Every one of them
7
- * is an icon with a `title`, which is a tooltip you have to already suspect is
8
- * there to go looking for.
9
- *
10
- * **It points at the real thing.** Each step finds its target by selector and
11
- * cuts a hole in the scrim over it, so the control being described is the
12
- * control you can see — not a screenshot of one, which goes stale the first
13
- * time the button moves.
14
- *
15
- * **A step whose target is missing is skipped, not shown empty.** The chat
16
- * launcher is not on the page while the dock is open, and the studio button
17
- * changes what it does when a session is minimised. A tour that insists on
18
- * pointing at something that is not there would be describing a different app.
19
- *
20
- * **Escape ends it and it never comes back on its own.** This is the least
21
- * important thing on the screen and it is in the way of everything else; the
22
- * one unforgivable version is the one you cannot get out of.
23
- */
24
1
  export interface TourStop {
25
2
  /** Where it points. Missing from the page means the stop is skipped. */
26
3
  target: string;
@@ -1,4 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /* Client, because this module's own JSX attaches handlers or calls hooks. A
4
+ server component may still import it -- that is the point -- it simply
5
+ renders on the client. The ones without this line (Brand, Empty, Icon,
6
+ Illustration, Pill, Progress, Skeleton, Stat, Textarea, Table) render on
7
+ the server, which is why the directive is per component rather than one
8
+ line at the package's front door. */
2
9
  /**
3
10
  * A short walk round the chrome, once, after onboarding.
4
11
  *
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What a `Field` tells the control inside it.
3
+ *
4
+ * Its own module for the reason `iconNames.ts` and `tourMarker.ts` are:
5
+ * `fastRefresh.test.ts` holds every component module to exporting its
6
+ * component and nothing else, because a module exporting a value beside a
7
+ * component loses its Fast Refresh boundary and editing it re-runs every
8
+ * importer instead of swapping the component in place.
9
+ *
10
+ * The context exists so `Field`'s children can be elements rather than a
11
+ * function. A function cannot cross the server boundary, so the render prop
12
+ * made every page with a form a client component whether or not it needed to
13
+ * be -- which is the line all three consuming apps have in their
14
+ * `design.ts`.
15
+ */
16
+ export interface FieldWiring {
17
+ id: string;
18
+ 'aria-describedby': string | undefined;
19
+ 'aria-invalid': boolean | undefined;
20
+ /** The label element's own id, for a control a `<label for>` cannot name.
21
+ *
22
+ * `htmlFor` is enough for an `<input>`, which is what almost every caller
23
+ * wraps. It is not enough for `Select`, or for anything else built on a
24
+ * `<button>`: a button takes its accessible name from its *contents*, and
25
+ * a `<label for>` pointing at one is ignored by the name computation. A
26
+ * caller that put a `Select` in a `Field` got a label on screen and a
27
+ * control announcing only its current value — and the workaround was an
28
+ * `aria-label` repeating the label string, which is two literals and two
29
+ * chances to drift apart. That drift is exactly the bug `Field` exists to
30
+ * prevent, so: pass this as `aria-labelledby` and there is one string. */
31
+ labelId: string;
32
+ }
33
+ /** `null` outside a `Field`, so a control can tell "no field around me" from
34
+ * "a field that wired me with nothing". */
35
+ export declare const FieldWiringContext: import("react").Context<FieldWiring | null>;
36
+ /**
37
+ * What the `Field` above this control wired, if there is one.
38
+ *
39
+ * For the package's own controls, which apply it when the caller has not
40
+ * named an `id` themselves. A consumer building its own control can use it
41
+ * for the same reason: it is how a control gets a label without the caller
42
+ * threading four attributes by hand.
43
+ */
44
+ export declare function useFieldWiring(): FieldWiring | null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * What a `Field` tells the control inside it.
3
+ *
4
+ * Its own module for the reason `iconNames.ts` and `tourMarker.ts` are:
5
+ * `fastRefresh.test.ts` holds every component module to exporting its
6
+ * component and nothing else, because a module exporting a value beside a
7
+ * component loses its Fast Refresh boundary and editing it re-runs every
8
+ * importer instead of swapping the component in place.
9
+ *
10
+ * The context exists so `Field`'s children can be elements rather than a
11
+ * function. A function cannot cross the server boundary, so the render prop
12
+ * made every page with a form a client component whether or not it needed to
13
+ * be -- which is the line all three consuming apps have in their
14
+ * `design.ts`.
15
+ */
16
+ import { createContext, useContext } from 'react';
17
+ /** `null` outside a `Field`, so a control can tell "no field around me" from
18
+ * "a field that wired me with nothing". */
19
+ export const FieldWiringContext = createContext(null);
20
+ /**
21
+ * What the `Field` above this control wired, if there is one.
22
+ *
23
+ * For the package's own controls, which apply it when the caller has not
24
+ * named an `id` themselves. A consumer building its own control can use it
25
+ * for the same reason: it is how a control gets a label without the caller
26
+ * threading four attributes by hand.
27
+ */
28
+ export function useFieldWiring() {
29
+ return useContext(FieldWiringContext);
30
+ }
package/dist/index.d.ts CHANGED
@@ -22,7 +22,10 @@ export { default as Brand } from './components/Brand.js';
22
22
  export { BRAND_MARKS, BRAND_NAMES } from './components/brandMarks.js';
23
23
  export type { BrandName, FillMark, Mark, StrokeMark } from './components/brandMarks.js';
24
24
  export { default as Button } from './components/Button.js';
25
- export type { Props as ButtonProps } from './components/Button.js';
25
+ /** The union: the button's props, or the DOM's when `asChild` hands the
26
+ * element to the caller. `ButtonOwnProps` is the button-only half, for a
27
+ * wrapper that never slots. */
28
+ export type { ButtonProps, Props as ButtonOwnProps } from './components/Button.js';
26
29
  export { default as Callout } from './components/Callout.js';
27
30
  export { default as Card } from './components/Card.js';
28
31
  export { default as Checkbox } from './components/Checkbox.js';
@@ -33,7 +36,10 @@ export { DangerAction, default as DangerZone } from './components/DangerZone.js'
33
36
  export { default as Dialog } from './components/Dialog.js';
34
37
  export { default as Empty } from './components/Empty.js';
35
38
  export { default as Field } from './components/Field.js';
36
- export type { FieldWiring } from './components/Field.js';
39
+ /** The wiring a `Field` hands its control, and the hook that reads it —
40
+ * its own module, so `Field.tsx` keeps its Fast Refresh boundary. */
41
+ export { useFieldWiring } from './components/fieldWiring.js';
42
+ export type { FieldWiring } from './components/fieldWiring.js';
37
43
  export { default as Icon } from './components/Icon.js';
38
44
  /** A person in one line -- initials, name, address -- and the two pure
39
45
  * functions behind the disc, exported so a caller colouring something else
@@ -45,6 +51,9 @@ export type { IconName } from './components/iconNames.js';
45
51
  export { default as Illustration } from './components/Illustration.js';
46
52
  export { default as Input } from './components/Input.js';
47
53
  export type { Props as InputProps } from './components/Input.js';
54
+ /** A key, or a chord. The rule `Menu` and `Command` each had a private copy
55
+ * of, now one component a consumer can reach. */
56
+ export { default as Kbd } from './components/Kbd.js';
48
57
  export { default as Markdown } from './components/Markdown.js';
49
58
  export { default as Menu } from './components/Menu.js';
50
59
  export type { Item as MenuItem, Section as MenuSection } from './components/Menu.js';
@@ -66,7 +75,12 @@ export { default as ScrollArea } from './components/ScrollArea.js';
66
75
  export type { Props as ScrollAreaProps } from './components/ScrollArea.js';
67
76
  export { default as Select } from './components/Select.js';
68
77
  export { default as SizeGrid } from './components/SizeGrid.js';
78
+ /** The frame a signed-in surface stands in. Gates nothing, on purpose. */
79
+ export { default as Shell } from './components/Shell.js';
69
80
  export { default as Skeleton } from './components/Skeleton.js';
81
+ /** One figure, with what it counts over it. Three apps drew this before it
82
+ * was here. */
83
+ export { default as Stat } from './components/Stat.js';
70
84
  export { default as Slider } from './components/Slider.js';
71
85
  /** Two panes and a handle between them, the handle being a real `separator`
72
86
  * widget rather than a div with a mousedown listener. */
@@ -77,8 +91,14 @@ export type { Column } from './components/Table.js';
77
91
  export { default as Tabs } from './components/Tabs.js';
78
92
  export type { Tab, TabGroup } from './components/Tabs.js';
79
93
  export { default as Textarea } from './components/Textarea.js';
94
+ /** The theme control, and the blocking script that beats it to the paint. */
95
+ export { default as ThemeSwitch } from './components/ThemeSwitch.js';
96
+ export { THEME_STORAGE_KEY, themeChoiceScript, themeChoices, storedTheme } from './themes/choice.js';
97
+ export type { ThemeChoice } from './themes/choice.js';
80
98
  export type { Props as TextareaProps } from './components/Textarea.js';
81
- export { Toast, ToastHost, useToast } from './components/Toast.js';
99
+ /** `ToastHost` nests: one inside another renders through, so a package can
100
+ * wrap itself and still leave an application with a single region. */
101
+ export { Toast, ToastHost, useHasToastHost, useToast } from './components/Toast.js';
82
102
  export { default as Toggle } from './components/Toggle.js';
83
103
  export { default as Tooltip } from './components/Tooltip.js';
84
104
  export { default as Tour } from './components/Tour.js';
package/dist/index.js CHANGED
@@ -33,6 +33,9 @@ export { DangerAction, default as DangerZone } from './components/DangerZone.js'
33
33
  export { default as Dialog } from './components/Dialog.js';
34
34
  export { default as Empty } from './components/Empty.js';
35
35
  export { default as Field } from './components/Field.js';
36
+ /** The wiring a `Field` hands its control, and the hook that reads it —
37
+ * its own module, so `Field.tsx` keeps its Fast Refresh boundary. */
38
+ export { useFieldWiring } from './components/fieldWiring.js';
36
39
  export { default as Icon } from './components/Icon.js';
37
40
  /** A person in one line -- initials, name, address -- and the two pure
38
41
  * functions behind the disc, exported so a caller colouring something else
@@ -42,6 +45,9 @@ export { hueOf, initialsOf } from './components/initials.js';
42
45
  export { ICON_NAMES } from './components/iconNames.js';
43
46
  export { default as Illustration } from './components/Illustration.js';
44
47
  export { default as Input } from './components/Input.js';
48
+ /** A key, or a chord. The rule `Menu` and `Command` each had a private copy
49
+ * of, now one component a consumer can reach. */
50
+ export { default as Kbd } from './components/Kbd.js';
45
51
  export { default as Markdown } from './components/Markdown.js';
46
52
  export { default as Menu } from './components/Menu.js';
47
53
  export { default as Modal } from './components/Modal.js';
@@ -61,7 +67,12 @@ export { Row, Rows } from './components/Rows.js';
61
67
  export { default as ScrollArea } from './components/ScrollArea.js';
62
68
  export { default as Select } from './components/Select.js';
63
69
  export { default as SizeGrid } from './components/SizeGrid.js';
70
+ /** The frame a signed-in surface stands in. Gates nothing, on purpose. */
71
+ export { default as Shell } from './components/Shell.js';
64
72
  export { default as Skeleton } from './components/Skeleton.js';
73
+ /** One figure, with what it counts over it. Three apps drew this before it
74
+ * was here. */
75
+ export { default as Stat } from './components/Stat.js';
65
76
  export { default as Slider } from './components/Slider.js';
66
77
  /** Two panes and a handle between them, the handle being a real `separator`
67
78
  * widget rather than a div with a mousedown listener. */
@@ -69,7 +80,12 @@ export { default as SplitPane } from './components/SplitPane.js';
69
80
  export { default as Table } from './components/Table.js';
70
81
  export { default as Tabs } from './components/Tabs.js';
71
82
  export { default as Textarea } from './components/Textarea.js';
72
- export { ToastHost, useToast } from './components/Toast.js';
83
+ /** The theme control, and the blocking script that beats it to the paint. */
84
+ export { default as ThemeSwitch } from './components/ThemeSwitch.js';
85
+ export { THEME_STORAGE_KEY, themeChoiceScript, themeChoices, storedTheme } from './themes/choice.js';
86
+ /** `ToastHost` nests: one inside another renders through, so a package can
87
+ * wrap itself and still leave an application with a single region. */
88
+ export { ToastHost, useHasToastHost, useToast } from './components/Toast.js';
73
89
  export { default as Toggle } from './components/Toggle.js';
74
90
  export { default as Tooltip } from './components/Tooltip.js';
75
91
  export { default as Tour } from './components/Tour.js';