@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.
- package/dist/components/composites/list-row/list-row-control.d.ts +9 -0
- package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -1
- package/dist/components/composites/list-row/list-row-control.js +5 -1
- package/dist/components/composites/list-row/list-row-control.js.map +1 -1
- package/dist/components/composites/settings/settings.d.ts +8 -0
- package/dist/components/composites/settings/settings.d.ts.map +1 -1
- package/dist/components/composites/settings/settings.js +2 -1
- package/dist/components/composites/settings/settings.js.map +1 -1
- package/dist/components/ui/empty/empty-state.d.ts +21 -2
- package/dist/components/ui/empty/empty-state.d.ts.map +1 -1
- package/dist/components/ui/empty/empty-state.js +62 -18
- package/dist/components/ui/empty/empty-state.js.map +1 -1
- package/dist/components/ui/field/field.d.ts +5 -1
- package/dist/components/ui/field/field.d.ts.map +1 -1
- package/dist/components/ui/field/field.js +5 -1
- package/dist/components/ui/field/field.js.map +1 -1
- package/dist/components/ui/field/number-field.d.ts +18 -2
- package/dist/components/ui/field/number-field.d.ts.map +1 -1
- package/dist/components/ui/field/number-field.js +10 -4
- package/dist/components/ui/field/number-field.js.map +1 -1
- package/dist/components/ui/input-group/secret-input-group.d.ts +20 -1
- package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -1
- package/dist/components/ui/input-group/secret-input-group.js +15 -4
- package/dist/components/ui/input-group/secret-input-group.js.map +1 -1
- package/dist/components/ui/native-select/native-select.d.ts +8 -0
- package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
- package/dist/components/ui/native-select/native-select.js +5 -3
- package/dist/components/ui/native-select/native-select.js.map +1 -1
- package/dist/r/src/components/composites/list-row/list-row-control.tsx +12 -1
- package/dist/r/src/components/composites/settings/settings.tsx +11 -1
- package/dist/r/src/components/ui/empty/empty-state.tsx +59 -21
- package/dist/r/src/components/ui/field/field.tsx +10 -2
- package/dist/r/src/components/ui/field/number-field.tsx +35 -4
- package/dist/r/src/components/ui/input-group/secret-input-group.tsx +28 -3
- package/dist/r/src/components/ui/native-select/native-select.tsx +14 -2
- package/package.json +1 -1
- package/src/components/composites/list-row/list-row-control.tsx +12 -1
- package/src/components/composites/settings/settings.tsx +11 -1
- package/src/components/ui/empty/empty-state.tsx +59 -21
- package/src/components/ui/field/field.tsx +10 -2
- package/src/components/ui/field/number-field.tsx +35 -4
- package/src/components/ui/input-group/secret-input-group.tsx +28 -3
- 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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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"
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
/**
|
|
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
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
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={
|
|
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=
|
|
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
|
|
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
|
@@ -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
|
|
105
|
+
'text-xs',
|
|
106
|
+
{ truncate: local.truncateDescription, 'break-words': !local.truncateDescription },
|
|
96
107
|
local.selected ? 'text-foreground' : 'text-muted-foreground'
|
|
97
108
|
)}
|
|
98
109
|
>
|