@parallelworks/ui 0.45.0 → 0.47.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 (85) hide show
  1. package/README.md +16 -5
  2. package/dist/ai/agent/permissions.d.ts +1 -1
  3. package/dist/ai/agent/permissions.js +1 -1
  4. package/dist/ai/ai.css +4 -4
  5. package/dist/ai/components/ChatInput.js +1 -1
  6. package/dist/ai/components/ChatStage.d.ts +1 -1
  7. package/dist/ai/components/ChatStage.js +1 -1
  8. package/dist/ai/core/pasteLanguage.d.ts +1 -1
  9. package/dist/ai/core/pasteLanguage.js +1 -1
  10. package/dist/brandIcons.js +1 -1
  11. package/dist/components/ColorInput.d.ts +36 -0
  12. package/dist/components/ColorInput.js +82 -0
  13. package/dist/components/Combobox.d.ts +5 -2
  14. package/dist/components/Combobox.js +12 -11
  15. package/dist/components/CreateModal.js +2 -2
  16. package/dist/components/DatePicker.d.ts +5 -1
  17. package/dist/components/DatePicker.js +7 -7
  18. package/dist/components/Field.d.ts +19 -5
  19. package/dist/components/Field.js +21 -4
  20. package/dist/components/Input.d.ts +11 -1
  21. package/dist/components/Input.js +9 -6
  22. package/dist/components/Loader.d.ts +1 -1
  23. package/dist/components/MenuEntries.js +1 -1
  24. package/dist/components/NumberInput.d.ts +1 -0
  25. package/dist/components/OneTimeCodeInput.d.ts +23 -0
  26. package/dist/components/OneTimeCodeInput.js +39 -0
  27. package/dist/components/Provider.d.ts +15 -6
  28. package/dist/components/Provider.js +17 -5
  29. package/dist/components/SearchInput.d.ts +1 -0
  30. package/dist/components/Section.d.ts +7 -2
  31. package/dist/components/Section.js +58 -39
  32. package/dist/components/SegmentedControl.d.ts +4 -2
  33. package/dist/components/SegmentedControl.js +4 -2
  34. package/dist/components/Select.d.ts +2 -1
  35. package/dist/components/Select.js +1 -1
  36. package/dist/components/StatusBadge.d.ts +2 -2
  37. package/dist/components/StatusBadge.js +1 -1
  38. package/dist/components/Tabs.js +1 -0
  39. package/dist/components/ThemeEditor.js +15 -52
  40. package/dist/components/Toast.js +1 -1
  41. package/dist/components/ToggleGroup.d.ts +42 -0
  42. package/dist/components/ToggleGroup.js +69 -0
  43. package/dist/components/comboboxItems.d.ts +1 -1
  44. package/dist/components/fieldBox.js +1 -1
  45. package/dist/components/tone.d.ts +1 -1
  46. package/dist/editing.d.ts +1 -1
  47. package/dist/file-explorer/FileExplorerContext.js +11 -11
  48. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +4 -4
  49. package/dist/file-explorer/FilePreview/useObjectText.js +4 -4
  50. package/dist/file-explorer/FilePreview/usePresignedUrl.js +4 -4
  51. package/dist/file-explorer/lib/types.d.ts +2 -2
  52. package/dist/file-explorer/toasts.js +9 -9
  53. package/dist/form/Fields/BooleanField.js +1 -1
  54. package/dist/form/Fields/ColorField.js +6 -10
  55. package/dist/form/Fields/DropdownField.js +1 -1
  56. package/dist/form/Fields/DurationField.js +1 -1
  57. package/dist/form/Fields/MultiDropdownField.js +1 -1
  58. package/dist/form/Fields/StringField.js +1 -1
  59. package/dist/form/Fields/TextAreaField.js +1 -1
  60. package/dist/form/Form.js +1 -1
  61. package/dist/form/utils/fieldItems.d.ts +1 -1
  62. package/dist/graph/GraphEditorDialogs.js +1 -1
  63. package/dist/graph/InputDialog.js +2 -2
  64. package/dist/graph/SuggestionInput.d.ts +1 -1
  65. package/dist/graph/SuggestionInput.js +1 -1
  66. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  67. package/dist/graph/editorFields.js +2 -2
  68. package/dist/graph/editorPrimitives.d.ts +2 -2
  69. package/dist/graph/editorPrimitives.js +1 -1
  70. package/dist/graph/editorStrings.d.ts +2 -2
  71. package/dist/graph/editorStrings.js +4 -4
  72. package/dist/graph/inputRefs.js +2 -2
  73. package/dist/graph/stepWith.js +1 -1
  74. package/dist/icons.js +1 -1
  75. package/dist/index.d.ts +5 -1
  76. package/dist/index.js +14 -10
  77. package/dist/list/ListViewControls.js +1 -1
  78. package/dist/list/listSearch.js +1 -1
  79. package/dist/logviewer/segments.d.ts +1 -1
  80. package/dist/logviewer/segments.js +1 -1
  81. package/dist/styles/theme.css +1 -1
  82. package/dist/styles.css +1 -1
  83. package/dist/theme/derive.d.ts +3 -0
  84. package/dist/theme/derive.js +11 -7
  85. 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
