@parallelworks/ui 0.46.0 → 0.48.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 (56) hide show
  1. package/README.md +17 -2
  2. package/dist/ai/components/ChatInput.js +1 -1
  3. package/dist/components/ColorInput.d.ts +36 -0
  4. package/dist/components/ColorInput.js +82 -0
  5. package/dist/components/Combobox.d.ts +5 -2
  6. package/dist/components/Combobox.js +12 -11
  7. package/dist/components/CreateModal.js +2 -2
  8. package/dist/components/DatePicker.d.ts +5 -1
  9. package/dist/components/DatePicker.js +7 -7
  10. package/dist/components/Field.d.ts +19 -5
  11. package/dist/components/Field.js +21 -4
  12. package/dist/components/Input.d.ts +11 -1
  13. package/dist/components/Input.js +9 -6
  14. package/dist/components/MenuEntries.js +1 -1
  15. package/dist/components/NumberInput.d.ts +1 -0
  16. package/dist/components/OneTimeCodeInput.d.ts +23 -0
  17. package/dist/components/OneTimeCodeInput.js +39 -0
  18. package/dist/components/Provider.d.ts +12 -3
  19. package/dist/components/Provider.js +14 -2
  20. package/dist/components/SearchInput.d.ts +1 -0
  21. package/dist/components/Section.d.ts +7 -2
  22. package/dist/components/Section.js +58 -39
  23. package/dist/components/SegmentedControl.d.ts +4 -2
  24. package/dist/components/SegmentedControl.js +4 -2
  25. package/dist/components/Select.d.ts +2 -1
  26. package/dist/components/Select.js +1 -1
  27. package/dist/components/Tabs.js +1 -0
  28. package/dist/components/ThemeEditor.js +15 -52
  29. package/dist/components/ToggleGroup.d.ts +42 -0
  30. package/dist/components/ToggleGroup.js +69 -0
  31. package/dist/components/fieldBox.js +1 -1
  32. package/dist/form/Fields/BooleanField.js +1 -1
  33. package/dist/form/Fields/ColorField.js +6 -10
  34. package/dist/form/Fields/DropdownField.js +1 -1
  35. package/dist/form/Fields/DurationField.js +1 -1
  36. package/dist/form/Fields/MultiDropdownField.js +1 -1
  37. package/dist/form/Fields/StringField.js +1 -1
  38. package/dist/form/Fields/TextAreaField.js +1 -1
  39. package/dist/form/Form.js +1 -1
  40. package/dist/graph/GraphEditorDialogs.js +1 -1
  41. package/dist/graph/InputDialog.js +2 -2
  42. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  43. package/dist/graph/editorFields.js +2 -2
  44. package/dist/graph/inputRefs.js +1 -1
  45. package/dist/graph/stepWith.js +1 -1
  46. package/dist/icons.d.ts +17 -1
  47. package/dist/icons.js +19 -3
  48. package/dist/index.d.ts +5 -1
  49. package/dist/index.js +14 -10
  50. package/dist/list/ListViewControls.js +1 -1
  51. package/dist/list/listSearch.js +1 -1
  52. package/dist/styles/theme.css +1 -1
  53. package/dist/styles.css +1 -1
  54. package/dist/theme/derive.d.ts +3 -0
  55. package/dist/theme/derive.js +11 -7
  56. package/package.json +1 -1
package/README.md CHANGED
@@ -32,9 +32,14 @@ Each need has one component. Reach for it before styling markup by hand.
32
32
  | Work under way | `Loader`; `Skeleton` in a `LoadingRegion` for the shape of content still loading |
33
33
  | Nothing to show yet, or nothing matches | `EmptyState` |
34
34
  | A text field | `Input`, `Textarea`, or a control framed by `Field`, whose `actions` hold buttons that act on it |
35
- | A number, a date | `NumberInput`, `DatePicker` |
35
+ | A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
36
+ | A one-time code, such as a sign-in check | `OneTimeCodeInput` |
37
+ | A labeled value nobody edits, among fields | `FieldValue` in a `Field`; `DescriptionList` for a set of them alone |
38
+ | An error beside a control outside a `Field` | `ErrorText` |
39
+ | A dense field in a toolbar or a table cell | any field control with `size="sm"`; a width in `className` wins, and `cx` merges classes the same way |
36
40
  | On or off | `Switch`; `Checkbox` in a list of things to tick, several of which may be on |
37
41
  | One of a few choices, all shown | `RadioGroup` in a form; `SegmentedControl` in a toolbar, or as a field's control in a `Field group` |
42
+ | Narrowing a list by values, such as statuses: several at once, or one or none | `ToggleGroup` |
38
43
  | Narrowing what is shown by typing | `SearchInput`; `ListSearchControl` on a list page's toolbar |
39
44
  | A value from a list | `Select`, or `Combobox` when the list needs filtering |
40
45
  | Free text with suggestions, or tags | `Autocomplete`; `Combobox` with `multiple creatable` |
@@ -78,6 +83,12 @@ Some pairs look alike and are split on purpose:
78
83
  thing, each with its own panel or route. A `SegmentedControl` sets a value,
79
84
  such as a time range, that changes what one view shows. `Tabs` with
80
85
  `variant="segmented"` look the same, so the choice rests on what pressing does.
