@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
@@ -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 };
@@ -5,7 +5,7 @@ export interface LoaderProps {
5
5
  * loader is named with the provider's `loading` string. */
6
6
  label?: string | undefined;
7
7
  variant?: 'spinner' | 'logo';
8
- /** Sits in a line of text or a button instead of centring in its own row. */
8
+ /** Sits in a line of text or a button instead of centering in its own row. */
9
9
  inline?: boolean;
10
10
  /** The spinner's color, such as `currentColor` inside a button. */
11
11
  color?: string | undefined;
@@ -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 };
@@ -30,6 +30,8 @@ export interface UIStrings {
30
30
  share: string;
31
31
  send: string;
32
32
  filter: string;
33
+ /** A filter's choice that turns it off, showing everything. */
34
+ all: string;
33
35
  loading: string;
34
36
  loadMore: string;
35
37
  showMore: string;
@@ -110,6 +112,16 @@ export interface UIStrings {
110
112
  previousMonth: string;
111
113
  nextMonth: string;
112
114
  };
115
+ colorInput: {
116
+ /** Names the picker beside the hex field: the field's name, or none when it has no plain one. */
117
+ pick: (name?: string) => string;
118
+ /** Under the hex field while what is typed isn't a color. */
119
+ invalid: string;
120
+ };
121
+ oneTimeCode: {
122
+ /** Names each box after the first, which the field's label names. */
123
+ character: (position: number, length: number) => string;
124
+ };
113
125
  theme: {
114
126
  /** Names the light, dark or system choice. */
115
127
  appearance: string;
@@ -131,9 +143,6 @@ export interface UIStrings {
131
143
  sidebarAccent: string;
132
144
  code: string;
133
145
  codeStyle: (style: 'balanced' | 'vivid' | 'muted' | 'mono') => string;
134
- /** Names a color input's picker, beside its hex field. */
135
- pick: (name: string) => string;
136
- invalidColor: string;
137
146
  copy: string;
138
147
  /** The thing a copy toast names. */
139
148
  copied: string;
@@ -406,7 +415,7 @@ export interface UIStrings {
406
415
  failedTitle: string;
407
416
  unknownTitle: string;
408
417
  unknownShort: string;
409
- cancelled: string;
418
+ canceled: string;
410
419
  completed: string;
411
420
  failed: string;
412
421
  totalSize: string;
@@ -417,9 +426,9 @@ export interface UIStrings {
417
426
  expandDetails: string;
418
427
  minimizeDetails: string;
419
428
  sessionNotQueued: (id: string, status: string) => string;
420
- allCancelled: (count: number) => string;
429
+ allCanceled: (count: number) => string;
421
430
  someUploaded: (count: number) => string;
422
- someCancelled: (count: number) => string;
431
+ someCanceled: (count: number) => string;
423
432
  allUploaded: string;
424
433
  failedSummary: (failed: number, total: number) => string;
425
434
  failReasonGeneric: string;
@@ -45,6 +45,7 @@ var DEFAULTS = {
45
45
  share: "Share",
46
46
  send: "Send",
47
47
  filter: "Filter",
48
+ all: "All",
48
49
  loading: "Loading…",
49
50
  loadMore: "Load more",
50
51
  showMore: "Show more",
@@ -112,6 +113,11 @@ var DEFAULTS = {
112
113
  previousMonth: "Previous month",
113
114
  nextMonth: "Next month"
114
115
  },
116
+ colorInput: {
117
+ pick: (name) => name ? `Pick ${name.toLowerCase()}` : "Pick a color",
118
+ invalid: "Enter a hex color, such as #1f6feb."
119
+ },
120
+ oneTimeCode: { character: (position, length) => `Character ${position} of ${length}` },
115
121
  theme: {
116
122
  appearance: "Appearance",
117
123
  light: "Light",
@@ -133,8 +139,6 @@ var DEFAULTS = {
133
139
  muted: "Muted",
134
140
  mono: "Monochrome"
135
141
  })[style],
136
- pick: (name) => `Pick ${name.toLowerCase()}`,
137
- invalidColor: "Enter a hex color, such as #1f6feb.",
138
142
  copy: "Copy theme",
139
143
  copied: "theme",
140
144
  import: "Import theme",
@@ -404,7 +408,7 @@ var DEFAULTS = {
404
408
  failedTitle: "Upload Failed",
405
409
  unknownTitle: "Unknown Status",
406
410
  unknownShort: "Unknown",
407
- cancelled: "Cancelled",
411
+ canceled: "Canceled",
408
412
  completed: "Completed",
409
413
  failed: "Failed",
410
414
  totalSize: "Total size:",
@@ -414,9 +418,9 @@ var DEFAULTS = {
414
418
  expandDetails: "Expand details",
415
419
  minimizeDetails: "Minimize details",
416
420
  sessionNotQueued: (id, status) => `Upload session with ID ${id} is not queued, current status is ${status}`,
417
- allCancelled: (count) => `All ${count} file(s) were cancelled by user.`,
421
+ allCanceled: (count) => `All ${count} file(s) were canceled by user.`,
418
422
  someUploaded: (count) => `${count} file(s) uploaded successfully!`,
419
- someCancelled: (count) => `${count} file(s) cancelled by user.`,
423
+ someCanceled: (count) => `${count} file(s) canceled by user.`,
420
424
  allUploaded: "Upload file(s) successfully!",
421
425
  failedSummary: (failed, total) => `${failed}/${total} files failed to upload:`,
422
426
  failReasonGeneric: "The file(s) may be too large or the connection may have been interrupted.",
@@ -632,6 +636,14 @@ function UIProvider({ strings, navigation, data, slots, engine, children }) {
632
636
  ...DEFAULTS.strings.datePicker,
633
637
  ...strings?.datePicker
634
638
  },
639
+ colorInput: {
640
+ ...DEFAULTS.strings.colorInput,
641
+ ...strings?.colorInput
642
+ },
643
+ oneTimeCode: {
644
+ ...DEFAULTS.strings.oneTimeCode,
645
+ ...strings?.oneTimeCode
646
+ },
635
647
  theme: {
636
648
  ...DEFAULTS.strings.theme,
637
649
  ...strings?.theme
@@ -5,6 +5,7 @@ export interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElem
5
5
  onValueChange: (value: string) => void;
6
6
  /** Names the box, and is its placeholder unless `placeholder` says otherwise. */
7
7
  label?: string | undefined;
8
+ /** `sm` for dense places such as toolbars and table cells. */
8
9
  size?: FieldBoxSize | undefined;
9
10
  /** On the wrapper: its width or margin. */
10
11
  className?: string | undefined;
@@ -1,7 +1,10 @@
1
- import type { ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  export type SectionVariant = 'plain' | 'card';
3
3
  export interface SectionProps {
4
4
  title: ReactNode;
5
+ /** Under the title in muted text, saying what the section holds; it describes
6
+ * the section, and its toggle, to assistive technology. */
7
+ description?: ReactNode;
5
8
  /** Beside the title, outside the toggle, such as an info tip or a count. */
6
9
  beside?: ReactNode;
7
10
  /** At the header's far end, such as an add button or a range picker. */
@@ -18,6 +21,8 @@ export interface SectionProps {
18
21
  defaultOpen?: boolean | undefined;
19
22
  /** The heading level the title sits at in the page's outline. */
20
23
  headingLevel?: 2 | 3 | 4 | undefined;
24
+ /** On the section, so a link or a scroll can target it. */
25
+ id?: string | undefined;
21
26
  className?: string | undefined;
22
27
  children: ReactNode;
23
28
  }
@@ -25,4 +30,4 @@ export interface SectionProps {
25
30
  * that can fold away. Folding keeps the content mounted, so fields hold their
26
31
  * values and validation while closed. For a line inside content that reveals
27
32
  * more detail, use Disclosure. */
28
- export declare function Section({ title, beside, actions, variant, collapsible, open, onOpenChange, defaultOpen, headingLevel, className, children, }: SectionProps): import("react").JSX.Element;
33
+ export declare function Section({ title, description, beside, actions, variant, collapsible, open, onOpenChange, defaultOpen, headingLevel, id, className, children, }: SectionProps): import("react").JSX.Element;
@@ -2,73 +2,92 @@ import { cx } from "../cx.js";
2
2
  import { ChevronRightIcon } from "../icons.js";
3
3
  import { collapsePanelClassName } from "./Disclosure.js";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useId } from "react";
5
6
  import { Collapsible } from "@base-ui/react/collapsible";
6
7
  //#region src/components/Section.tsx
7
8
  var rootClasses = {
8
9
  plain: "flex w-full flex-col",
9
- card: "group/section panel mb-1 flex w-full flex-col"
10
+ card: "panel mb-1 flex w-full flex-col"
10
11
  };
11
12
  var headerClasses = {
12
13
  plain: "mb-2 flex w-full items-center gap-2 border-b border-(--theme-border) pb-1",
13
- card: "flex w-full items-center gap-2 px-2 py-3"
14
+ card: "flex w-full items-center gap-2 px-3 py-3"
14
15
  };
16
+ var cardBodyClassName = "px-3 pb-3";
15
17
  var titleClasses = {
16
18
  plain: "text-lg font-semibold",
17
- card: "text-sm font-bold text-(--theme-muted-text-color) group-data-open/section:text-inherit"
19
+ card: "text-sm font-bold"
18
20
  };
21
+ var foldedTitleClasses = {
22
+ plain: "",
23
+ card: "not-data-panel-open:text-(--theme-muted-text-color)"
24
+ };
25
+ var chevronIndent = "pl-6";
19
26
  /** A titled part of a page or form, its title a heading in the page's outline,
20
27
  * that can fold away. Folding keeps the content mounted, so fields hold their
21
28
  * values and validation while closed. For a line inside content that reveals
22
29
  * more detail, use Disclosure. */
23
- function Section({ title, beside, actions, variant = "plain", collapsible = true, open, onOpenChange, defaultOpen = true, headingLevel = 3, className, children }) {
30
+ function Section({ title, description, beside, actions, variant = "plain", collapsible = true, open, onOpenChange, defaultOpen = true, headingLevel = 3, id, className, children }) {
24
31
  const Heading = `h${headingLevel}`;
25
- const end = actions && /* @__PURE__ */ jsx("div", {
26
- className: "ml-auto flex items-center gap-2",
27
- children: actions
28
- });
32
+ const descriptionId = useId();
33
+ const describedBy = description ? descriptionId : void 0;
29
34
  const titleProps = { "data-section-title": "" };
30
35
  const fill = !beside && !actions;
36
+ const header = (heading) => /* @__PURE__ */ jsxs("div", {
37
+ className: headerClasses[variant],
38
+ children: [/* @__PURE__ */ jsxs("div", {
39
+ className: "flex min-w-0 flex-1 flex-col gap-0.5",
40
+ children: [/* @__PURE__ */ jsxs("div", {
41
+ className: "flex min-w-0 items-center gap-2",
42
+ children: [heading, beside]
43
+ }), description && /* @__PURE__ */ jsx("p", {
44
+ id: descriptionId,
45
+ className: cx("text-sm theme-muted-text", collapsible && chevronIndent),
46
+ children: description
47
+ })]
48
+ }), actions && /* @__PURE__ */ jsx("div", {
49
+ className: "flex shrink-0 items-center gap-2",
50
+ children: actions
51
+ })]
52
+ });
31
53
  if (!collapsible) return /* @__PURE__ */ jsxs("section", {
54
+ id,
55
+ "aria-describedby": describedBy,
32
56
  className: cx(rootClasses[variant], className),
33
- children: [/* @__PURE__ */ jsxs("div", {
34
- className: headerClasses[variant],
35
- children: [
36
- /* @__PURE__ */ jsx(Heading, {
37
- ...titleProps,
38
- className: cx("flex items-center", titleClasses[variant]),
39
- children: title
40
- }),
41
- beside,
42
- end
43
- ]
44
- }), children]
57
+ children: [header(/* @__PURE__ */ jsx(Heading, {
58
+ ...titleProps,
59
+ className: cx("flex items-center", titleClasses[variant]),
60
+ children: title
61
+ })), variant === "card" ? /* @__PURE__ */ jsx("div", {
62
+ className: cardBodyClassName,
63
+ children
64
+ }) : children]
45
65
  });
46
66
  return /* @__PURE__ */ jsxs(Collapsible.Root, {
67
+ id,
68
+ "aria-describedby": describedBy,
47
69
  open,
48
70
  onOpenChange: (next) => onOpenChange?.(next),
49
71
  defaultOpen,
50
72
  className: cx(rootClasses[variant], className),
51
- children: [/* @__PURE__ */ jsxs("div", {
52
- className: headerClasses[variant],
53
- children: [
54
- /* @__PURE__ */ jsx(Heading, {
55
- className: cx("flex min-w-0", fill && "flex-1"),
56
- children: /* @__PURE__ */ jsxs(Collapsible.Trigger, {
57
- ...titleProps,
58
- className: cx("group flex min-w-0 cursor-pointer items-center gap-1 text-left", fill && "w-full", titleClasses[variant]),
59
- children: [/* @__PURE__ */ jsx(ChevronRightIcon, {
60
- "aria-hidden": "true",
61
- className: cx("shrink-0 transition-transform group-data-panel-open:rotate-90 motion-reduce:transition-none", "px-1 text-[1.25rem]")
62
- }), title]
63
- })
64
- }),
65
- beside,
66
- end
67
- ]
68
- }), /* @__PURE__ */ jsx(Collapsible.Panel, {
73
+ children: [header(/* @__PURE__ */ jsx(Heading, {
74
+ className: cx("flex min-w-0", fill && "flex-1"),
75
+ children: /* @__PURE__ */ jsxs(Collapsible.Trigger, {
76
+ ...titleProps,
77
+ "aria-describedby": describedBy,
78
+ className: cx("group flex min-w-0 cursor-pointer items-center gap-1 text-left", fill && "w-full", titleClasses[variant], foldedTitleClasses[variant]),
79
+ children: [/* @__PURE__ */ jsx(ChevronRightIcon, {
80
+ "aria-hidden": "true",
81
+ className: cx("shrink-0 transition-transform group-data-panel-open:rotate-90 motion-reduce:transition-none", "px-1 text-[1.25rem]")
82
+ }), title]
83
+ })
84
+ })), /* @__PURE__ */ jsx(Collapsible.Panel, {
69
85
  keepMounted: true,
70
86
  className: collapsePanelClassName,
71
- children
87
+ children: variant === "card" ? /* @__PURE__ */ jsx("div", {
88
+ className: cardBodyClassName,
89
+ children
90
+ }) : children
72
91
  })]
73
92
  });
74
93
  }
@@ -10,8 +10,10 @@ export interface SegmentedOption<T extends string> {
10
10
  }
11
11
  /** A few mutually exclusive choices side by side, one always current, such as
12
12
  * a time range or what to group by. To move between views or pages, use
13
- * `Tabs` with `variant="segmented"`, which looks the same. Name it with
14
- * `label`, or leave that out inside a `Field group`, whose legend names it. */
13
+ * `Tabs` with `variant="segmented"`, which looks the same. To filter a list
14
+ * by values that may all be off, or several at once, use `ToggleGroup`. Name
15
+ * it with `label`, or leave that out inside a `Field group`, whose legend
16
+ * names it. */
15
17
  export declare function SegmentedControl<T extends string>({ label, options, value, onValueChange, fill, iconOnly, testId, }: SegmentLayout & {
16
18
  label?: string | undefined;
17
19
  options: SegmentedOption<T>[];
@@ -6,8 +6,10 @@ import { ToggleGroup } from "@base-ui/react/toggle-group";
6
6
  //#region src/components/SegmentedControl.tsx
7
7
  /** A few mutually exclusive choices side by side, one always current, such as
8
8
  * a time range or what to group by. To move between views or pages, use
9
- * `Tabs` with `variant="segmented"`, which looks the same. Name it with
10
- * `label`, or leave that out inside a `Field group`, whose legend names it. */
9
+ * `Tabs` with `variant="segmented"`, which looks the same. To filter a list
10
+ * by values that may all be off, or several at once, use `ToggleGroup`. Name
11
+ * it with `label`, or leave that out inside a `Field group`, whose legend
12
+ * names it. */
11
13
  function SegmentedControl({ label, options, value, onValueChange, fill = false, iconOnly = false, testId }) {
12
14
  const layout = {
13
15
  fill,
@@ -1,5 +1,6 @@
1
1
  import { Select as BaseSelect } from '@base-ui/react/select';
2
2
  import type { ReactElement, ReactNode } from 'react';
3
+ import { type FieldBoxSize } from './fieldBox.js';
3
4
  export interface SelectItem<Value> {
4
5
  value: Value;
5
6
  label: string;
@@ -24,7 +25,7 @@ export type SelectProps<Value, Multiple extends boolean | undefined = false> = O
24
25
  /** A hint on the trigger button. */
25
26
  triggerTooltip?: ReactNode;
26
27
  /** `sm` for dense places such as toolbars and table cells. */
27
- size?: 'sm' | 'md' | undefined;
28
+ size?: FieldBoxSize | undefined;
28
29
  /** The item a person is on as they move through the open list, then undefined once it
29
30
  * closes, such as to preview a theme before it is chosen. */
30
31
  onItemHighlighted?: ((value: Value | undefined) => void) | undefined;
@@ -36,7 +36,7 @@ function Select$1({ items, placeholder, className, "aria-label": ariaLabel, "ari
36
36
  className: cx(fieldBox({ size }), triggerClassName, className),
37
37
  children: [/* @__PURE__ */ jsx(Select.Value, {
38
38
  placeholder,
39
- className: "truncate data-placeholder:text-(--theme-muted-text-color)",
39
+ className: "truncate data-placeholder:text-(--theme-placeholder)",
40
40
  children: rootProps.multiple ? (values) => values.length > 0 ? values.map(labelOf).join(", ") : placeholder : void 0
41
41
  }), /* @__PURE__ */ jsx(Select.Icon, {
42
42
  className: "shrink-0 text-(--theme-muted-text-color)",
@@ -9,14 +9,14 @@ interface StatusBadgeProps {
9
9
  variant?: BadgeVariant;
10
10
  /** Show animated pulse indicator */
11
11
  pulse?: boolean;
12
- /** Show a still indicator in the variant's colour */
12
+ /** Show a still indicator in the variant's color */
13
13
  dot?: boolean;
14
14
  /** Size */
15
15
  size?: 'sm' | 'md';
16
16
  /** Additional class */
17
17
  className?: string;
18
18
  }
19
- /** A line of text in a variant's colour, for a status that stands beside a
19
+ /** A line of text in a variant's color, for a status that stands beside a
20
20
  * dot rather than inside a badge. */
21
21
  export declare function statusTextClass(variant: BadgeVariant): string;
22
22
  export declare function StatusBadge({ children, variant, pulse, dot, size, className, }: StatusBadgeProps): import("react").JSX.Element;
@@ -2,7 +2,7 @@ import { cx } from "../cx.js";
2
2
  import { toneFill, toneSurface, toneText } from "./tone.js";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/components/StatusBadge.tsx
5
- /** A line of text in a variant's colour, for a status that stands beside a
5
+ /** A line of text in a variant's color, for a status that stands beside a
6
6
  * dot rather than inside a badge. */
7
7
  function statusTextClass(variant) {
8
8
  return toneText[variant];
@@ -52,6 +52,7 @@ function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "l
52
52
  },
53
53
  orientation: layout.iconOnly ? "vertical" : "horizontal",
54
54
  "data-testid": testId,
55
+ className: children ? void 0 : "contents",
55
56
  children: [showRow && /* @__PURE__ */ jsxs("div", {
56
57
  className: cx("flex items-center justify-between gap-2", !segmented && "border-b border-(--theme-border)", sticky && stickyClasses[sticky], className),
57
58
  children: [/* @__PURE__ */ jsxs(Tabs.List, {
@@ -2,18 +2,19 @@ import { cx } from "../cx.js";
2
2
  import { useStrings } from "./Provider.js";
3
3
  import { useCopy } from "./useCopy.js";
4
4
  import { Button } from "./Button.js";
5
+ import { parseColor, toHex } from "../theme/color.js";
6
+ import { CODE_STYLES, DEFAULT_THEME, THEME_PRESETS, modeOf, parseTheme, schemeFor, serializeTheme } from "../theme/model.js";
7
+ import { deriveTheme, generateTheme } from "../theme/derive.js";
8
+ import { prefersWideGamut } from "../theme/index.js";
9
+ import { Field } from "./Field.js";
5
10
  import { fieldBox } from "./fieldBox.js";
11
+ import { Textarea } from "./Input.js";
12
+ import { ColorInput } from "./ColorInput.js";
6
13
  import { Disclosure } from "./Disclosure.js";
7
14
  import { Switch } from "./Switch.js";
8
- import { Field } from "./Field.js";
9
- import { Textarea } from "./Input.js";
10
15
  import { Select } from "./Select.js";
11
16
  import { SegmentedControl } from "./SegmentedControl.js";
12
17
  import { Slider } from "./Slider.js";
13
- import { parseColor, toHex } from "../theme/color.js";
14
- import { CODE_STYLES, DEFAULT_THEME, THEME_PRESETS, modeOf, parseTheme, schemeFor, serializeTheme } from "../theme/model.js";
15
- import { deriveTheme, generateTheme } from "../theme/derive.js";
16
- import { prefersWideGamut } from "../theme/index.js";
17
18
  import { jsx, jsxs } from "react/jsx-runtime";
18
19
  import { useId, useMemo, useState } from "react";
19
20
  //#region src/components/ThemeEditor.tsx
@@ -101,15 +102,15 @@ function ThemeEditor({ value, onChange, mode, onModeChange, preference, onPrefer
101
102
  onValueChange: setEditing
102
103
  })
103
104
  }),
104
- /* @__PURE__ */ jsx(ColorField, {
105
+ /* @__PURE__ */ jsx(ColorInput, {
105
106
  label: t.base,
106
107
  value: seed.base,
107
- onChange: (base) => setSeed({ base })
108
+ onValueChange: (base) => setSeed({ base })
108
109
  }),
109
- /* @__PURE__ */ jsx(ColorField, {
110
+ /* @__PURE__ */ jsx(ColorInput, {
110
111
  label: t.accent,
111
112
  value: seed.accent,
112
- onChange: (accent) => setSeed({ accent })
113
+ onValueChange: (accent) => setSeed({ accent })
113
114
  }),
114
115
  /* @__PURE__ */ jsx(Field, {
115
116
  label: t.contrast,
@@ -141,20 +142,20 @@ function ThemeEditor({ value, onChange, mode, onModeChange, preference, onPrefer
141
142
  }),
142
143
  scheme.sidebar && /* @__PURE__ */ jsxs("div", {
143
144
  className: "flex flex-col gap-5 border-l-2 border-(--theme-border) pl-4",
144
- children: [/* @__PURE__ */ jsx(ColorField, {
145
+ children: [/* @__PURE__ */ jsx(ColorInput, {
145
146
  label: t.sidebarBase,
146
147
  value: scheme.sidebar.base ?? derived["--theme-sidebar-bg"],
147
- onChange: (base) => update({
148
+ onValueChange: (base) => update({
148
149
  ...scheme,
149
150
  sidebar: {
150
151
  ...scheme.sidebar,
151
152
  base
152
153
  }
153
154
  })
154
- }), /* @__PURE__ */ jsx(ColorField, {
155
+ }), /* @__PURE__ */ jsx(ColorInput, {
155
156
  label: t.sidebarAccent,
156
157
  value: scheme.sidebar.accent ?? seed.accent,
157
- onChange: (accent) => update({
158
+ onValueChange: (accent) => update({
158
159
  ...scheme,
159
160
  sidebar: {
160
161
  ...scheme.sidebar,
@@ -214,44 +215,6 @@ function Swatch({ colors }) {
214
215
  }, i))
215
216
  });
216
217
  }
217
- /** A color as a picker beside a hex field. A half-typed hex stays in the field, marked, until it parses. */
218
- function ColorField({ label, value, onChange }) {
219
- const { theme: t } = useStrings();
220
- const [draft, setDraft] = useState(null);
221
- const parsed = parseColor(value);
222
- const hex = parsed ? toHex(parsed) : "#000000";
223
- const invalid = draft !== null && parseColor(draft) === null;
224
- return /* @__PURE__ */ jsx(Field, {
225
- label,
226
- error: invalid ? t.invalidColor : void 0,
227
- children: (control) => /* @__PURE__ */ jsxs("div", {
228
- className: "flex items-center gap-2",
229
- children: [/* @__PURE__ */ jsx("input", {
230
- type: "color",
231
- "aria-label": t.pick(label),
232
- value: hex,
233
- onChange: (e) => {
234
- setDraft(null);
235
- onChange(e.target.value);
236
- },
237
- className: "h-8 w-10 shrink-0 cursor-pointer rounded-md border border-(--theme-border) bg-transparent p-0.5"
238
- }), /* @__PURE__ */ jsx("input", {
239
- ...control,
240
- className: fieldBox({ size: "sm" }),
241
- spellCheck: false,
242
- value: draft ?? value,
243
- onChange: (e) => {
244
- const text = e.target.value;
245
- if (parseColor(text)) {
246
- setDraft(null);
247
- onChange(text.trim().toLowerCase());
248
- } else setDraft(text);
249
- },
250
- onBlur: () => setDraft(null)
251
- })]
252
- })
253
- });
254
- }
255
218
  function ImportTheme({ onImport }) {
256
219
  const { theme: t } = useStrings();
257
220
  const [open, setOpen] = useState(false);