@@ -300,8 +311,8 @@ root:
300
311
 
301
312
  | Variable | Meaning |
302
313
  | ------------------- | ----------------------------------------------------------------------- |
303
- | `--app-inset-top` | The height of a fixed top bar. A drawer starts below it. |
304
- | `--app-inset-start` | The width of a fixed sidebar on the start side. A drawer at its widest leaves that strip, and never less than 2.5rem. |
314
+ | `--app-inset-top` | The height of a fixed top bar. Drawers and toasts start below it. |
315
+ | `--app-inset-start` | The width of a fixed sidebar on the start side. Toasts center over the page beside it, and a drawer at its widest leaves that strip, and never less than 2.5rem. Update it when the sidebar collapses. |
305
316
 
306
317
  ```ts
307
318
  document.documentElement.style.setProperty('--app-inset-top', '56px')
@@ -340,7 +351,7 @@ name is a meaning, such as `RetryIcon` or `WarningIcon`, drawn from
340
351
  stroke. Don't import `lucide-react` directly: a name here keeps one glyph per
341
352
  meaning across the apps.
342
353
 
343
- - **Size and colour:** an icon is `1em` square in `currentColor`, so it follows
354
+ - **Size and color:** an icon is `1em` square in `currentColor`, so it follows
344
355
  the text around it. A class such as `size-4` or `h-3 w-3` sets another size,
345
356
  and `size` takes pixels.
346
357
  - **Accessibility:** an icon is decoration, hidden from assistive technology,
@@ -362,7 +373,7 @@ meaning across the apps.
362
373
 
363
374
  Logos of other companies' products, such as `GitHubIcon` or `UbuntuIcon`, are
364
375
  in `@parallelworks/ui/brand-icons`. They take the same props, are filled in the
365
- text's colour, and are only for naming the product. They come from Simple Icons
376
+ text's color, and are only for naming the product. They come from Simple Icons
366
377
  (CC0) and Bootstrap Icons (MIT), and the file carries their notices.
367
378
 
368
379
  The `UI/Icons` story shows every icon and logo by name.
@@ -6,7 +6,7 @@ export declare const DEFAULT_PERMISSION_MODE: PermissionMode;
6
6
  export declare function isNamedPermissionMode(mode: string | undefined): mode is NamedPermissionMode;
7
7
  /** The daemon clamps a remote request to its owner's remoteMaxPermissionMode
8
8
  * and refuses a mode it does not know, so a menu offers only what that
9
- * machine will honour. */
9
+ * machine will honor. */
10
10
  export declare function permissionModesUpTo(ceiling: string | undefined, accepted?: readonly string[] | undefined): PermissionMode[];
11
11
  export declare function clampPermissionMode(mode: PermissionMode, ceiling: string | undefined, accepted?: readonly string[] | undefined): PermissionMode;
12
12
  export {};
@@ -26,7 +26,7 @@ var LEGACY_PERMISSION_MODES = [
26
26
  ];
27
27
  /** The daemon clamps a remote request to its owner's remoteMaxPermissionMode
28
28
  * and refuses a mode it does not know, so a menu offers only what that
29
- * machine will honour. */
29
+ * machine will honor. */
30
30
  function permissionModesUpTo(ceiling, accepted = void 0) {
31
31
  const max = isNamedPermissionMode(ceiling) ? PERMISSION_RANK[ceiling] : PERMISSION_RANK[DEFAULT_PERMISSION_MODE];
32
32
  const known = accepted?.length ? accepted : LEGACY_PERMISSION_MODES;
package/dist/ai/ai.css CHANGED
@@ -55,7 +55,7 @@
55
55
  padding: 0.125em 0.375em;
56
56
  background-color: color-mix(in oklab, var(--theme-app, #000) 8%, transparent);
57
57
  border-radius: 0.25rem;
58
- /* Identifiers carry a colour of their own so they read as code at a glance
58
+ /* Identifiers carry a color of their own so they read as code at a glance
59
59
  * without the weight the plugin gives them: the theme's link blue, which
60
60
  * every palette already keeps legible on its surfaces. */
61
61
  color: var(--theme-link, #2f6fa8);
@@ -248,7 +248,7 @@
248
248
  explicitly to ensure correct colors in all modes.
249
249
  See: https://shiki.style/guide/dual-themes#class-based-dark-mode */
250
250
 
251
- /* Light mode: the primary Shiki theme's colours (--sdm-c) on the block's own
251
+ /* Light mode: the primary Shiki theme's colors (--sdm-c) on the block's own
252
252
  surface, which stands in for the theme's background. */
253
253
  .markdown [data-streamdown="code-block-body"] pre {
254
254
  /* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
@@ -259,7 +259,7 @@
259
259
  color: var(--sdm-c, inherit);
260
260
  }
261
261
 
262
- /* Dark mode: the --shiki-dark colours, again on the block's surface. */
262
+ /* Dark mode: the --shiki-dark colors, again on the block's surface. */
263
263
  .dark .markdown [data-streamdown="code-block-body"] pre {
264
264
  /* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
265
265
  background-color: transparent !important;
@@ -369,7 +369,7 @@
369
369
 
370
370
  /* The composer is one surface lifted off the page: a hairline of the ink for
371
371
  * its edge, and a short, soft shadow that deepens while it has focus. The
372
- * shadow is black on every scheme; an ink-coloured one would glow on dark. */
372
+ * shadow is black on every scheme; an ink-colored one would glow on dark. */
373
373
  .chat-composer {
374
374
  box-shadow:
375
375
  0 0 0 1px color-mix(in oklab, var(--theme-app, #000) 12%, transparent),
@@ -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
  }),
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /** Where a new conversation starts: the heading and the composer together, a
3
- * little above centre, with what the surface offers next under the box. The
3
+ * little above center, with what the surface offers next under the box. The
4
4
  * composer keeps its place however much is under it, so surfaces that share
5
5
  * the stage put it in the same spot; a long list scrolls instead. */
6
6
  export default function ChatStage({ heading, subheading, children, }: {
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  //#region src/ai/components/ChatStage.tsx
3
3
  /** Where a new conversation starts: the heading and the composer together, a
4
- * little above centre, with what the surface offers next under the box. The
4
+ * little above center, with what the surface offers next under the box. The
5
5
  * composer keeps its place however much is under it, so surfaces that share
6
6
  * the stage put it in the same spot; a long list scrolls instead. */
7
7
  function ChatStage({ heading, subheading, children }) {
@@ -1,5 +1,5 @@
1
1
  /** The highlighter's name for the language a paste is written in, or ''
2
- * when it is not recognisably one. */
2
+ * when it is not recognizably one. */
3
3
  export declare function guessPasteLanguage(text: string): string;
4
4
  /** The paste as a fenced code block in its language. The fence outruns any
5
5
  * run of backticks inside the paste, so nothing in it can close the fence. */
@@ -17,7 +17,7 @@ function isJson(trimmed) {
17
17
  var YAML_LINE = /^\s*(-\s+)?["']?[\w./-]+["']?\s*:(\s|$)|^\s*-(\s|$)|^---\s*$/;
18
18
  var TOML_LINE = /^\s*\[{1,2}[\w."-]+\]{1,2}\s*$|^\s*[\w."-]+\s*=\s*\S/;
19
19
  /** The highlighter's name for the language a paste is written in, or ''
20
- * when it is not recognisably one. */
20
+ * when it is not recognizably one. */
21
21
  function guessPasteLanguage(text) {
22
22
  const trimmed = text.trim();
23
23
  if (!trimmed) return "";
@@ -25,7 +25,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
25
25
  *
26
26
  * Each logo is its owner's trademark; use one only to refer to its product.
27
27
  */
28
- /** A one-colour logo in the text's colour, at the text's size. */
28
+ /** A one-color logo in the text's color, at the text's size. */
29
29
  function brand(viewBox, ...paths) {
30
30
  function Icon({ title, size = "1em", color = "currentColor", strokeWidth: _strokeWidth, absoluteStrokeWidth: _absoluteStrokeWidth, nonScalingStroke: _nonScalingStroke, children, ...props }) {
31
31
  const named = Boolean(title || props["aria-label"] || props["aria-labelledby"]);
@@ -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. */