86
+ - **SegmentedControl or ToggleGroup.** A `SegmentedControl` holds a setting
87
+ that always has exactly one value, such as a time range or which view of the
88
+ data to show. A `ToggleGroup` filters a list by values and may be off: its
89
+ options are pressed independently, or one or none with `multiple={false}`,
90
+ and `all` leads with an All option for "no filter". Each option can carry a
91
+ status dot or icon and a count.
81
92
  - **SearchInput or ListSearchControl.** A `SearchInput` is a box that is
82
93
  always shown and narrows what sits beside it, such as a panel's rows or a
83
94
  log's lines. `ListSearchControl` belongs to a list page's toolbar: it folds to
@@ -354,7 +365,11 @@ meaning across the apps.
354
365
  instance. `RemoveIcon` takes an item out of a list, while `CloseIcon` closes,
355
366
  dismisses or clears; they share the X glyph, the convention for both.
356
367
  `DestroyIcon` tears down what was provisioned, while `TrashIcon` deletes a
357
- record.
368
+ record. `RemoveUserIcon` takes someone out of a group or project, while
369
+ `DisableUserIcon` turns off their account. `ChangesRequestedIcon` asks for
370
+ changes to something sent, while `FlaggedIcon` marks an item for attention.
371
+ `PendingIcon` is a requirement not met yet and `NotRunningIcon` a run not
372
+ started; they share the empty circle, so keep them out of the same list.
358
373
  - **Adding an icon:** pick the [Lucide glyph](https://lucide.dev/icons) and add
359
374
  `export const NameIcon = /* @__PURE__ */ icon(Glyph)` to `src/icons.tsx`,
360
375
  named for what it means rather than what it shows. Reuse an existing name
@@ -261,7 +261,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
261
261
  "aria-label": inputLabel ?? effectivePlaceholder,
262
262
  disabled,
263
263
  rows: 1,
264
- className: cx("theme-text block w-full resize-none overflow-hidden", "bg-transparent border-0 px-4", toolbar ? "pt-3.5 pb-1" : "pt-3 pb-1", "placeholder:theme-muted-text", "focus:outline-none focus:ring-0", "disabled:opacity-50 disabled:cursor-not-allowed", "text-[1rem] leading-relaxed")
264
+ className: cx("theme-text block w-full resize-none overflow-hidden", "bg-transparent border-0 px-4", toolbar ? "pt-3.5 pb-1" : "pt-3 pb-1", "placeholder:text-(--theme-placeholder)", "focus:outline-none focus:ring-0", "disabled:opacity-50 disabled:cursor-not-allowed", "text-[1rem] leading-relaxed")
265
265
  })
266
266
  }), !toolbar && action]
267
267
  }),
