@adea-ai/ui 0.131.1 → 0.134.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 (43) hide show
  1. package/dist/components/composites/list-row/list-row-control.d.ts +9 -0
  2. package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -1
  3. package/dist/components/composites/list-row/list-row-control.js +5 -1
  4. package/dist/components/composites/list-row/list-row-control.js.map +1 -1
  5. package/dist/components/composites/settings/settings.d.ts +8 -0
  6. package/dist/components/composites/settings/settings.d.ts.map +1 -1
  7. package/dist/components/composites/settings/settings.js +2 -1
  8. package/dist/components/composites/settings/settings.js.map +1 -1
  9. package/dist/components/ui/empty/empty-state.d.ts +21 -2
  10. package/dist/components/ui/empty/empty-state.d.ts.map +1 -1
  11. package/dist/components/ui/empty/empty-state.js +62 -18
  12. package/dist/components/ui/empty/empty-state.js.map +1 -1
  13. package/dist/components/ui/field/field.d.ts +5 -1
  14. package/dist/components/ui/field/field.d.ts.map +1 -1
  15. package/dist/components/ui/field/field.js +5 -1
  16. package/dist/components/ui/field/field.js.map +1 -1
  17. package/dist/components/ui/field/number-field.d.ts +18 -2
  18. package/dist/components/ui/field/number-field.d.ts.map +1 -1
  19. package/dist/components/ui/field/number-field.js +10 -4
  20. package/dist/components/ui/field/number-field.js.map +1 -1
  21. package/dist/components/ui/input-group/secret-input-group.d.ts +20 -1
  22. package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -1
  23. package/dist/components/ui/input-group/secret-input-group.js +15 -4
  24. package/dist/components/ui/input-group/secret-input-group.js.map +1 -1
  25. package/dist/components/ui/native-select/native-select.d.ts +8 -0
  26. package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
  27. package/dist/components/ui/native-select/native-select.js +5 -3
  28. package/dist/components/ui/native-select/native-select.js.map +1 -1
  29. package/dist/r/src/components/composites/list-row/list-row-control.tsx +12 -1
  30. package/dist/r/src/components/composites/settings/settings.tsx +11 -1
  31. package/dist/r/src/components/ui/empty/empty-state.tsx +59 -21
  32. package/dist/r/src/components/ui/field/field.tsx +10 -2
  33. package/dist/r/src/components/ui/field/number-field.tsx +35 -4
  34. package/dist/r/src/components/ui/input-group/secret-input-group.tsx +28 -3
  35. package/dist/r/src/components/ui/native-select/native-select.tsx +14 -2
  36. package/package.json +1 -1
  37. package/src/components/composites/list-row/list-row-control.tsx +12 -1
  38. package/src/components/composites/settings/settings.tsx +11 -1
  39. package/src/components/ui/empty/empty-state.tsx +59 -21
  40. package/src/components/ui/field/field.tsx +10 -2
  41. package/src/components/ui/field/number-field.tsx +35 -4
  42. package/src/components/ui/input-group/secret-input-group.tsx +28 -3
  43. package/src/components/ui/native-select/native-select.tsx +14 -2
@@ -4,8 +4,27 @@ export type SecretInputGroupProps = {
4
4
  disabled: boolean;
5
5
  onChange: JSX.EventHandler<HTMLInputElement, Event>;
6
6
  onClear?: () => void;
7
+ /**
8
+ * What the clear action does, for this field: its accessible name and its
9
+ * tooltip while enabled ("Clear stored token", "Clear Google token path").
10
+ * Defaults to "Clear API key".
11
+ */
12
+ clearLabel?: string;
13
+ /**
14
+ * Why the clear action is unavailable, shown as its tooltip while the group is
15
+ * `disabled`. Defaults to the policy message, so name the real reason when a
16
+ * field is disabled for another one ("Set a token variable first").
17
+ */
18
+ disabledReason?: string;
7
19
  class?: string;
8
20
  } & Pick<ComponentProps<'input'>, 'id' | 'aria-label' | 'aria-describedby' | 'aria-invalid'>;
9
- /** A password input with an optional keyboard-accessible clear action. */
21
+ /**
22
+ * A password input with an optional keyboard-accessible clear action.
23
+ *
24
+ * The wording is the caller's: inside a `FormField` the input takes the field's
25
+ * label, outside one it needs `aria-label` (the fallback names an API key), and
26
+ * `clearLabel` and `disabledReason` name the clear action for the field it
27
+ * belongs to, so a bearer token or a token path is not announced as an API key.
28
+ */
10
29
  export declare function SecretInputGroup(props: SecretInputGroupProps): JSX.Element;
11
30
  //# sourceMappingURL=secret-input-group.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-input-group.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/input-group/secret-input-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAKhF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,GAAG,CAAC,YAAY,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAA;IACnD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,kBAAkB,GAAG,cAAc,CAAC,CAAA;AAE5F,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,eA2C5D"}
1
+ {"version":3,"file":"secret-input-group.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/input-group/secret-input-group.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAKhF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,GAAG,CAAC,YAAY,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAA;IACnD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,kBAAkB,GAAG,cAAc,CAAC,CAAA;AAE5F;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,eAiD5D"}
@@ -1,10 +1,17 @@
1
1
  import { FormFieldContext } from "../../../lib/form-field.js";
2
2
  import { ActionButton } from "../../composites/action-button/action-button.js";
3
3
  import { InputGroup, InputGroupInput } from "./input-group.js";
4
- import { createComponent, mergeProps } from "solid-js/web";
4
+ import { createComponent, memo, mergeProps } from "solid-js/web";
5
5
  import { splitProps, useContext } from "solid-js";
6
6
  //#region src/components/ui/input-group/secret-input-group.tsx
7
- /** A password input with an optional keyboard-accessible clear action. */
7
+ /**
8
+ * A password input with an optional keyboard-accessible clear action.
9
+ *
10
+ * The wording is the caller's: inside a `FormField` the input takes the field's
11
+ * label, outside one it needs `aria-label` (the fallback names an API key), and
12
+ * `clearLabel` and `disabledReason` name the clear action for the field it
13
+ * belongs to, so a bearer token or a token path is not announced as an API key.
14
+ */
8
15
  function SecretInputGroup(props) {
9
16
  const field = useContext(FormFieldContext);
10
17
  const [local, rest] = splitProps(props, [
@@ -12,6 +19,8 @@ function SecretInputGroup(props) {
12
19
  "disabled",
13
20
  "onChange",
14
21
  "onClear",
22
+ "clearLabel",
23
+ "disabledReason",
15
24
  "aria-label",
16
25
  "class"
17
26
  ]);
@@ -51,10 +60,12 @@ function SecretInputGroup(props) {
51
60
  return local.disabled;
52
61
  },
53
62
  get tooltip() {
54
- return local.disabled ? "This API key is managed by policy" : "Clear API key";
63
+ return memo(() => !!local.disabled)() ? local.disabledReason ?? "This API key is managed by policy" : local.clearLabel ?? "Clear API key";
55
64
  },
56
65
  tooltipSide: "top",
57
- "aria-label": "Clear API key",
66
+ get ["aria-label"]() {
67
+ return local.clearLabel ?? "Clear API key";
68
+ },
58
69
  onClick: () => local.onClear?.(),
59
70
  children: "Clear"
60
71
  })];
@@ -1 +1 @@
1
- {"version":3,"file":"secret-input-group.js","names":["splitProps","useContext","ComponentProps","JSX","ActionButton","FormFieldContext","InputGroup","InputGroupInput","SecretInputGroupProps","value","disabled","onChange","EventHandler","HTMLInputElement","Event","onClear","class","Pick","SecretInputGroup","props","field","local","rest","input","_$createComponent","_$mergeProps","aria-label","undefined","type","autocomplete","onInput","children","variant","size","tooltip","tooltipSide","onClick","_$createComponent","_$mergeProps"],"sources":["../../../../src/components/ui/input-group/secret-input-group.tsx"],"sourcesContent":["import { splitProps, useContext, type ComponentProps, type JSX } from 'solid-js'\nimport { ActionButton } from '../../composites/action-button'\nimport { FormFieldContext } from '#lib/form-field'\nimport { InputGroup, InputGroupInput } from './input-group'\n\nexport type SecretInputGroupProps = {\n value: string\n disabled: boolean\n onChange: JSX.EventHandler<HTMLInputElement, Event>\n onClear?: () => void\n class?: string\n} & Pick<ComponentProps<'input'>, 'id' | 'aria-label' | 'aria-describedby' | 'aria-invalid'>\n\n/** A password input with an optional keyboard-accessible clear action. */\nexport function SecretInputGroup(props: SecretInputGroupProps) {\n const field = useContext(FormFieldContext)\n const [local, rest] = splitProps(props, [\n 'value',\n 'disabled',\n 'onChange',\n 'onClear',\n 'aria-label',\n 'class',\n ])\n\n const input = (\n <InputGroupInput\n {...rest}\n aria-label={local['aria-label'] ?? (field ? undefined : 'New API key')}\n type=\"password\"\n autocomplete=\"new-password\"\n value={local.value}\n disabled={local.disabled}\n onInput={local.onChange}\n />\n )\n\n if (!local.onClear) return <InputGroup class={local.class}>{input}</InputGroup>\n\n return (\n <InputGroup class={local.class}>\n {input}\n <ActionButton\n data-slot=\"input-group-button\"\n variant=\"ghost\"\n size=\"xs\"\n class=\"-me-1 h-6 min-w-0 shrink-0 px-2\"\n disabled={local.disabled}\n tooltip={local.disabled ? 'This API key is managed by policy' : 'Clear API key'}\n tooltipSide=\"top\"\n aria-label=\"Clear API key\"\n onClick={() => local.onClear?.()}\n >\n Clear\n </ActionButton>\n </InputGroup>\n )\n}\n"],"mappings":";;;;;;;AAcA,SAAgBkB,iBAAiBC,OAA8B;CAC7D,MAAMC,QAAQnB,WAAWI,gBAAgB;CACzC,MAAM,CAACgB,OAAOC,QAAQtB,WAAWmB,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;CAAO,CACR;CAED,MAAMI,QAAKC,gBACRjB,iBAAekB,WACVH,MAAI;EAAA,KAAA,gBAAA;GAAA,OACID,MAAM,kBAAkBD,QAAQO,KAAAA,IAAY;EAAc;EACtEC,MAAI;EACJC,cAAY;EAAA,IACZpB,QAAK;GAAA,OAAEY,MAAMZ;EAAK;EAAA,IAClBC,WAAQ;GAAA,OAAEW,MAAMX;EAAQ;EAAA,IACxBoB,UAAO;GAAA,OAAET,MAAMV;EAAQ;CAAA,CAAA,CAAA;CAI3B,IAAI,CAACU,MAAMN,SAAS,OAAAS,gBAAQlB,YAAU;EAAA,KAAA,WAAA;GAAA,OAAQe,MAAML;EAAK;EAAAe,UAAGR;CAAK,CAAA;CAEjE,OAAAC,gBACGlB,YAAU;EAAA,KAAA,WAAA;GAAA,OAAQe,MAAML;EAAK;EAAA,IAAAe,WAAA;GAAA,OAAA,CAC3BR,OAAKC,gBACLpB,cAAY;IAAA,aAAA;IAEX4B,SAAO;IACPC,MAAI;IAAA,SAAA;IAAA,IAEJvB,WAAQ;KAAA,OAAEW,MAAMX;IAAQ;IAAA,IACxBwB,UAAO;KAAA,OAAEb,MAAMX,WAAW,sCAAsC;IAAe;IAC/EyB,aAAW;IAAA,cAAA;IAEXC,eAAef,MAAMN,UAAU;IAACgB,UAAA;GAAA,CAAA,CAAA;EAAA;CAAA,CAAA;AAMxC"}
1
+ {"version":3,"file":"secret-input-group.js","names":["splitProps","useContext","ComponentProps","JSX","ActionButton","FormFieldContext","InputGroup","InputGroupInput","SecretInputGroupProps","value","disabled","onChange","EventHandler","HTMLInputElement","Event","onClear","clearLabel","disabledReason","class","Pick","SecretInputGroup","props","field","local","rest","input","_$createComponent","_$mergeProps","aria-label","undefined","type","autocomplete","onInput","children","variant","size","tooltip","_$memo","tooltipSide","onClick","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/input-group/secret-input-group.tsx"],"sourcesContent":["import { splitProps, useContext, type ComponentProps, type JSX } from 'solid-js'\nimport { ActionButton } from '../../composites/action-button'\nimport { FormFieldContext } from '#lib/form-field'\nimport { InputGroup, InputGroupInput } from './input-group'\n\nexport type SecretInputGroupProps = {\n value: string\n disabled: boolean\n onChange: JSX.EventHandler<HTMLInputElement, Event>\n onClear?: () => void\n /**\n * What the clear action does, for this field: its accessible name and its\n * tooltip while enabled (\"Clear stored token\", \"Clear Google token path\").\n * Defaults to \"Clear API key\".\n */\n clearLabel?: string\n /**\n * Why the clear action is unavailable, shown as its tooltip while the group is\n * `disabled`. Defaults to the policy message, so name the real reason when a\n * field is disabled for another one (\"Set a token variable first\").\n */\n disabledReason?: string\n class?: string\n} & Pick<ComponentProps<'input'>, 'id' | 'aria-label' | 'aria-describedby' | 'aria-invalid'>\n\n/**\n * A password input with an optional keyboard-accessible clear action.\n *\n * The wording is the caller's: inside a `FormField` the input takes the field's\n * label, outside one it needs `aria-label` (the fallback names an API key), and\n * `clearLabel` and `disabledReason` name the clear action for the field it\n * belongs to, so a bearer token or a token path is not announced as an API key.\n */\nexport function SecretInputGroup(props: SecretInputGroupProps) {\n const field = useContext(FormFieldContext)\n const [local, rest] = splitProps(props, [\n 'value',\n 'disabled',\n 'onChange',\n 'onClear',\n 'clearLabel',\n 'disabledReason',\n 'aria-label',\n 'class',\n ])\n\n const input = (\n <InputGroupInput\n {...rest}\n aria-label={local['aria-label'] ?? (field ? undefined : 'New API key')}\n type=\"password\"\n autocomplete=\"new-password\"\n value={local.value}\n disabled={local.disabled}\n onInput={local.onChange}\n />\n )\n\n if (!local.onClear) return <InputGroup class={local.class}>{input}</InputGroup>\n\n return (\n <InputGroup class={local.class}>\n {input}\n <ActionButton\n data-slot=\"input-group-button\"\n variant=\"ghost\"\n size=\"xs\"\n class=\"-me-1 h-6 min-w-0 shrink-0 px-2\"\n disabled={local.disabled}\n tooltip={\n local.disabled\n ? (local.disabledReason ?? 'This API key is managed by policy')\n : (local.clearLabel ?? 'Clear API key')\n }\n tooltipSide=\"top\"\n aria-label={local.clearLabel ?? 'Clear API key'}\n onClick={() => local.onClear?.()}\n >\n Clear\n </ActionButton>\n </InputGroup>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAiCA,SAAgBoB,iBAAiBC,OAA8B;CAC7D,MAAMC,QAAQrB,WAAWI,gBAAgB;CACzC,MAAM,CAACkB,OAAOC,QAAQxB,WAAWqB,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAO,CACR;CAED,MAAMI,QAAKC,gBACRnB,iBAAeoB,WACVH,MAAI;EAAA,KAAA,gBAAA;GAAA,OACID,MAAM,kBAAkBD,QAAQO,KAAAA,IAAY;EAAc;EACtEC,MAAI;EACJC,cAAY;EAAA,IACZtB,QAAK;GAAA,OAAEc,MAAMd;EAAK;EAAA,IAClBC,WAAQ;GAAA,OAAEa,MAAMb;EAAQ;EAAA,IACxBsB,UAAO;GAAA,OAAET,MAAMZ;EAAQ;CAAA,CAAA,CAAA;CAI3B,IAAI,CAACY,MAAMR,SAAS,OAAAW,gBAAQpB,YAAU;EAAA,KAAA,WAAA;GAAA,OAAQiB,MAAML;EAAK;EAAAe,UAAGR;CAAK,CAAA;CAEjE,OAAAC,gBACGpB,YAAU;EAAA,KAAA,WAAA;GAAA,OAAQiB,MAAML;EAAK;EAAA,IAAAe,WAAA;GAAA,OAAA,CAC3BR,OAAKC,gBACLtB,cAAY;IAAA,aAAA;IAEX8B,SAAO;IACPC,MAAI;IAAA,SAAA;IAAA,IAEJzB,WAAQ;KAAA,OAAEa,MAAMb;IAAQ;IAAA,IACxB0B,UAAO;KAAA,OACLC,WAAA,CAAA,CAAAd,MAAMb,QAAQ,CAAA,CAAA,IACTa,MAAMN,kBAAkB,sCACxBM,MAAMP,cAAc;IAAgB;IAE3CsB,aAAW;IAAA,KAAA,gBAAA;KAAA,OACCf,MAAMP,cAAc;IAAe;IAC/CuB,eAAehB,MAAMR,UAAU;IAACkB,UAAA;GAAA,CAAA,CAAA;EAAA;CAAA,CAAA;AAMxC"}
@@ -7,6 +7,14 @@ export type NativeSelectProps = Omit<ComponentProps<'select'>, 'size'> & {
7
7
  * control that this styled single-line select is not.
8
8
  */
9
9
  size?: 'sm' | 'md' | 'lg';
10
+ /**
11
+ * Layout classes for the element that wraps the select and its chevron: a
12
+ * width or `max-w-*`, `flex-1`, `col-span-*`, a margin. `class` reaches the
13
+ * `<select>` itself, which is the wrong box for any of those, because the
14
+ * wrapper is what a flex or grid parent lays out. The select fills the
15
+ * wrapper, so the chevron stays at the control's edge at any width.
16
+ */
17
+ wrapperClass?: string;
10
18
  /** Render the choices from data instead of raw option children. */
11
19
  options?: readonly NativeSelectOption[];
12
20
  /** Initialize an uncontrolled selection after its native options mount. */
@@ -1 +1 @@
1
- {"version":3,"file":"native-select.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/native-select/native-select.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IACvE;;;;;OAKG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACvC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAA;CACjD,CAAA;AAkCD;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,CAAA;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCAuFpD"}
1
+ {"version":3,"file":"native-select.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/native-select/native-select.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IACvE;;;;;OAKG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACvC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAA;CACjD,CAAA;AAkCD;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,CAAA;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCA2FpD"}
@@ -1,10 +1,10 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { useFormFieldControl } from "../../../lib/form-field.js";
3
- import { createComponent, effect, insert, mergeProps, spread, template, use } from "solid-js/web";
3
+ import { className, createComponent, effect, insert, mergeProps, spread, template, use } from "solid-js/web";
4
4
  import { ChevronDownIcon } from "lucide-solid";
5
5
  import { For, Show, createEffect, onMount, splitProps } from "solid-js";
6
6
  //#region src/components/ui/native-select/native-select.tsx
7
- var _tmpl$ = /*#__PURE__*/ template(`<span data-slot=native-select-wrapper class="relative inline-flex min-w-0"><select data-slot=native-select>`);
7
+ var _tmpl$ = /*#__PURE__*/ template(`<span data-slot=native-select-wrapper><select data-slot=native-select>`);
8
8
  var _tmpl$2 = /*#__PURE__*/ template(`<option>`);
9
9
  function setSelectedValue(select, value, multiple) {
10
10
  if (value === void 0) return;
@@ -24,6 +24,7 @@ function initializeDefaultSelection(select, value, multiple) {
24
24
  function NativeSelect(props) {
25
25
  const [local, rest] = splitProps(props, [
26
26
  "class",
27
+ "wrapperClass",
27
28
  "size",
28
29
  "children",
29
30
  "defaultValue",
@@ -76,7 +77,7 @@ function NativeSelect(props) {
76
77
  return field?.["aria-errormessage"] ?? local["aria-errormessage"];
77
78
  },
78
79
  get ["class"]() {
79
- return cn("min-w-0 appearance-none rounded-md border border-input bg-transparent", {
80
+ return cn("w-full min-w-0 appearance-none rounded-md border border-input bg-transparent", {
80
81
  "h-control-sm px-control-sm pr-8 text-xs": local.size === "sm",
81
82
  "h-control-md px-control-md pr-9 text-sm": local.size === "md" || local.size === void 0,
82
83
  "h-control-lg px-control-lg pr-9 text-sm": local.size === "lg"
@@ -110,6 +111,7 @@ function NativeSelect(props) {
110
111
  return cn("pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 text-muted-foreground", local.size === "sm" ? "size-3.5" : "size-4");
111
112
  }
112
113
  }), null);
114
+ effect(() => className(_el$, cn("relative inline-flex min-w-0", local.wrapperClass)));
113
115
  return _el$;
114
116
  })();
115
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native-select.js","names":["ChevronDownIcon","ComponentProps","createEffect","For","onMount","Show","splitProps","useFormFieldControl","cn","NativeSelectProps","Omit","size","options","NativeSelectOption","defaultValue","setSelectedValue","select","HTMLSelectElement","value","multiple","undefined","Array","isArray","selected","Set","map","String","option","has","initializeDefaultSelection","defaultValues","defaultSelected","Readonly","label","disabled","NativeSelect","props","local","rest","field","id","Boolean","_el$","_tmpl$","_el$2","firstChild","_$use","element","_$spread","_$mergeProps","aria-label","aria-labelledby","aria-describedby","aria-invalid","aria-errormessage","class","_$insert","_$createComponent","when","fallback","children","each","_el$3","_tmpl$2","_$effect","_$mergeProps","_$createComponent"],"sources":["../../../../src/components/ui/native-select/native-select.tsx"],"sourcesContent":["import { ChevronDownIcon } from 'lucide-solid'\nimport type { ComponentProps } from 'solid-js'\nimport { createEffect, For, onMount, Show, splitProps } from 'solid-js'\nimport { useFormFieldControl } from '#lib/form-field'\nimport { cn } from '#lib/utils'\n\nexport type NativeSelectProps = Omit<ComponentProps<'select'>, 'size'> & {\n /**\n * The control rung, matching `Input` and `SelectTrigger`: `sm` for a dense\n * toolbar or pane filter, `md` (the default) everywhere else. This replaces the\n * native `size` attribute, which turns a select into a list box — a different\n * control that this styled single-line select is not.\n */\n size?: 'sm' | 'md' | 'lg'\n /** Render the choices from data instead of raw option children. */\n options?: readonly NativeSelectOption[]\n /** Initialize an uncontrolled selection after its native options mount. */\n defaultValue?: ComponentProps<'select'>['value']\n}\n\nfunction setSelectedValue(\n select: HTMLSelectElement,\n value: ComponentProps<'select'>['value'],\n multiple: boolean\n) {\n if (value === undefined) return\n\n if (multiple && Array.isArray(value)) {\n const selected = new Set(value.map(String))\n for (const option of select.options) option.selected = selected.has(option.value)\n return\n }\n\n select.value = String(Array.isArray(value) ? (value[0] ?? '') : value)\n}\n\nfunction initializeDefaultSelection(\n select: HTMLSelectElement,\n value: ComponentProps<'select'>['value'],\n multiple: boolean\n) {\n if (value === undefined) return\n\n const defaultValues =\n multiple && Array.isArray(value)\n ? new Set(value.map(String))\n : new Set([String(Array.isArray(value) ? (value[0] ?? '') : value)])\n\n for (const option of select.options) option.defaultSelected = defaultValues.has(option.value)\n setSelectedValue(select, value, multiple)\n}\n\n/**\n * NativeSelect.\n *\n * A styled native `<select>` for compact, finite choices. Use `Select` when the\n * list needs a custom popover, typed options, or runtime filtering; this keeps\n * the browser's built-in keyboard, form, option, and label behavior.\n *\n * Translated from shadcn/ui's MIT-licensed NativeSelect wrapper to SolidJS and\n * the Adea token system; see the root NOTICE for source and attribution.\n */\nexport type NativeSelectOption = Readonly<{\n value: string\n label: string\n disabled?: boolean\n}>\n\nexport function NativeSelect(props: NativeSelectProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'size',\n 'children',\n 'defaultValue',\n 'options',\n 'id',\n 'aria-label',\n 'aria-labelledby',\n 'aria-describedby',\n 'aria-invalid',\n 'aria-errormessage',\n ])\n const field = useFormFieldControl('control', {\n id: local.id,\n 'aria-label': local['aria-label'],\n 'aria-labelledby': local['aria-labelledby'],\n 'aria-describedby': local['aria-describedby'],\n 'aria-invalid': local['aria-invalid'],\n 'aria-errormessage': local['aria-errormessage'],\n })\n let select: HTMLSelectElement | undefined\n\n // Set selection after native options exist; setting select.value before its\n // children are mounted makes browsers fall back to the first option.\n createEffect(() => {\n if (!select) return\n setSelectedValue(select, rest.value, Boolean(rest.multiple))\n })\n onMount(() => {\n if (select && rest.value === undefined) {\n initializeDefaultSelection(select, local.defaultValue, Boolean(rest.multiple))\n }\n })\n\n return (\n <span data-slot=\"native-select-wrapper\" class=\"relative inline-flex min-w-0\">\n <select\n ref={(element) => {\n select = element\n }}\n data-slot=\"native-select\"\n id={field?.id ?? local.id}\n aria-label={local['aria-label']}\n aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}\n aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}\n aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}\n aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}\n class={cn(\n 'min-w-0 appearance-none rounded-md border border-input bg-transparent',\n {\n 'h-control-sm px-control-sm pr-8 text-xs': local.size === 'sm',\n 'h-control-md px-control-md pr-9 text-sm':\n local.size === 'md' || local.size === undefined,\n 'h-control-lg px-control-lg pr-9 text-sm': local.size === 'lg',\n },\n 'transition-[color,box-shadow,border-color] ease-out outline-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',\n 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',\n local.class\n )}\n {...rest}\n >\n <Show when={local.options} fallback={local.children}>\n {(options) => (\n <For each={options()}>\n {(option) => (\n <option value={option.value} disabled={option.disabled}>\n {option.label}\n </option>\n )}\n </For>\n )}\n </Show>\n </select>\n <ChevronDownIcon\n data-slot=\"native-select-icon\"\n aria-hidden=\"true\"\n class={cn(\n 'pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 text-muted-foreground',\n local.size === 'sm' ? 'size-3.5' : 'size-4'\n )}\n />\n </span>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"native-select.js","names":["ChevronDownIcon","ComponentProps","createEffect","For","onMount","Show","splitProps","useFormFieldControl","cn","NativeSelectProps","Omit","size","wrapperClass","options","NativeSelectOption","defaultValue","setSelectedValue","select","HTMLSelectElement","value","multiple","undefined","Array","isArray","selected","Set","map","String","option","has","initializeDefaultSelection","defaultValues","defaultSelected","Readonly","label","disabled","NativeSelect","props","local","rest","field","id","Boolean","_el$","_tmpl$","_el$2","firstChild","_$use","element","_$spread","_$mergeProps","aria-label","aria-labelledby","aria-describedby","aria-invalid","aria-errormessage","class","_$insert","_$createComponent","when","fallback","children","each","_el$3","_tmpl$2","_$effect","_$className","_$mergeProps","_$createComponent","_$className"],"sources":["../../../../src/components/ui/native-select/native-select.tsx"],"sourcesContent":["import { ChevronDownIcon } from 'lucide-solid'\nimport type { ComponentProps } from 'solid-js'\nimport { createEffect, For, onMount, Show, splitProps } from 'solid-js'\nimport { useFormFieldControl } from '#lib/form-field'\nimport { cn } from '#lib/utils'\n\nexport type NativeSelectProps = Omit<ComponentProps<'select'>, 'size'> & {\n /**\n * The control rung, matching `Input` and `SelectTrigger`: `sm` for a dense\n * toolbar or pane filter, `md` (the default) everywhere else. This replaces the\n * native `size` attribute, which turns a select into a list box — a different\n * control that this styled single-line select is not.\n */\n size?: 'sm' | 'md' | 'lg'\n /**\n * Layout classes for the element that wraps the select and its chevron: a\n * width or `max-w-*`, `flex-1`, `col-span-*`, a margin. `class` reaches the\n * `<select>` itself, which is the wrong box for any of those, because the\n * wrapper is what a flex or grid parent lays out. The select fills the\n * wrapper, so the chevron stays at the control's edge at any width.\n */\n wrapperClass?: string\n /** Render the choices from data instead of raw option children. */\n options?: readonly NativeSelectOption[]\n /** Initialize an uncontrolled selection after its native options mount. */\n defaultValue?: ComponentProps<'select'>['value']\n}\n\nfunction setSelectedValue(\n select: HTMLSelectElement,\n value: ComponentProps<'select'>['value'],\n multiple: boolean\n) {\n if (value === undefined) return\n\n if (multiple && Array.isArray(value)) {\n const selected = new Set(value.map(String))\n for (const option of select.options) option.selected = selected.has(option.value)\n return\n }\n\n select.value = String(Array.isArray(value) ? (value[0] ?? '') : value)\n}\n\nfunction initializeDefaultSelection(\n select: HTMLSelectElement,\n value: ComponentProps<'select'>['value'],\n multiple: boolean\n) {\n if (value === undefined) return\n\n const defaultValues =\n multiple && Array.isArray(value)\n ? new Set(value.map(String))\n : new Set([String(Array.isArray(value) ? (value[0] ?? '') : value)])\n\n for (const option of select.options) option.defaultSelected = defaultValues.has(option.value)\n setSelectedValue(select, value, multiple)\n}\n\n/**\n * NativeSelect.\n *\n * A styled native `<select>` for compact, finite choices. Use `Select` when the\n * list needs a custom popover, typed options, or runtime filtering; this keeps\n * the browser's built-in keyboard, form, option, and label behavior.\n *\n * Translated from shadcn/ui's MIT-licensed NativeSelect wrapper to SolidJS and\n * the Adea token system; see the root NOTICE for source and attribution.\n */\nexport type NativeSelectOption = Readonly<{\n value: string\n label: string\n disabled?: boolean\n}>\n\nexport function NativeSelect(props: NativeSelectProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'wrapperClass',\n 'size',\n 'children',\n 'defaultValue',\n 'options',\n 'id',\n 'aria-label',\n 'aria-labelledby',\n 'aria-describedby',\n 'aria-invalid',\n 'aria-errormessage',\n ])\n const field = useFormFieldControl('control', {\n id: local.id,\n 'aria-label': local['aria-label'],\n 'aria-labelledby': local['aria-labelledby'],\n 'aria-describedby': local['aria-describedby'],\n 'aria-invalid': local['aria-invalid'],\n 'aria-errormessage': local['aria-errormessage'],\n })\n let select: HTMLSelectElement | undefined\n\n // Set selection after native options exist; setting select.value before its\n // children are mounted makes browsers fall back to the first option.\n createEffect(() => {\n if (!select) return\n setSelectedValue(select, rest.value, Boolean(rest.multiple))\n })\n onMount(() => {\n if (select && rest.value === undefined) {\n initializeDefaultSelection(select, local.defaultValue, Boolean(rest.multiple))\n }\n })\n\n return (\n <span\n data-slot=\"native-select-wrapper\"\n class={cn('relative inline-flex min-w-0', local.wrapperClass)}\n >\n <select\n ref={(element) => {\n select = element\n }}\n data-slot=\"native-select\"\n id={field?.id ?? local.id}\n aria-label={local['aria-label']}\n aria-labelledby={field?.['aria-labelledby'] ?? local['aria-labelledby']}\n aria-describedby={field?.['aria-describedby'] ?? local['aria-describedby']}\n aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}\n aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}\n class={cn(\n 'w-full min-w-0 appearance-none rounded-md border border-input bg-transparent',\n {\n 'h-control-sm px-control-sm pr-8 text-xs': local.size === 'sm',\n 'h-control-md px-control-md pr-9 text-sm':\n local.size === 'md' || local.size === undefined,\n 'h-control-lg px-control-lg pr-9 text-sm': local.size === 'lg',\n },\n 'transition-[color,box-shadow,border-color] ease-out outline-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive-subtle',\n 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30',\n local.class\n )}\n {...rest}\n >\n <Show when={local.options} fallback={local.children}>\n {(options) => (\n <For each={options()}>\n {(option) => (\n <option value={option.value} disabled={option.disabled}>\n {option.label}\n </option>\n )}\n </For>\n )}\n </Show>\n </select>\n <ChevronDownIcon\n data-slot=\"native-select-icon\"\n aria-hidden=\"true\"\n class={cn(\n 'pointer-events-none absolute end-2 top-1/2 -translate-y-1/2 text-muted-foreground',\n local.size === 'sm' ? 'size-3.5' : 'size-4'\n )}\n />\n </span>\n )\n}\n"],"mappings":"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"}
@@ -18,6 +18,15 @@ export type ListRowControlProps<T extends ValidComponent = 'div'> = PolymorphicP
18
18
  trailing?: JSX.Element
19
19
  /** A second line that wraps and grows the row to fit its content. */
20
20
  description?: string
21
+ /**
22
+ * Keep the description on one line and end it with an ellipsis instead of
23
+ * wrapping, so a described row stays at its minimum height however long the
24
+ * text is. Use it where rows must be a predictable height (a virtualised
25
+ * queue); leave it off where the whole description should be readable. Pair
26
+ * it with a `tooltip` when the full text matters, and with
27
+ * `stackTrailing={false}` to keep a trailing slot beside the text.
28
+ */
29
+ truncateDescription?: boolean
21
30
  /**
22
31
  * Whether a described row may drop its trailing slot onto its own line when
23
32
  * the row gets narrow (the default). Pass `false` for a short, truncating
@@ -43,6 +52,7 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
43
52
  'leading',
44
53
  'trailing',
45
54
  'description',
55
+ 'truncateDescription',
46
56
  'stackTrailing',
47
57
  'type',
48
58
  'tabIndex',
@@ -92,7 +102,8 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
92
102
  <span
93
103
  data-slot="list-row-description"
94
104
  class={cn(
95
- 'text-xs break-words',
105
+ 'text-xs',
106
+ { truncate: local.truncateDescription, 'break-words': !local.truncateDescription },
96
107
  local.selected ? 'text-foreground' : 'text-muted-foreground'
97
108
  )}
98
109
  >
@@ -86,6 +86,14 @@ export function SettingsRow(
86
86
  orientation?: 'horizontal' | 'vertical'
87
87
  /** Hide the divider this row would otherwise contribute. */
88
88
  bare?: boolean
89
+ /**
90
+ * Drop the row's own 16px padding so its label and control sit on the same
91
+ * edges as the fields around it. For a row placed loose in a
92
+ * `bodyLayout="content"` section, where nothing encloses it and the section's
93
+ * gap already spaces it from its neighbours. Leave it off inside a divided
94
+ * rows body or a `Card`, whose edge the padding is there to clear.
95
+ */
96
+ flush?: boolean
89
97
  }
90
98
  ) {
91
99
  const [local, rest] = splitProps(props, [
@@ -94,6 +102,7 @@ export function SettingsRow(
94
102
  'description',
95
103
  'orientation',
96
104
  'bare',
105
+ 'flush',
97
106
  'children',
98
107
  ])
99
108
  const rowId = createUniqueId()
@@ -113,7 +122,8 @@ export function SettingsRow(
113
122
  <div
114
123
  data-slot="settings-row"
115
124
  class={cn(
116
- 'flex gap-4 p-4',
125
+ 'flex gap-4',
126
+ local.flush ? 'p-0' : 'p-4',
117
127
  local.orientation === 'vertical'
118
128
  ? 'flex-col'
119
129
  : 'flex-col sm:flex-row sm:items-center sm:justify-between',
@@ -2,22 +2,45 @@ import { Search } from 'lucide-solid'
2
2
  import type { ComponentProps, JSX } from 'solid-js'
3
3
  import { Show, splitProps } from 'solid-js'
4
4
  import { ActionButton } from '../../composites/action-button'
5
+ import { cn } from '../../../lib/utils'
5
6
  import { Spinner } from '../spinner'
6
7
  import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from './empty'
7
8
 
8
- export type EmptyStateProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'aria-live'> & {
9
- title: string
10
- detail: string
11
- busy?: boolean
12
- icon?: JSX.Element
13
- announceAs?: 'alert' | 'status'
14
- headingLevel?: 1 | 2 | 3 | 4 | 5 | 6
15
- action?: () => void
16
- actionLabel?: string
17
- actionTooltip?: string
18
- }
9
+ type EmptyStateSecondaryAction =
10
+ | {
11
+ secondaryAction?: undefined
12
+ secondaryActionLabel?: undefined
13
+ secondaryActionTooltip?: never
14
+ }
15
+ | {
16
+ /** A second, quieter way out beside `action`: "Open settings" next to "Retry". */
17
+ secondaryAction: () => void
18
+ /** Required: there is no sensible default name for an arbitrary second action. */
19
+ secondaryActionLabel: string
20
+ secondaryActionTooltip?: string
21
+ }
22
+
23
+ export type EmptyStateProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'aria-live'> &
24
+ EmptyStateSecondaryAction & {
25
+ title: string
26
+ detail: string
27
+ busy?: boolean
28
+ icon?: JSX.Element
29
+ announceAs?: 'alert' | 'status'
30
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6
31
+ action?: () => void
32
+ actionLabel?: string
33
+ actionTooltip?: string
34
+ }
19
35
 
20
- /** A semantic empty, loading, or error state built from the shared empty parts. */
36
+ /**
37
+ * A semantic empty, loading, or error state built from the shared empty parts.
38
+ *
39
+ * `action` is the primary way forward. `secondaryAction` adds one quieter
40
+ * alternative after it, in a `secondary` button, so the primary stays first in
41
+ * the tab order. Two actions side by side is the limit: a state that needs more
42
+ * should compose the `Empty*` parts directly.
43
+ */
21
44
  export function EmptyState(props: EmptyStateProps) {
22
45
  const [local, rest] = splitProps(props, [
23
46
  'title',
@@ -29,6 +52,9 @@ export function EmptyState(props: EmptyStateProps) {
29
52
  'action',
30
53
  'actionLabel',
31
54
  'actionTooltip',
55
+ 'secondaryAction',
56
+ 'secondaryActionLabel',
57
+ 'secondaryActionTooltip',
32
58
  'class',
33
59
  ])
34
60
 
@@ -55,15 +81,27 @@ export function EmptyState(props: EmptyStateProps) {
55
81
  </EmptyTitle>
56
82
  <EmptyDescription>{local.detail}</EmptyDescription>
57
83
  </EmptyHeader>
58
- <Show when={local.action}>
59
- <EmptyContent>
60
- <ActionButton
61
- tooltip={local.actionTooltip ?? local.actionLabel ?? 'Try again'}
62
- aria-label={local.actionLabel ?? 'Try again'}
63
- onClick={local.action}
64
- >
65
- {local.actionLabel ?? 'Try again'}
66
- </ActionButton>
84
+ <Show when={local.action || local.secondaryAction}>
85
+ <EmptyContent class={cn({ 'flex-row flex-wrap justify-center': !!local.secondaryAction })}>
86
+ <Show when={local.action}>
87
+ <ActionButton
88
+ tooltip={local.actionTooltip ?? local.actionLabel ?? 'Try again'}
89
+ aria-label={local.actionLabel ?? 'Try again'}
90
+ onClick={local.action}
91
+ >
92
+ {local.actionLabel ?? 'Try again'}
93
+ </ActionButton>
94
+ </Show>
95
+ <Show when={local.secondaryAction}>
96
+ <ActionButton
97
+ variant="secondary"
98
+ tooltip={local.secondaryActionTooltip ?? local.secondaryActionLabel}
99
+ aria-label={local.secondaryActionLabel}
100
+ onClick={local.secondaryAction}
101
+ >
102
+ {local.secondaryActionLabel}
103
+ </ActionButton>
104
+ </Show>
67
105
  </EmptyContent>
68
106
  </Show>
69
107
  </Empty>
@@ -195,8 +195,12 @@ export type FieldGroupProps = ComponentProps<'div'> & {
195
195
  * that needs the full width says `class="col-span-full"`. Below `sm` it is
196
196
  * one column again, because two inputs side by side on a phone are each too
197
197
  * narrow to read.
198
+ *
199
+ * `3` is the same grid with three columns from `sm`, for a row of short values
200
+ * (a trio of limits, a day count and two thresholds). Each column is a third of
201
+ * the group, so reach for `2` when a field holds a path, a URL or a sentence.
198
202
  */
199
- columns?: 1 | 2
203
+ columns?: 1 | 2 | 3
200
204
  }
201
205
 
202
206
  /** A visual stack for related controls that does not imply fieldset semantics. */
@@ -206,7 +210,11 @@ export function FieldGroup(props: FieldGroupProps) {
206
210
  <div
207
211
  data-slot="field-group"
208
212
  class={cn(
209
- local.columns === 2 ? 'grid gap-4 sm:grid-cols-2' : 'flex flex-col gap-4',
213
+ {
214
+ 'grid gap-4 sm:grid-cols-2': local.columns === 2,
215
+ 'grid gap-4 sm:grid-cols-3': local.columns === 3,
216
+ 'flex flex-col gap-4': local.columns !== 2 && local.columns !== 3,
217
+ },
210
218
  local.class
211
219
  )}
212
220
  {...rest}
@@ -8,6 +8,20 @@ export type NumberFieldProps = {
8
8
  value: number
9
9
  min: number
10
10
  max: number
11
+ /**
12
+ * The increment of the spinner and the arrow keys, and the grid a typed value
13
+ * must sit on: `min` plus a whole number of steps. Use it for stepped or
14
+ * fractional values (`0.05` for a 0-1 score, `5` for a percentage in fives).
15
+ * Without it a field steps by 1, or by `any` when `integer` is `false`, which
16
+ * accepts every decimal but leaves the spinner moving in whole numbers.
17
+ */
18
+ step?: number
19
+ /**
20
+ * Whether only whole numbers are valid. It defaults to `true`, or, when a
21
+ * `step` is given, to whether both the step and its base `min` are whole
22
+ * numbers, so `step={0.05}` accepts decimals without also saying
23
+ * `integer={false}`, and `min={0.5} step={1}` accepts 0.5, 1.5, 2.5.
24
+ */
11
25
  integer?: boolean
12
26
  onChange: (value: number) => void
13
27
  class?: string
@@ -16,13 +30,20 @@ export type NumberFieldProps = {
16
30
 
17
31
  /**
18
32
  * A controlled numeric field that keeps an editable draft while validating
19
- * range and integer constraints. Invalid drafts are announced and restored
20
- * to the latest controlled value when focus leaves the input.
33
+ * range, integer and step constraints. Invalid drafts are announced and restored
34
+ * to the latest controlled value when focus leaves the input, so `onChange`
35
+ * only ever receives a value the field accepts — a caller never needs a
36
+ * `Number(value) || fallback` of its own.
21
37
  */
22
38
  export function NumberField(props: NumberFieldProps) {
23
39
  const [draft, setDraft] = createSignal(String(props.value))
24
40
  const [error, setError] = createSignal('')
25
- const integer = () => props.integer ?? true
41
+ // Whole numbers only when nothing on the grid is fractional: a whole step from
42
+ // a fractional `min` (0.5, 1.5, 2.5) puts the spinner's own values off the
43
+ // integers, so inference needs the step and its base to both be whole.
44
+ const integer = () =>
45
+ props.integer ??
46
+ (props.step === undefined || (Number.isInteger(props.step) && Number.isInteger(props.min)))
26
47
 
27
48
  createEffect(() => {
28
49
  setDraft(String(props.value))
@@ -38,6 +59,16 @@ export function NumberField(props: NumberFieldProps) {
38
59
  if (next < props.min || next > props.max) {
39
60
  return `${props.label} must be between ${props.min} and ${props.max}.`
40
61
  }
62
+ if (props.step !== undefined && props.step > 0) {
63
+ // Compare in whole steps with a tolerance: 0.15 / 0.05 is 3.0000000000000004,
64
+ // and rejecting it would make the spinner's own values invalid.
65
+ const steps = (next - props.min) / props.step
66
+ if (Math.abs(steps - Math.round(steps)) > 1e-9) {
67
+ return props.min === 0
68
+ ? `${props.label} must be a multiple of ${props.step}.`
69
+ : `${props.label} must be ${props.min} plus a multiple of ${props.step}.`
70
+ }
71
+ }
41
72
  return ''
42
73
  }
43
74
 
@@ -48,7 +79,7 @@ export function NumberField(props: NumberFieldProps) {
48
79
  value={draft()}
49
80
  min={props.min}
50
81
  max={props.max}
51
- step={integer() ? 1 : 'any'}
82
+ step={props.step ?? (integer() ? 1 : 'any')}
52
83
  required
53
84
  onInput={(event) => {
54
85
  const raw = event.currentTarget.value
@@ -8,10 +8,29 @@ export type SecretInputGroupProps = {
8
8
  disabled: boolean
9
9
  onChange: JSX.EventHandler<HTMLInputElement, Event>
10
10
  onClear?: () => void
11
+ /**
12
+ * What the clear action does, for this field: its accessible name and its
13
+ * tooltip while enabled ("Clear stored token", "Clear Google token path").
14
+ * Defaults to "Clear API key".
15
+ */
16
+ clearLabel?: string
17
+ /**
18
+ * Why the clear action is unavailable, shown as its tooltip while the group is
19
+ * `disabled`. Defaults to the policy message, so name the real reason when a
20
+ * field is disabled for another one ("Set a token variable first").
21
+ */
22
+ disabledReason?: string
11
23
  class?: string
12
24
  } & Pick<ComponentProps<'input'>, 'id' | 'aria-label' | 'aria-describedby' | 'aria-invalid'>
13
25
 
14
- /** A password input with an optional keyboard-accessible clear action. */
26
+ /**
27
+ * A password input with an optional keyboard-accessible clear action.
28
+ *
29
+ * The wording is the caller's: inside a `FormField` the input takes the field's
30
+ * label, outside one it needs `aria-label` (the fallback names an API key), and
31
+ * `clearLabel` and `disabledReason` name the clear action for the field it
32
+ * belongs to, so a bearer token or a token path is not announced as an API key.
33
+ */
15
34
  export function SecretInputGroup(props: SecretInputGroupProps) {
16
35
  const field = useContext(FormFieldContext)
17
36
  const [local, rest] = splitProps(props, [
@@ -19,6 +38,8 @@ export function SecretInputGroup(props: SecretInputGroupProps) {
19
38
  'disabled',
20
39
  'onChange',
21
40
  'onClear',
41
+ 'clearLabel',
42
+ 'disabledReason',
22
43
  'aria-label',
23
44
  'class',
24
45
  ])
@@ -46,9 +67,13 @@ export function SecretInputGroup(props: SecretInputGroupProps) {
46
67
  size="xs"
47
68
  class="-me-1 h-6 min-w-0 shrink-0 px-2"
48
69
  disabled={local.disabled}
49
- tooltip={local.disabled ? 'This API key is managed by policy' : 'Clear API key'}
70
+ tooltip={
71
+ local.disabled
72
+ ? (local.disabledReason ?? 'This API key is managed by policy')
73
+ : (local.clearLabel ?? 'Clear API key')
74
+ }
50
75
  tooltipSide="top"
51
- aria-label="Clear API key"
76
+ aria-label={local.clearLabel ?? 'Clear API key'}
52
77
  onClick={() => local.onClear?.()}
53
78
  >
54
79
  Clear
@@ -12,6 +12,14 @@ export type NativeSelectProps = Omit<ComponentProps<'select'>, 'size'> & {
12
12
  * control that this styled single-line select is not.
13
13
  */
14
14
  size?: 'sm' | 'md' | 'lg'
15
+ /**
16
+ * Layout classes for the element that wraps the select and its chevron: a
17
+ * width or `max-w-*`, `flex-1`, `col-span-*`, a margin. `class` reaches the
18
+ * `<select>` itself, which is the wrong box for any of those, because the
19
+ * wrapper is what a flex or grid parent lays out. The select fills the
20
+ * wrapper, so the chevron stays at the control's edge at any width.
21
+ */
22
+ wrapperClass?: string
15
23
  /** Render the choices from data instead of raw option children. */
16
24
  options?: readonly NativeSelectOption[]
17
25
  /** Initialize an uncontrolled selection after its native options mount. */
@@ -69,6 +77,7 @@ export type NativeSelectOption = Readonly<{
69
77
  export function NativeSelect(props: NativeSelectProps) {
70
78
  const [local, rest] = splitProps(props, [
71
79
  'class',
80
+ 'wrapperClass',
72
81
  'size',
73
82
  'children',
74
83
  'defaultValue',
@@ -103,7 +112,10 @@ export function NativeSelect(props: NativeSelectProps) {
103
112
  })
104
113
 
105
114
  return (
106
- <span data-slot="native-select-wrapper" class="relative inline-flex min-w-0">
115
+ <span
116
+ data-slot="native-select-wrapper"
117
+ class={cn('relative inline-flex min-w-0', local.wrapperClass)}
118
+ >
107
119
  <select
108
120
  ref={(element) => {
109
121
  select = element
@@ -116,7 +128,7 @@ export function NativeSelect(props: NativeSelectProps) {
116
128
  aria-invalid={field?.['aria-invalid'] ?? local['aria-invalid']}
117
129
  aria-errormessage={field?.['aria-errormessage'] ?? local['aria-errormessage']}
118
130
  class={cn(
119
- 'min-w-0 appearance-none rounded-md border border-input bg-transparent',
131
+ 'w-full min-w-0 appearance-none rounded-md border border-input bg-transparent',
120
132
  {
121
133
  'h-control-sm px-control-sm pr-8 text-xs': local.size === 'sm',
122
134
  'h-control-md px-control-md pr-9 text-sm':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.131.1",
3
+ "version": "0.134.0",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -18,6 +18,15 @@ export type ListRowControlProps<T extends ValidComponent = 'div'> = PolymorphicP
18
18
  trailing?: JSX.Element
19
19
  /** A second line that wraps and grows the row to fit its content. */
20
20
  description?: string
21
+ /**
22
+ * Keep the description on one line and end it with an ellipsis instead of
23
+ * wrapping, so a described row stays at its minimum height however long the
24
+ * text is. Use it where rows must be a predictable height (a virtualised
25
+ * queue); leave it off where the whole description should be readable. Pair
26
+ * it with a `tooltip` when the full text matters, and with
27
+ * `stackTrailing={false}` to keep a trailing slot beside the text.
28
+ */
29
+ truncateDescription?: boolean
21
30
  /**
22
31
  * Whether a described row may drop its trailing slot onto its own line when
23
32
  * the row gets narrow (the default). Pass `false` for a short, truncating
@@ -43,6 +52,7 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
43
52
  'leading',
44
53
  'trailing',
45
54
  'description',
55
+ 'truncateDescription',
46
56
  'stackTrailing',
47
57
  'type',
48
58
  'tabIndex',
@@ -92,7 +102,8 @@ export function ListRowControl<T extends ValidComponent = 'div'>(props: ListRowC
92
102
  <span
93
103
  data-slot="list-row-description"
94
104
  class={cn(
95
- 'text-xs break-words',
105
+ 'text-xs',
106
+ { truncate: local.truncateDescription, 'break-words': !local.truncateDescription },
96
107
  local.selected ? 'text-foreground' : 'text-muted-foreground'
97
108
  )}
98
109
  >