@@ -0,0 +1,36 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type FieldBoxSize } from './fieldBox.js';
3
+ export interface ColorInputProps {
4
+ /** A CSS color the theme reads: a hex, `oklch()` or `color(display-p3 …)`. */
5
+ value: string;
6
+ /** Called with each color that parses, as typed or as picked: a picked one is a hex. */
7
+ onValueChange: (color: string) => void;
8
+ /** Without a label the row stands alone; name it with `aria-label`. */
9
+ label?: ReactNode;
10
+ /** Beside the label and outside it, so it stays out of the field's name. */
11
+ hint?: ReactNode;
12
+ /** Help under the field. */
13
+ description?: ReactNode;
14
+ error?: ReactNode;
15
+ /** `sm` for dense places such as toolbars and table cells. */
16
+ size?: FieldBoxSize | undefined;
17
+ /** The hex field's id, which a label names. */
18
+ id?: string | undefined;
19
+ /** Submits the color under this name with a form. */
20
+ name?: string | undefined;
21
+ disabled?: boolean | undefined;
22
+ required?: boolean | undefined;
23
+ 'aria-label'?: string | undefined;
24
+ 'aria-labelledby'?: string | undefined;
25
+ 'aria-describedby'?: string | undefined;
26
+ 'aria-invalid'?: boolean | undefined;
27
+ 'aria-required'?: boolean | undefined;
28
+ /** On the row that holds the swatch and the hex field: its width or margin. */
29
+ className?: string | undefined;
30
+ }
31
+ /**
32
+ * A color as a swatch that opens the system picker, beside a field its hex can be typed or pasted
33
+ * in. A half-typed color stays in the field, marked invalid, until it parses; leaving the field
34
+ * puts back the last color that did.
35
+ */
36
+ export declare function ColorInput({ value, onValueChange, label, hint, description, error, size, id, name, disabled, required, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, className, }: ColorInputProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
@@ -0,0 +1,82 @@
1
+ import { cx } from "../cx.js";
2
+ import { useStrings } from "./Provider.js";
3
+ import { parseColor, toHex } from "../theme/color.js";
4
+ import { ErrorText } from "./Field.js";
5
+ import { fieldBox } from "./fieldBox.js";
6
+ import { framed } from "./Input.js";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { useId, useState } from "react";
9
+ //#region src/components/ColorInput.tsx
10
+ var swatchClassName = cx("shrink-0 cursor-pointer p-0.5", "[&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded [&::-webkit-color-swatch]:border-0", "[&::-moz-color-swatch]:rounded [&::-moz-color-swatch]:border-0");
11
+ /**
12
+ * A color as a swatch that opens the system picker, beside a field its hex can be typed or pasted
13
+ * in. A half-typed color stays in the field, marked invalid, until it parses; leaving the field
14
+ * puts back the last color that did.
15
+ */
16
+ function ColorInput({ value, onValueChange, label, hint, description, error, size = "md", id, name, disabled, required, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, className }) {
17
+ const t = useStrings().colorInput;
18
+ const [draft, setDraft] = useState(null);
19
+ const errorId = useId();
20
+ const parsed = parseColor(value);
21
+ const unparsed = draft !== null && parseColor(draft) === null;
22
+ const named = typeof label === "string" ? label : ariaLabel;
23
+ const ownError = unparsed && label === void 0;
24
+ return framed({
25
+ label,
26
+ hint,
27
+ description,
28
+ error: error ?? (unparsed ? t.invalid : void 0)
29
+ }, {
30
+ id,
31
+ required,
32
+ disabled
33
+ }, (control) => /* @__PURE__ */ jsxs("div", {
34
+ className: cx("flex w-full flex-col gap-1", className),
35
+ children: [/* @__PURE__ */ jsxs("div", {
36
+ className: cx("flex items-center", size === "sm" ? "gap-1.5" : "gap-2"),
37
+ children: [/* @__PURE__ */ jsx("input", {
38
+ type: "color",
39
+ "aria-label": t.pick(named),
40
+ value: parsed ? toHex(parsed) : "#000000",
41
+ disabled,
42
+ onChange: (e) => {
43
+ setDraft(null);
44
+ onValueChange(e.target.value);
45
+ },
46
+ className: cx(fieldBox({ size }), swatchClassName, size === "sm" ? "w-8" : "w-10")
47
+ }), /* @__PURE__ */ jsx("input", {
48
+ id,
49
+ name,
50
+ "aria-label": ariaLabel,
51
+ "aria-labelledby": ariaLabelledBy,
52
+ ...control,
53
+ "aria-describedby": [
54
+ ariaDescribedBy,
55
+ control["aria-describedby"],
56
+ ownError && errorId
57
+ ].filter(Boolean).join(" ") || void 0,
58
+ "aria-invalid": control["aria-invalid"] ?? (ariaInvalid || unparsed || void 0),
59
+ "aria-required": control["aria-required"] ?? (ariaRequired || void 0),
60
+ required,
61
+ disabled,
62
+ spellCheck: false,
63
+ autoComplete: "off",
64
+ value: draft ?? value,
65
+ onChange: (e) => {
66
+ const text = e.target.value;
67
+ if (parseColor(text)) {
68
+ setDraft(null);
69
+ onValueChange(text.trim().toLowerCase());
70
+ } else setDraft(text);
71
+ },
72
+ onBlur: () => setDraft(null),
73
+ className: cx(fieldBox({ size }), "font-mono")
74
+ })]
75
+ }), ownError && /* @__PURE__ */ jsx(ErrorText, {
76
+ id: errorId,
77
+ children: t.invalid
78
+ })]
79
+ }));
80
+ }
81
+ //#endregion
82
+ export { ColorInput };
@@ -1,5 +1,6 @@
1
1
  import { type ReactElement, type ReactNode } from 'react';
2
2
  import { type ComboboxItems } from './comboboxItems.js';
3
+ import { type FieldBoxSize } from './fieldBox.js';
3
4
  export type { ComboboxGroup, ComboboxItem, ComboboxItems, ComboboxLeaf, } from './comboboxItems.js';
4
5
  interface FieldProps {
5
6
  id?: string | undefined;
@@ -11,7 +12,9 @@ interface FieldProps {
11
12
  'aria-describedby'?: string | undefined;
12
13
  'aria-invalid'?: boolean | undefined;
13
14
  'aria-required'?: boolean | undefined;
14
- /** Classes for the field box: border, background, size. */
15
+ /** `sm` for dense places such as toolbars and table cells. */
16
+ size?: FieldBoxSize | undefined;
17
+ /** Classes for the field box: border, background, width. */
15
18
  className?: string | undefined;
16
19
  }
17
20
  type SelectionProps<Value> = {
@@ -54,4 +57,4 @@ export interface AutocompleteProps extends FieldProps {
54
57
  /** A pick-from-the-list field: one value, or several with `multiple`. Typing filters the list. */
55
58
  export declare function Combobox<Value = string>(props: ComboboxProps<Value>): import("react").JSX.Element;
56
59
  /** A free-text field that suggests values as you type. */
57
- export declare function Autocomplete({ items, value, defaultValue, onValueChange, className, placeholder, disabled, required, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, }: AutocompleteProps): import("react").JSX.Element;
60
+ export declare function Autocomplete({ items, value, defaultValue, onValueChange, className, placeholder, disabled, required, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, size, }: AutocompleteProps): import("react").JSX.Element;
@@ -4,8 +4,8 @@ import { panelClassName, panelItemClassName, positionerClassName } from "./float
4
4
  import { Tooltip } from "./Tooltip.js";
5
5
  import { useStrings } from "./Provider.js";
6
6
  import { useQueryMatch } from "./search.js";
7
- import { isComboboxGroup } from "./comboboxItems.js";
8
7
  import { fieldBox } from "./fieldBox.js";
8
+ import { isComboboxGroup } from "./comboboxItems.js";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { useCallback, useImperativeHandle, useMemo, useRef, useState } from "react";
11
11
  import { Combobox } from "@base-ui/react/combobox";
@@ -68,17 +68,18 @@ function useEntryFilter() {
68
68
  return useCallback((entry, query) => entry.created === true || match(query, entry.label, typeof entry.value === "string" ? entry.value : void 0, entry.group), [match]);
69
69
  }
70
70
  var entryLabel = (entry) => entry?.label ?? "";
71
- var fieldClassName = cx(fieldBox({
71
+ var fieldClassName = (size) => cx(fieldBox({
72
+ size,
72
73
  within: true,
73
74
  multiline: true,
74
75
  padded: false
75
76
  }), "relative flex items-center gap-1");
76
- var inputClassName = "h-full min-w-12 flex-1 self-stretch border-0 bg-transparent py-1.5 pl-3 text-inherit outline-none placeholder:text-(--theme-muted-text-color) disabled:cursor-not-allowed";
77
+ var inputClassName = (size) => cx("min-w-12 flex-1 self-stretch border-0 bg-transparent p-0 text-inherit outline-none placeholder:text-(--theme-placeholder) disabled:cursor-not-allowed", size === "sm" ? "pl-2" : "pl-3");
77
78
  var triggerClassName = "flex shrink-0 items-center self-stretch px-2 text-(--theme-muted-text-color) disabled:cursor-not-allowed";
78
79
  var popupInputClassName = fieldBox({ size: "sm" });
79
80
  /** A pick-from-the-list field: one value, or several with `multiple`. Typing filters the list. */
80
81
  function Combobox$1(props) {
81
- const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip } = props;
82
+ const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip, size = "md" } = props;
82
83
  const entries = useEntries(items);
83
84
  const filter = useEntryFilter();
84
85
  const strings = useStrings();
@@ -122,7 +123,7 @@ function Combobox$1(props) {
122
123
  "aria-invalid": ariaInvalid || void 0,
123
124
  "aria-required": ariaRequired || required || void 0,
124
125
  autoComplete: "off",
125
- className: trigger ? popupInputClassName : inputClassName
126
+ className: trigger ? popupInputClassName : inputClassName(size)
126
127
  });
127
128
  const itemRow = (entry, rowProps) => /* @__PURE__ */ jsxs(Combobox.Item, {
128
129
  value: entry,
@@ -158,9 +159,9 @@ function Combobox$1(props) {
158
159
  renderItem: itemRow
159
160
  })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Combobox.InputGroup, {
160
161
  "data-testid": "combobox",
161
- className: cx(fieldClassName, className),
162
+ className: cx(fieldClassName(size), className),
162
163
  children: [props.multiple ? /* @__PURE__ */ jsxs(Combobox.Chips, {
163
- className: "flex min-w-0 flex-1 flex-wrap items-center gap-1 py-1 pl-1",
164
+ className: cx("flex min-w-0 flex-1 flex-wrap items-center gap-1 pl-1", size === "sm" ? "py-0.5" : "py-1"),
164
165
  children: [/* @__PURE__ */ jsx(Combobox.Value, { children: (values) => values.map((entry) => /* @__PURE__ */ jsxs(Combobox.Chip, {
165
166
  "aria-label": entry.label,
166
167
  className: "flex items-center gap-1 rounded bg-(--theme-muted-panel-bg) py-0.5 pr-0.5 pl-1.5 text-xs outline-none data-highlighted:bg-(--theme-hover)",
@@ -171,7 +172,7 @@ function Combobox$1(props) {
171
172
  })]
172
173
  }, entry.label)) }), input]
173
174
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [selectedIcon && /* @__PURE__ */ jsx("span", {
174
- className: "flex shrink-0 items-center pl-3",
175
+ className: cx("flex shrink-0 items-center", size === "sm" ? "pl-2" : "pl-3"),
175
176
  children: selectedIcon
176
177
  }), input] }), /* @__PURE__ */ jsx(Combobox.Trigger, {
177
178
  "aria-label": t.showOptions,
@@ -216,7 +217,7 @@ function Combobox$1(props) {
216
217
  });
217
218
  }
218
219
  /** A free-text field that suggests values as you type. */
219
- function Autocomplete$1({ items, value, defaultValue, onValueChange, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired }) {
220
+ function Autocomplete$1({ items, value, defaultValue, onValueChange, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, size = "md" }) {
220
221
  const entries = useEntries(items);
221
222
  const filter = useEntryFilter();
222
223
  const t = useStrings().dropdown;
@@ -234,7 +235,7 @@ function Autocomplete$1({ items, value, defaultValue, onValueChange, className,
234
235
  virtualized: true,
235
236
  children: [/* @__PURE__ */ jsxs(Autocomplete.InputGroup, {
236
237
  "data-testid": "combobox",
237
- className: cx(fieldClassName, className),
238
+ className: cx(fieldClassName(size), className),
238
239
  children: [/* @__PURE__ */ jsx(Autocomplete.Input, {
239
240
  id,
240
241
  placeholder,
@@ -244,7 +245,7 @@ function Autocomplete$1({ items, value, defaultValue, onValueChange, className,
244
245
  "aria-invalid": ariaInvalid || void 0,
245
246
  "aria-required": ariaRequired || required || void 0,
246
247
  autoComplete: "off",
247
- className: inputClassName
248
+ className: inputClassName(size)
248
249
  }), entries.flat.length > 0 && /* @__PURE__ */ jsx(Autocomplete.Trigger, {
249
250
  "aria-label": t.showOptions,
250
251
  "data-testid": "combobox-button",
@@ -124,7 +124,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, namePlaceho
124
124
  placeholder: namePlaceholder,
125
125
  "aria-label": namePlaceholder,
126
126
  onAnimationEnd: () => setNameFlash(false),
127
- className: cx("mt-2 -mb-1 -mx-1.5 w-[calc(100%+0.75rem)] rounded-md border-0 px-1.5 py-1 bg-transparent shadow-none text-lg font-medium text-(--theme-app) placeholder:text-(--theme-muted-text-color) focus:outline-none", nameFlash && "animate-field-flash", nameMissing ? "ring-1 ring-(--theme-error)" : "focus:ring-0")
127
+ className: cx("mt-2 -mb-1 -mx-1.5 w-[calc(100%+0.75rem)] rounded-md border-0 px-1.5 py-1 bg-transparent shadow-none text-lg font-medium text-(--theme-app) placeholder:text-(--theme-placeholder) focus:outline-none", nameFlash && "animate-field-flash", nameMissing ? "ring-1 ring-(--theme-error)" : "focus:ring-0")
128
128
  }),
129
129
  nameError && /* @__PURE__ */ jsx("p", {
130
130
  className: "mt-1 text-xs text-(--theme-error)",
@@ -142,7 +142,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, namePlaceho
142
142
  placeholder: descriptionText,
143
143
  "aria-label": descriptionText,
144
144
  rows: 3,
145
- className: "mt-1.5 w-full border-0 p-0 bg-transparent shadow-none resize-none text-sm text-(--theme-app) placeholder:text-(--theme-muted-text-color) focus:outline-none focus:ring-0"
145
+ className: "mt-1.5 w-full border-0 p-0 bg-transparent shadow-none resize-none text-sm text-(--theme-app) placeholder:text-(--theme-placeholder) focus:outline-none focus:ring-0"
146
146
  }) : /* @__PURE__ */ jsx("div", { className: "mt-3" })
147
147
  ] }),
148
148
  children && /* @__PURE__ */ jsx("div", {
@@ -1,3 +1,4 @@
1
+ import { type FieldBoxSize } from './fieldBox.js';
1
2
  export interface DatePickerProps {
2
3
  /** As a native date input holds it: `yyyy-MM-dd`, or with `withTime` the local
3
4
  * `yyyy-MM-ddTHH:mm` of `datetime-local`. Empty while nothing is chosen. */
@@ -21,6 +22,9 @@ export interface DatePickerProps {
21
22
  'aria-describedby'?: string | undefined;
22
23
  'aria-invalid'?: boolean | undefined;
23
24
  'aria-required'?: boolean | undefined;
25
+ /** `sm` for dense places such as toolbars and table cells. */
26
+ size?: FieldBoxSize | undefined;
27
+ /** On the row that holds the date and the time: its width or margin. */
24
28
  className?: string | undefined;
25
29
  }
26
30
  /**
@@ -28,4 +32,4 @@ export interface DatePickerProps {
28
32
  * date or datetime-local input holds it, so it reads the same in forms and URLs; months, weekdays
29
33
  * and the shown date follow the reader's locale.
30
34
  */
31
- export declare function DatePicker({ value, onValueChange, withTime, min, max, timeStep, required, disabled, placeholder, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, className, }: DatePickerProps): import("react").JSX.Element;
35
+ export declare function DatePicker({ value, onValueChange, withTime, min, max, timeStep, required, disabled, placeholder, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, size, className, }: DatePickerProps): import("react").JSX.Element;
@@ -65,13 +65,13 @@ var calendarClassNames = {
65
65
  outside: "text-(--theme-muted-text-color) opacity-60",
66
66
  disabled: "text-(--theme-muted-text-color) opacity-40"
67
67
  };
68
- var triggerClassName = cx(fieldBox(), "flex flex-1 min-w-40 cursor-pointer items-center gap-2 text-left");
68
+ var triggerClassName = (size) => cx(fieldBox({ size }), "flex flex-1 cursor-pointer items-center text-left", size === "sm" ? "min-w-32 gap-1.5" : "min-w-40 gap-2");
69
69
  /**
70
70
  * A day from a calendar, and with `withTime` a time beside it. The value is a string as a native
71
71
  * date or datetime-local input holds it, so it reads the same in forms and URLs; months, weekdays
72
72
  * and the shown date follow the reader's locale.
73
73
  */
74
- function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep = 60, required = false, disabled = false, placeholder, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, className }) {
74
+ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep = 60, required = false, disabled = false, placeholder, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, size = "md", className }) {
75
75
  const t = useStrings().datePicker;
76
76
  const { common } = useStrings();
77
77
  const [open, setOpen] = useState(false);
@@ -95,7 +95,7 @@ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep
95
95
  };
96
96
  const sameDay = (bound) => day !== void 0 && parseDay(bound)?.getTime() === day.getTime();
97
97
  return /* @__PURE__ */ jsxs("div", {
98
- className: cx("flex w-full items-center gap-2", className),
98
+ className: cx("flex w-full items-center", size === "sm" ? "gap-1.5" : "gap-2", className),
99
99
  children: [/* @__PURE__ */ jsxs(Popover.Root, {
100
100
  open,
101
101
  onOpenChange: setOpen,
@@ -107,12 +107,12 @@ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep
107
107
  "aria-describedby": ariaDescribedBy,
108
108
  "aria-invalid": ariaInvalid || void 0,
109
109
  "aria-required": ariaRequired || void 0,
110
- className: triggerClassName,
110
+ className: triggerClassName(size),
111
111
  children: [/* @__PURE__ */ jsx(CalendarIcon, {
112
112
  "aria-hidden": "true",
113
- className: "size-3.5 shrink-0 text-(--theme-muted-text-color)"
113
+ className: cx("shrink-0 text-(--theme-muted-text-color)", size === "sm" ? "size-3" : "size-3.5")
114
114
  }), /* @__PURE__ */ jsx("span", {
115
- className: cx("truncate", !day && "text-(--theme-muted-text-color)"),
115
+ className: cx("truncate", !day && "text-(--theme-placeholder)"),
116
116
  children: day ? shownDay.format(day) : placeholder ?? t.pickDate
117
117
  })]
118
118
  }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
@@ -183,7 +183,7 @@ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep
183
183
  const next = event.target.value;
184
184
  if (next) emit(day ?? earliest ?? /* @__PURE__ */ new Date(), next);
185
185
  },
186
- className: cx(fieldBox(), "w-auto tabular-nums")
186
+ className: cx(fieldBox({ size }), "w-auto tabular-nums")
187
187
  })]
188
188
  });
189
189
  }
@@ -1,4 +1,5 @@
1
- import { type ReactNode } from 'react';
1
+ import { type OutputHTMLAttributes, type ReactNode } from 'react';
2
+ import type { FieldBoxSize } from './fieldBox.js';
2
3
  /** What a field's control takes to be named, described and marked by its field. Spread it
3
4
  * on the control: `{(control) => <input {...control} />}`. */
4
5
  export interface FieldControlProps {
@@ -43,12 +44,15 @@ export interface FieldProps {
43
44
  id?: string | undefined;
44
45
  className?: string | undefined;
45
46
  /** The control, or a function that places the control props on it. A Base UI control, such
46
- * as Select, Combobox, NumberInput, Checkbox or Switch, needs only the `id`. */
47
+ * as Select, Combobox, NumberInput, Checkbox or Switch, needs only the `id`. A value shown
48
+ * but not edited goes in a `FieldValue`. */
47
49
  children: ReactNode | ((control: FieldControlProps) => ReactNode);
48
50
  }
49
- /** An error in the look a Field gives its own, for one under a part of a control rather than
50
- * the whole of it, such as a row of a list editor. */
51
- export declare function ErrorText({ children }: {
51
+ /** An error in the look a Field gives its own, for a control outside a Field, or for a part of
52
+ * one rather than the whole of it, such as a row of a list editor. Give it an `id` and name it
53
+ * in the control's `aria-describedby` so the error is read with the control. */
54
+ export declare function ErrorText({ id, children }: {
55
+ id?: string | undefined;
52
56
  children: ReactNode;
53
57
  }): import("react").JSX.Element | null;
54
58
  /** A control with its label, help and error, tied to it for assistive technology. One frame
@@ -68,3 +72,13 @@ export declare function RevealToggle({ revealed, onRevealedChange, disabled, }:
68
72
  onRevealedChange: (revealed: boolean) => void;
69
73
  disabled?: boolean | undefined;
70
74
  }): import("react").JSX.Element;
75
+ export interface FieldValueProps extends OutputHTMLAttributes<HTMLOutputElement> {
76
+ /** Matches the height and text of the controls around it, as `size` does on a control. */
77
+ size?: FieldBoxSize | undefined;
78
+ }
79
+ /** A value a Field shows that nobody edits, such as one worked out from other fields. It is an
80
+ * `<output>`, which the Field's label names and which reads out when the value changes; list
81
+ * the controls it is worked out from in `htmlFor`. It keeps a control's height, so it lines
82
+ * up with the fields around it. For a set of names and values on their own, use
83
+ * DescriptionList. */
84
+ export declare function FieldValue({ size, className, children, ...props }: FieldValueProps): import("react").JSX.Element;
@@ -20,10 +20,12 @@ function useFieldControl() {
20
20
  var labelClassName = "flex items-center gap-1 text-sm font-medium text-(--theme-app)";
21
21
  var actionsClassName = "ml-auto flex shrink-0 items-center gap-1";
22
22
  var errorTextClassName = "flex items-center gap-1.5 text-xs text-(--theme-error)";
23
- /** An error in the look a Field gives its own, for one under a part of a control rather than
24
- * the whole of it, such as a row of a list editor. */
25
- function ErrorText({ children }) {
23
+ /** An error in the look a Field gives its own, for a control outside a Field, or for a part of
24
+ * one rather than the whole of it, such as a row of a list editor. Give it an `id` and name it
25
+ * in the control's `aria-describedby` so the error is read with the control. */
26
+ function ErrorText({ id, children }) {
26
27
  return children ? /* @__PURE__ */ jsxs("div", {
28
+ id,
27
29
  role: "alert",
28
30
  className: errorTextClassName,
29
31
  children: [/* @__PURE__ */ jsx(WarningIcon, {
@@ -155,5 +157,20 @@ function RevealToggle({ revealed, onRevealedChange, disabled }) {
155
157
  })
156
158
  });
157
159
  }
160
+ /** A value a Field shows that nobody edits, such as one worked out from other fields. It is an
161
+ * `<output>`, which the Field's label names and which reads out when the value changes; list
162
+ * the controls it is worked out from in `htmlFor`. It keeps a control's height, so it lines
163
+ * up with the fields around it. For a set of names and values on their own, use
164
+ * DescriptionList. */
165
+ function FieldValue({ size = "md", className, children, ...props }) {
166
+ const control = useFieldControl();
167
+ return /* @__PURE__ */ jsx("output", {
168
+ id: control?.id,
169
+ "aria-describedby": control?.["aria-describedby"],
170
+ ...props,
171
+ className: cx("flex min-w-0 items-center break-words text-(--theme-app)", size === "sm" ? "min-h-7 text-xs" : "min-h-9 text-sm", className),
172
+ children
173
+ });
174
+ }
158
175
  //#endregion
159
- export { ErrorText, Field$1 as Field, FieldOption, FieldRowContext, RevealToggle, useFieldControl };
176
+ export { ErrorText, Field$1 as Field, FieldOption, FieldRowContext, FieldValue, RevealToggle, useFieldControl };
@@ -1,4 +1,6 @@
1
1
  import type { InputHTMLAttributes, ReactNode, TextareaHTMLAttributes } from 'react';
2
+ import { type FieldControlProps } from './Field.js';
3
+ import { type FieldBoxSize } from './fieldBox.js';
2
4
  interface FramedProps {
3
5
  /** Without a label the box stands alone; name it with `aria-label`. */
4
6
  label?: ReactNode;
@@ -8,11 +10,19 @@ interface FramedProps {
8
10
  description?: ReactNode;
9
11
  error?: ReactNode;
10
12
  mono?: boolean;
13
+ /** `sm` for dense places such as toolbars and table cells. */
14
+ size?: FieldBoxSize | undefined;
11
15
  }
12
- export interface InputProps extends InputHTMLAttributes<HTMLInputElement>, FramedProps {
16
+ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>, FramedProps {
13
17
  }
14
18
  export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement>, FramedProps {
15
19
  }
20
+ /** The Field around a control when it has a label; the control alone when it doesn't. */
21
+ export declare function framed({ label, hint, description, error }: Omit<FramedProps, 'mono' | 'size'>, { id, required, disabled, }: {
22
+ id?: string | undefined;
23
+ required?: boolean | undefined;
24
+ disabled?: boolean | undefined;
25
+ }, control: (props: Partial<FieldControlProps>) => ReactNode): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
16
26
  /** A single-line text field. `type="password"` adds a button that shows what was typed. */
17
27
  export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
18
28
  /** A multi-line text field that grows by hand. */
@@ -1,6 +1,6 @@
1
1
  import { cx } from "../cx.js";
2
- import { fieldBox } from "./fieldBox.js";
3
2
  import { Field, RevealToggle } from "./Field.js";
3
+ import { fieldBox } from "./fieldBox.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { forwardRef, useState } from "react";
6
6
  //#region src/components/Input.tsx
@@ -19,7 +19,7 @@ function framed({ label, hint, description, error }, { id, required, disabled },
19
19
  });
20
20
  }
21
21
  /** A single-line text field. `type="password"` adds a button that shows what was typed. */
22
- var Input = forwardRef(({ label, hint, description, error, className, mono, type, ...props }, ref) => {
22
+ var Input = forwardRef(({ label, hint, description, error, className, mono, size, type, ...props }, ref) => {
23
23
  const [revealed, setRevealed] = useState(false);
24
24
  const secret = type === "password";
25
25
  return framed({
@@ -35,7 +35,7 @@ var Input = forwardRef(({ label, hint, description, error, className, mono, type
35
35
  "aria-invalid": control["aria-invalid"] ?? props["aria-invalid"],
36
36
  "aria-describedby": [props["aria-describedby"], control["aria-describedby"]].filter(Boolean).join(" ") || void 0,
37
37
  type: secret && revealed ? "text" : type,
38
- className: cx(fieldBox(), mono && "font-mono", secret && "pr-9", className)
38
+ className: cx(fieldBox({ size }), mono && "font-mono", secret && "pr-9", className)
39
39
  });
40
40
  return secret ? /* @__PURE__ */ jsxs("div", {
41
41
  className: "relative w-full",
@@ -49,7 +49,7 @@ var Input = forwardRef(({ label, hint, description, error, className, mono, type
49
49
  });
50
50
  Input.displayName = "Input";
51
51
  /** A multi-line text field that grows by hand. */
52
- var Textarea = forwardRef(({ label, hint, description, error, className, mono, ...props }, ref) => framed({
52
+ var Textarea = forwardRef(({ label, hint, description, error, className, mono, size, ...props }, ref) => framed({
53
53
  label,
54
54
  hint,
55
55
  description,
@@ -60,8 +60,11 @@ var Textarea = forwardRef(({ label, hint, description, error, className, mono, .
60
60
  ...control,
61
61
  "aria-invalid": control["aria-invalid"] ?? props["aria-invalid"],
62
62
  "aria-describedby": [props["aria-describedby"], control["aria-describedby"]].filter(Boolean).join(" ") || void 0,
63
- className: cx(fieldBox({ multiline: true }), "min-h-24 resize-y", mono && "font-mono", className)
63
+ className: cx(fieldBox({
64
+ size,
65
+ multiline: true
66
+ }), size === "sm" ? "min-h-16" : "min-h-24", "resize-y", mono && "font-mono", className)
64
67
  })));
65
68
  Textarea.displayName = "Textarea";
66
69
  //#endregion
67
- export { Input, Textarea };
70
+ export { Input, Textarea, framed };
@@ -38,7 +38,7 @@ function MenuEntries({ entries, search, query = "" }) {
38
38
  children: /* @__PURE__ */ jsx(Menu.Input, {
39
39
  "aria-label": search.placeholder,
40
40
  placeholder: search.placeholder,
41
- className: "w-full rounded-md border border-(--theme-border) bg-(--theme-muted-panel-bg) px-2 py-1 text-[13px] text-(--theme-app) outline-none placeholder:text-(--theme-muted-text-color) focus:border-(--theme-accent)"
41
+ className: "w-full rounded-md border border-(--theme-border) bg-(--theme-muted-panel-bg) px-2 py-1 text-[13px] text-(--theme-app) outline-none placeholder:text-(--theme-placeholder) focus:border-(--theme-accent)"
42
42
  })
43
43
  }),
44
44
  /* @__PURE__ */ jsx(Menu.List, { children: /* @__PURE__ */ jsx(EntryRows, { entries: shown }) }),
@@ -15,6 +15,7 @@ export interface NumberInputProps extends Omit<NumberField.Root.Props, 'classNam
15
15
  'aria-invalid'?: boolean | undefined;
16
16
  /** Marks the field required for assistive technology without the native check `required` adds. */
17
17
  'aria-required'?: boolean | undefined;
18
+ /** `sm` for dense places such as toolbars and table cells. */
18
19
  size?: FieldBoxSize | undefined;
19
20
  /** Applied to the box around the input and its steppers, such as a width. */
20
21
  className?: string | undefined;
@@ -0,0 +1,23 @@
1
+ import { OTPField } from '@base-ui/react/otp-field';
2
+ import { type FieldBoxSize } from './fieldBox.js';
3
+ export interface OneTimeCodeInputProps extends Omit<OTPField.Root.Props, 'className' | 'render' | 'children' | 'length' | 'style'> {
4
+ /** How many characters the code has. */
5
+ length?: number | undefined;
6
+ /** `sm` for dense places such as toolbars and table cells. */
7
+ size?: FieldBoxSize | undefined;
8
+ 'aria-invalid'?: boolean | undefined;
9
+ 'aria-required'?: boolean | undefined;
10
+ /** On the row of boxes: its margin or gap. */
11
+ className?: string | undefined;
12
+ }
13
+ /**
14
+ * A one-time code, such as the six digits of a sign-in check, typed one character to a box. It
15
+ * fills from the browser's or phone's code autofill (`autocomplete="one-time-code"`) and from a
16
+ * paste of the whole code, moves on as each character is typed and back on Backspace, and opens a
17
+ * number pad for a numeric code. The value is the code as one string; `onValueComplete` fires once
18
+ * every box is filled, and `autoSubmit` submits the form then.
19
+ *
20
+ * Name it with a Field or a `<label>` for its `id`, which is the first box's, or with
21
+ * `aria-label`.
22
+ */
23
+ export declare function OneTimeCodeInput({ length, size, className, 'aria-label': ariaLabel, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, ...rootProps }: OneTimeCodeInputProps): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { cx } from "../cx.js";
2
+ import { useStrings } from "./Provider.js";
3
+ import { fieldBox } from "./fieldBox.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useId } from "react";
6
+ import { OTPField } from "@base-ui/react/otp-field";
7
+ //#region src/components/OneTimeCodeInput.tsx
8
+ /**
9
+ * A one-time code, such as the six digits of a sign-in check, typed one character to a box. It
10
+ * fills from the browser's or phone's code autofill (`autocomplete="one-time-code"`) and from a
11
+ * paste of the whole code, moves on as each character is typed and back on Backspace, and opens a
12
+ * number pad for a numeric code. The value is the code as one string; `onValueComplete` fires once
13
+ * every box is filled, and `autoSubmit` submits the form then.
14
+ *
15
+ * Name it with a Field or a `<label>` for its `id`, which is the first box's, or with
16
+ * `aria-label`.
17
+ */
18
+ function OneTimeCodeInput({ length = 6, size = "md", className, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, ...rootProps }) {
19
+ const t = useStrings().oneTimeCode;
20
+ const labelId = useId();
21
+ return /* @__PURE__ */ jsxs(OTPField.Root, {
22
+ length,
23
+ "aria-label": ariaLabel,
24
+ ...rootProps,
25
+ className: cx("flex items-center", size === "sm" ? "gap-1" : "gap-2", className),
26
+ children: [ariaLabel && /* @__PURE__ */ jsx("span", {
27
+ id: labelId,
28
+ hidden: true,
29
+ children: ariaLabel
30
+ }), Array.from({ length }, (_, index) => /* @__PURE__ */ jsx(OTPField.Input, {
31
+ ...index > 0 ? { "aria-label": t.character(index + 1, length) } : ariaLabel ? { "aria-labelledby": labelId } : {},
32
+ ...ariaInvalid && { "aria-invalid": true },
33
+ ...ariaRequired && { "aria-required": true },
34
+ className: cx(fieldBox({ size }), "px-0 text-center font-mono tabular-nums", size === "sm" ? "w-7" : "w-9 text-base")
35
+ }, index))]
36
+ });
37
+ }
38
+ //#endregion
39
+ export { OneTimeCodeInput };