@adea-ai/ui 0.133.0 → 0.135.1

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 (31) hide show
  1. package/dist/components/composites/settings/settings.d.ts +8 -0
  2. package/dist/components/composites/settings/settings.d.ts.map +1 -1
  3. package/dist/components/composites/settings/settings.js +2 -1
  4. package/dist/components/composites/settings/settings.js.map +1 -1
  5. package/dist/components/composites/stat/stat.d.ts +14 -2
  6. package/dist/components/composites/stat/stat.d.ts.map +1 -1
  7. package/dist/components/composites/stat/stat.js +13 -3
  8. package/dist/components/composites/stat/stat.js.map +1 -1
  9. package/dist/components/ui/card/card.d.ts +9 -0
  10. package/dist/components/ui/card/card.d.ts.map +1 -1
  11. package/dist/components/ui/card/card.js +10 -1
  12. package/dist/components/ui/card/card.js.map +1 -1
  13. package/dist/components/ui/input-group/secret-input-group.d.ts +20 -1
  14. package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -1
  15. package/dist/components/ui/input-group/secret-input-group.js +15 -4
  16. package/dist/components/ui/input-group/secret-input-group.js.map +1 -1
  17. package/dist/components/ui/native-select/native-select.d.ts +8 -0
  18. package/dist/components/ui/native-select/native-select.d.ts.map +1 -1
  19. package/dist/components/ui/native-select/native-select.js +5 -3
  20. package/dist/components/ui/native-select/native-select.js.map +1 -1
  21. package/dist/r/src/components/composites/settings/settings.tsx +11 -1
  22. package/dist/r/src/components/composites/stat/stat.tsx +22 -4
  23. package/dist/r/src/components/ui/card/card.tsx +17 -1
  24. package/dist/r/src/components/ui/input-group/secret-input-group.tsx +28 -3
  25. package/dist/r/src/components/ui/native-select/native-select.tsx +14 -2
  26. package/package.json +1 -1
  27. package/src/components/composites/settings/settings.tsx +11 -1
  28. package/src/components/composites/stat/stat.tsx +22 -4
  29. package/src/components/ui/card/card.tsx +17 -1
  30. package/src/components/ui/input-group/secret-input-group.tsx +28 -3
  31. package/src/components/ui/native-select/native-select.tsx +14 -2
@@ -36,6 +36,14 @@ export declare function SettingsRow(props: ComponentProps<'div'> & {
36
36
  orientation?: 'horizontal' | 'vertical';
37
37
  /** Hide the divider this row would otherwise contribute. */
38
38
  bare?: boolean;
39
+ /**
40
+ * Drop the row's own 16px padding so its label and control sit on the same
41
+ * edges as the fields around it. For a row placed loose in a
42
+ * `bodyLayout="content"` section, where nothing encloses it and the section's
43
+ * gap already spaces it from its neighbours. Leave it off inside a divided
44
+ * rows body or a `Card`, whose edge the padding is there to clear.
45
+ */
46
+ flush?: boolean;
39
47
  }): JSX.Element;
40
48
  /** A stacked label-and-control pair, for a control too wide for a row. */
41
49
  export declare function SettingsField(props: ComponentProps<'div'> & {
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/settings/settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,CAAC,SAAS,CAAC,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC,eA0CF;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,4DAA4D;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,eAsDF;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,eAiBzF;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eASxD"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/settings/settings.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,CAAC,SAAS,CAAC,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC,eA0CF;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,4DAA4D;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,eAwDF;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,eAiBzF;AAED,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eASxD"}
@@ -85,6 +85,7 @@ function SettingsRow(props) {
85
85
  "description",
86
86
  "orientation",
87
87
  "bare",
88
+ "flush",
88
89
  "children"
89
90
  ]);
90
91
  const rowId = createUniqueId();
@@ -103,7 +104,7 @@ function SettingsRow(props) {
103
104
  get children() {
104
105
  var _el$7 = _tmpl$4(), _el$8 = _el$7.firstChild, _el$9 = _el$8.firstChild, _el$1 = _el$8.nextSibling;
105
106
  spread(_el$7, mergeProps({ get ["class"]() {
106
- return cn("flex gap-4 p-4", local.orientation === "vertical" ? "flex-col" : "flex-col sm:flex-row sm:items-center sm:justify-between", local.class);
107
+ return cn("flex gap-4", local.flush ? "p-0" : "p-4", local.orientation === "vertical" ? "flex-col" : "flex-col sm:flex-row sm:items-center sm:justify-between", local.class);
107
108
  } }, rest), false, true);
108
109
  setAttribute(_el$9, "id", labelId);
109
110
  insert(_el$9, () => local.label);
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","names":["ComponentProps","JSX","createUniqueId","Show","splitProps","createFormFieldContext","FormFieldContext","cn","Heading","SettingsSection","props","title","description","action","Element","bodyLayout","local","rest","_el$","_tmpl$3","_el$2","firstChild","_el$3","_el$6","nextSibling","_$spread","_$mergeProps","class","_$insert","_$createComponent","as","size","children","when","_el$4","_tmpl$","_el$5","_tmpl$2","_$effect","_$className","SettingsRow","label","orientation","bare","rowId","labelId","descriptionId","field","defaultControlId","hint","error","undefined","group","Provider","value","_el$7","_tmpl$4","_el$8","_el$9","_el$1","_$setAttribute","_el$0","SettingsField","htmlFor","_el$10","_tmpl$5","_el$11","_el$12","_el$14","_el$13","SettingsPage","_el$15","_tmpl$6","_$mergeProps","_$createComponent","_$className","_$setAttribute"],"sources":["../../../../src/components/composites/settings/settings.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { createUniqueId, Show, splitProps } from 'solid-js'\nimport { createFormFieldContext, FormFieldContext } from '#lib/form-field'\nimport { cn } from '#lib/utils'\nimport { Heading } from '../../ui/typography'\n\n/**\n * SettingsSection and SettingsRow.\n *\n * A settings page is the most repetitive surface in any application, and the\n * one where drift is most visible: label sizes, control widths and description\n * placement all wander unless something fixes them.\n *\n * The row puts the label and description at the start and the control at the\n * end (stacked on a narrow window), so controls line up down the page's end\n * edge regardless of how long the labels are. `SettingsRow` also\n * exposes its label and description through the shared form-field context to\n * the first control inside it. `SettingsField` is the visual stacked layout;\n * use `FormField` when the control needs label, help, or validation wiring.\n */\nexport function SettingsSection(\n props: ComponentProps<'section'> & {\n title: string\n description?: string\n /** A control for the section as a whole, e.g. a master toggle. */\n action?: JSX.Element\n /** Use a spaced content stack for fields, alerts, and cards instead of divided rows. */\n bodyLayout?: 'rows' | 'content'\n }\n) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'title',\n 'description',\n 'action',\n 'bodyLayout',\n 'children',\n ])\n\n return (\n <section\n data-slot=\"settings-section\"\n class={cn('flex flex-col gap-4 px-5 py-5', local.class)}\n {...rest}\n >\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <Heading as=\"h2\" size=\"card\">\n {local.title}\n </Heading>\n <Show when={local.description}>\n <p class=\"max-w-prose text-sm text-muted-foreground text-pretty\">{local.description}</p>\n </Show>\n </div>\n <Show when={local.action}>\n <div class=\"flex shrink-0 items-center gap-2\">{local.action}</div>\n </Show>\n </div>\n <div\n data-slot=\"settings-section-body\"\n class={cn(\n local.bodyLayout === 'content'\n ? 'flex flex-col gap-4'\n : 'divide-y divide-border rounded-xl border border-border [&>[data-slot=empty-description]]:p-4'\n )}\n >\n {local.children}\n </div>\n </section>\n )\n}\n\n/**\n * One setting: a label and description on the leading side, a control on the\n * trailing side.\n *\n * The label and description are linked to a shared control through field\n * context. An explicit `aria-label` or `aria-labelledby` on the control keeps\n * precedence; otherwise the visible row label names it.\n */\nexport function SettingsRow(\n props: ComponentProps<'div'> & {\n label: string\n description?: string\n /** Render a vertical stack when the control needs the full width. */\n orientation?: 'horizontal' | 'vertical'\n /** Hide the divider this row would otherwise contribute. */\n bare?: boolean\n }\n) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'description',\n 'orientation',\n 'bare',\n 'children',\n ])\n const rowId = createUniqueId()\n const labelId = `${rowId}-label`\n const descriptionId = `${rowId}-description`\n const field = createFormFieldContext({\n defaultControlId: rowId,\n labelId,\n descriptionId,\n hint: () => local.description,\n error: () => undefined,\n group: () => false,\n })\n\n return (\n <FormFieldContext.Provider value={field}>\n <div\n data-slot=\"settings-row\"\n class={cn(\n 'flex gap-4 p-4',\n local.orientation === 'vertical'\n ? 'flex-col'\n : 'flex-col sm:flex-row sm:items-center sm:justify-between',\n local.class\n )}\n {...rest}\n >\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <div id={labelId} data-slot=\"settings-row-label\" class=\"text-sm font-medium\">\n {local.label}\n </div>\n <Show when={local.description}>\n <p id={descriptionId} class=\"max-w-prose text-sm text-muted-foreground text-pretty\">\n {local.description}\n </p>\n </Show>\n </div>\n <div\n data-slot=\"settings-row-control\"\n class={cn('shrink-0', local.orientation === 'vertical' && 'w-full')}\n >\n {local.children}\n </div>\n </div>\n </FormFieldContext.Provider>\n )\n}\n\n/** A stacked label-and-control pair, for a control too wide for a row. */\nexport function SettingsField(\n props: ComponentProps<'div'> & { label: string; description?: string; htmlFor?: string }\n) {\n const [local, rest] = splitProps(props, ['class', 'label', 'description', 'htmlFor', 'children'])\n\n return (\n <div data-slot=\"settings-field\" class={cn('flex flex-col gap-2 p-4', local.class)} {...rest}>\n <div class=\"flex flex-col gap-0.5\">\n <label for={local.htmlFor} class=\"text-sm font-medium\">\n {local.label}\n </label>\n <Show when={local.description}>\n <p class=\"max-w-prose text-sm text-muted-foreground text-pretty\">{local.description}</p>\n </Show>\n </div>\n <div>{local.children}</div>\n </div>\n )\n}\n\n/** A settings page's scrolling body, with the header sticky at the top. */\nexport function SettingsPage(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"settings-page\"\n class={cn('flex min-h-0 flex-1 flex-col overflow-y-auto', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"settings.js","names":["ComponentProps","JSX","createUniqueId","Show","splitProps","createFormFieldContext","FormFieldContext","cn","Heading","SettingsSection","props","title","description","action","Element","bodyLayout","local","rest","_el$","_tmpl$3","_el$2","firstChild","_el$3","_el$6","nextSibling","_$spread","_$mergeProps","class","_$insert","_$createComponent","as","size","children","when","_el$4","_tmpl$","_el$5","_tmpl$2","_$effect","_$className","SettingsRow","label","orientation","bare","flush","rowId","labelId","descriptionId","field","defaultControlId","hint","error","undefined","group","Provider","value","_el$7","_tmpl$4","_el$8","_el$9","_el$1","_$setAttribute","_el$0","SettingsField","htmlFor","_el$10","_tmpl$5","_el$11","_el$12","_el$14","_el$13","SettingsPage","_el$15","_tmpl$6","_$mergeProps","_$createComponent","_$className","_$setAttribute"],"sources":["../../../../src/components/composites/settings/settings.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { createUniqueId, Show, splitProps } from 'solid-js'\nimport { createFormFieldContext, FormFieldContext } from '#lib/form-field'\nimport { cn } from '#lib/utils'\nimport { Heading } from '../../ui/typography'\n\n/**\n * SettingsSection and SettingsRow.\n *\n * A settings page is the most repetitive surface in any application, and the\n * one where drift is most visible: label sizes, control widths and description\n * placement all wander unless something fixes them.\n *\n * The row puts the label and description at the start and the control at the\n * end (stacked on a narrow window), so controls line up down the page's end\n * edge regardless of how long the labels are. `SettingsRow` also\n * exposes its label and description through the shared form-field context to\n * the first control inside it. `SettingsField` is the visual stacked layout;\n * use `FormField` when the control needs label, help, or validation wiring.\n */\nexport function SettingsSection(\n props: ComponentProps<'section'> & {\n title: string\n description?: string\n /** A control for the section as a whole, e.g. a master toggle. */\n action?: JSX.Element\n /** Use a spaced content stack for fields, alerts, and cards instead of divided rows. */\n bodyLayout?: 'rows' | 'content'\n }\n) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'title',\n 'description',\n 'action',\n 'bodyLayout',\n 'children',\n ])\n\n return (\n <section\n data-slot=\"settings-section\"\n class={cn('flex flex-col gap-4 px-5 py-5', local.class)}\n {...rest}\n >\n <div class=\"flex items-start justify-between gap-4\">\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <Heading as=\"h2\" size=\"card\">\n {local.title}\n </Heading>\n <Show when={local.description}>\n <p class=\"max-w-prose text-sm text-muted-foreground text-pretty\">{local.description}</p>\n </Show>\n </div>\n <Show when={local.action}>\n <div class=\"flex shrink-0 items-center gap-2\">{local.action}</div>\n </Show>\n </div>\n <div\n data-slot=\"settings-section-body\"\n class={cn(\n local.bodyLayout === 'content'\n ? 'flex flex-col gap-4'\n : 'divide-y divide-border rounded-xl border border-border [&>[data-slot=empty-description]]:p-4'\n )}\n >\n {local.children}\n </div>\n </section>\n )\n}\n\n/**\n * One setting: a label and description on the leading side, a control on the\n * trailing side.\n *\n * The label and description are linked to a shared control through field\n * context. An explicit `aria-label` or `aria-labelledby` on the control keeps\n * precedence; otherwise the visible row label names it.\n */\nexport function SettingsRow(\n props: ComponentProps<'div'> & {\n label: string\n description?: string\n /** Render a vertical stack when the control needs the full width. */\n orientation?: 'horizontal' | 'vertical'\n /** Hide the divider this row would otherwise contribute. */\n bare?: boolean\n /**\n * Drop the row's own 16px padding so its label and control sit on the same\n * edges as the fields around it. For a row placed loose in a\n * `bodyLayout=\"content\"` section, where nothing encloses it and the section's\n * gap already spaces it from its neighbours. Leave it off inside a divided\n * rows body or a `Card`, whose edge the padding is there to clear.\n */\n flush?: boolean\n }\n) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'description',\n 'orientation',\n 'bare',\n 'flush',\n 'children',\n ])\n const rowId = createUniqueId()\n const labelId = `${rowId}-label`\n const descriptionId = `${rowId}-description`\n const field = createFormFieldContext({\n defaultControlId: rowId,\n labelId,\n descriptionId,\n hint: () => local.description,\n error: () => undefined,\n group: () => false,\n })\n\n return (\n <FormFieldContext.Provider value={field}>\n <div\n data-slot=\"settings-row\"\n class={cn(\n 'flex gap-4',\n local.flush ? 'p-0' : 'p-4',\n local.orientation === 'vertical'\n ? 'flex-col'\n : 'flex-col sm:flex-row sm:items-center sm:justify-between',\n local.class\n )}\n {...rest}\n >\n <div class=\"flex min-w-0 flex-col gap-0.5\">\n <div id={labelId} data-slot=\"settings-row-label\" class=\"text-sm font-medium\">\n {local.label}\n </div>\n <Show when={local.description}>\n <p id={descriptionId} class=\"max-w-prose text-sm text-muted-foreground text-pretty\">\n {local.description}\n </p>\n </Show>\n </div>\n <div\n data-slot=\"settings-row-control\"\n class={cn('shrink-0', local.orientation === 'vertical' && 'w-full')}\n >\n {local.children}\n </div>\n </div>\n </FormFieldContext.Provider>\n )\n}\n\n/** A stacked label-and-control pair, for a control too wide for a row. */\nexport function SettingsField(\n props: ComponentProps<'div'> & { label: string; description?: string; htmlFor?: string }\n) {\n const [local, rest] = splitProps(props, ['class', 'label', 'description', 'htmlFor', 'children'])\n\n return (\n <div data-slot=\"settings-field\" class={cn('flex flex-col gap-2 p-4', local.class)} {...rest}>\n <div class=\"flex flex-col gap-0.5\">\n <label for={local.htmlFor} class=\"text-sm font-medium\">\n {local.label}\n </label>\n <Show when={local.description}>\n <p class=\"max-w-prose text-sm text-muted-foreground text-pretty\">{local.description}</p>\n </Show>\n </div>\n <div>{local.children}</div>\n </div>\n )\n}\n\n/** A settings page's scrolling body, with the header sticky at the top. */\nexport function SettingsPage(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"settings-page\"\n class={cn('flex min-h-0 flex-1 flex-col overflow-y-auto', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":"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"}
@@ -24,8 +24,20 @@ export type StatProps = ComponentProps<'div'> & {
24
24
  accent?: boolean;
25
25
  };
26
26
  export declare function Stat(props: StatProps): JSX.Element;
27
- /** A row of stats that shares one gap and wraps as the window narrows. */
28
- export declare function StatGroup(props: ComponentProps<'div'>): JSX.Element;
27
+ /**
28
+ * A row of stats that shares one gap and wraps as the window narrows.
29
+ *
30
+ * By default the column count steps with the *viewport* (two from `sm`, four from
31
+ * `lg`), which is right for a group that spans the page. A group inside a pane
32
+ * whose width is not the window's (a docked panel, a resizable column, a view
33
+ * beside a sidebar) gets the wrong count from a viewport breakpoint. `fit` sizes
34
+ * from the group's own width instead: as many tiles per row as fit at the
35
+ * minimum tile width, each growing to share the remainder, however many stats
36
+ * there are.
37
+ */
38
+ export declare function StatGroup(props: ComponentProps<'div'> & {
39
+ fit?: boolean;
40
+ }): JSX.Element;
29
41
  /**
30
42
  * PropertyList.
31
43
  *
@@ -1 +1 @@
1
- {"version":3,"file":"stat.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/stat/stat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;IACrC,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,eAmDpC;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eASrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASvD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASxD;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,eAa1E"}
1
+ {"version":3,"file":"stat.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/stat/stat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,GAAG,CAAC,OAAO,CAAA;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;IACrC,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,eAmDpC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,eAiBzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASvD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,eASxD;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,eAa1E"}
@@ -83,13 +83,23 @@ function Stat(props) {
83
83
  return _el$;
84
84
  })();
85
85
  }
86
- /** A row of stats that shares one gap and wraps as the window narrows. */
86
+ /**
87
+ * A row of stats that shares one gap and wraps as the window narrows.
88
+ *
89
+ * By default the column count steps with the *viewport* (two from `sm`, four from
90
+ * `lg`), which is right for a group that spans the page. A group inside a pane
91
+ * whose width is not the window's (a docked panel, a resizable column, a view
92
+ * beside a sidebar) gets the wrong count from a viewport breakpoint. `fit` sizes
93
+ * from the group's own width instead: as many tiles per row as fit at the
94
+ * minimum tile width, each growing to share the remainder, however many stats
95
+ * there are.
96
+ */
87
97
  function StatGroup(props) {
88
- const [local, rest] = splitProps(props, ["class"]);
98
+ const [local, rest] = splitProps(props, ["class", "fit"]);
89
99
  return (() => {
90
100
  var _el$6 = _tmpl$3();
91
101
  spread(_el$6, mergeProps({ get ["class"]() {
92
- return cn("grid gap-3 sm:grid-cols-2 lg:grid-cols-4", local.class);
102
+ return cn("grid gap-3", local.fit ? "grid-cols-[repeat(auto-fill,minmax(9.375rem,1fr))]" : "sm:grid-cols-2 lg:grid-cols-4", local.class);
93
103
  } }, rest), false, false);
94
104
  return _el$6;
95
105
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"stat.js","names":["ArrowDownRight","ArrowUpRight","ComponentProps","JSX","Show","splitProps","Dynamic","cn","Heading","Text","StatProps","label","value","Element","unit","delta","deltaTone","accent","Stat","props","local","rest","_el$","_tmpl$2","_el$2","firstChild","_el$3","nextSibling","_$spread","_$mergeProps","class","_$insert","_$createComponent","as","size","numeric","children","when","variant","tone","_el$4","_tmpl$","_el$5","component","StatGroup","_el$6","_tmpl$3","PropertyList","_el$7","_tmpl$4","PropertyTerm","_el$8","_tmpl$5","PropertyValue","_el$9","_tmpl$6","PropertyRow","term","_el$0","_tmpl$7","_el$1","_el$10","_$mergeProps","_$createComponent"],"sources":["../../../../src/components/composites/stat/stat.tsx"],"sourcesContent":["import { ArrowDownRight, ArrowUpRight } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { cn } from '#lib/utils'\nimport { Heading, Text } from '../../ui/typography'\n\n/**\n * Stat.\n *\n * A single measured number with its label and, optionally, its change. The\n * whole component is a decision about honesty: a value shown without its\n * comparison is not information, so `delta` carries the direction, the amount\n * and — through `deltaTone` — whether the direction is *good*. A rising number\n * is not always a rising outcome, and only the caller knows which way is which.\n *\n * `tabular-nums` on the value is what lets two stats in a row be compared\n * without the digits shifting under the reader.\n */\nexport type StatProps = ComponentProps<'div'> & {\n label: string\n value: JSX.Element\n /** A unit or qualifier after the value, e.g. \"ms\", \"of 10\". */\n unit?: string\n /** The change, e.g. \"+12% from last week\". */\n delta?: string\n /** Whether the change is an improvement. Decides the arrow and the colour. */\n deltaTone?: 'up' | 'down' | 'neutral'\n /** Draws the leading accent rule used for a row of headline numbers. */\n accent?: boolean\n}\n\nexport function Stat(props: StatProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'value',\n 'unit',\n 'delta',\n 'deltaTone',\n 'accent',\n 'children',\n ])\n\n return (\n <div\n data-slot=\"stat\"\n class={cn(\n 'flex min-w-0 flex-col gap-1 rounded-lg border border-border bg-card p-3',\n { 'border-t-2 border-t-primary': local.accent },\n local.class\n )}\n {...rest}\n >\n <div class=\"text-muted-foreground truncate text-xs font-medium\">{local.label}</div>\n <div class=\"flex items-baseline gap-1\">\n <Heading as=\"span\" size=\"page\" numeric>\n {local.value}\n </Heading>\n <Show when={local.unit}>\n <Text variant=\"caption\" tone=\"muted\">\n {local.unit}\n </Text>\n </Show>\n </div>\n <Show when={local.delta}>\n <div class=\"text-foreground flex items-center gap-1 text-xs\">\n <Show when={local.deltaTone === 'up' || local.deltaTone === 'down'}>\n <Dynamic\n component={local.deltaTone === 'down' ? ArrowDownRight : ArrowUpRight}\n aria-hidden=\"true\"\n class={cn('size-3', {\n 'text-success': local.deltaTone === 'up',\n 'text-destructive': local.deltaTone === 'down',\n })}\n />\n </Show>\n <span class=\"tabular-nums\">{local.delta}</span>\n </div>\n </Show>\n {local.children}\n </div>\n )\n}\n\n/** A row of stats that shares one gap and wraps as the window narrows. */\nexport function StatGroup(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"stat-group\"\n class={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-4', local.class)}\n {...rest}\n />\n )\n}\n\n/**\n * PropertyList.\n *\n * A definition list for read-only metadata: the details of a file, a session, a\n * build. This is a `<dl>`, not a table and not a grid of divs, because the\n * relationship being expressed is term-and-definition and the platform has an\n * element for exactly that.\n *\n * Values are selectable by default. Metadata is the thing users copy most and\n * get the least often: a commit hash behind a drag-to-select gesture is a small\n * daily failure.\n */\nexport function PropertyList(props: ComponentProps<'dl'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dl\n data-slot=\"property-list\"\n class={cn('grid grid-cols-[auto_1fr] items-baseline gap-x-4 gap-y-2 text-sm', local.class)}\n {...rest}\n />\n )\n}\n\nexport function PropertyTerm(props: ComponentProps<'dt'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dt\n data-slot=\"property-term\"\n class={cn('text-muted-foreground whitespace-nowrap', local.class)}\n {...rest}\n />\n )\n}\n\nexport function PropertyValue(props: ComponentProps<'dd'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dd\n data-slot=\"property-value\"\n class={cn('min-w-0 break-words select-text', local.class)}\n {...rest}\n />\n )\n}\n\n/** A single term/value pair, for a list built one row at a time. */\nexport function PropertyRow(props: ComponentProps<'div'> & { term: string }) {\n const [local, rest] = splitProps(props, ['class', 'term', 'children'])\n\n return (\n <div\n data-slot=\"property-row\"\n class={cn('flex items-baseline justify-between gap-4 text-sm', local.class)}\n {...rest}\n >\n <span class=\"text-muted-foreground shrink-0\">{local.term}</span>\n <span class=\"min-w-0 truncate text-end select-text\">{local.children}</span>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stat.js","names":["ArrowDownRight","ArrowUpRight","ComponentProps","JSX","Show","splitProps","Dynamic","cn","Heading","Text","StatProps","label","value","Element","unit","delta","deltaTone","accent","Stat","props","local","rest","_el$","_tmpl$2","_el$2","firstChild","_el$3","nextSibling","_$spread","_$mergeProps","class","_$insert","_$createComponent","as","size","numeric","children","when","variant","tone","_el$4","_tmpl$","_el$5","component","StatGroup","fit","_el$6","_tmpl$3","PropertyList","_el$7","_tmpl$4","PropertyTerm","_el$8","_tmpl$5","PropertyValue","_el$9","_tmpl$6","PropertyRow","term","_el$0","_tmpl$7","_el$1","_el$10","_$mergeProps","_$createComponent"],"sources":["../../../../src/components/composites/stat/stat.tsx"],"sourcesContent":["import { ArrowDownRight, ArrowUpRight } from 'lucide-solid'\nimport type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { cn } from '#lib/utils'\nimport { Heading, Text } from '../../ui/typography'\n\n/**\n * Stat.\n *\n * A single measured number with its label and, optionally, its change. The\n * whole component is a decision about honesty: a value shown without its\n * comparison is not information, so `delta` carries the direction, the amount\n * and — through `deltaTone` — whether the direction is *good*. A rising number\n * is not always a rising outcome, and only the caller knows which way is which.\n *\n * `tabular-nums` on the value is what lets two stats in a row be compared\n * without the digits shifting under the reader.\n */\nexport type StatProps = ComponentProps<'div'> & {\n label: string\n value: JSX.Element\n /** A unit or qualifier after the value, e.g. \"ms\", \"of 10\". */\n unit?: string\n /** The change, e.g. \"+12% from last week\". */\n delta?: string\n /** Whether the change is an improvement. Decides the arrow and the colour. */\n deltaTone?: 'up' | 'down' | 'neutral'\n /** Draws the leading accent rule used for a row of headline numbers. */\n accent?: boolean\n}\n\nexport function Stat(props: StatProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'value',\n 'unit',\n 'delta',\n 'deltaTone',\n 'accent',\n 'children',\n ])\n\n return (\n <div\n data-slot=\"stat\"\n class={cn(\n 'flex min-w-0 flex-col gap-1 rounded-lg border border-border bg-card p-3',\n { 'border-t-2 border-t-primary': local.accent },\n local.class\n )}\n {...rest}\n >\n <div class=\"text-muted-foreground truncate text-xs font-medium\">{local.label}</div>\n <div class=\"flex items-baseline gap-1\">\n <Heading as=\"span\" size=\"page\" numeric>\n {local.value}\n </Heading>\n <Show when={local.unit}>\n <Text variant=\"caption\" tone=\"muted\">\n {local.unit}\n </Text>\n </Show>\n </div>\n <Show when={local.delta}>\n <div class=\"text-foreground flex items-center gap-1 text-xs\">\n <Show when={local.deltaTone === 'up' || local.deltaTone === 'down'}>\n <Dynamic\n component={local.deltaTone === 'down' ? ArrowDownRight : ArrowUpRight}\n aria-hidden=\"true\"\n class={cn('size-3', {\n 'text-success': local.deltaTone === 'up',\n 'text-destructive': local.deltaTone === 'down',\n })}\n />\n </Show>\n <span class=\"tabular-nums\">{local.delta}</span>\n </div>\n </Show>\n {local.children}\n </div>\n )\n}\n\n/**\n * A row of stats that shares one gap and wraps as the window narrows.\n *\n * By default the column count steps with the *viewport* (two from `sm`, four from\n * `lg`), which is right for a group that spans the page. A group inside a pane\n * whose width is not the window's (a docked panel, a resizable column, a view\n * beside a sidebar) gets the wrong count from a viewport breakpoint. `fit` sizes\n * from the group's own width instead: as many tiles per row as fit at the\n * minimum tile width, each growing to share the remainder, however many stats\n * there are.\n */\nexport function StatGroup(props: ComponentProps<'div'> & { fit?: boolean }) {\n const [local, rest] = splitProps(props, ['class', 'fit'])\n return (\n <div\n data-slot=\"stat-group\"\n class={cn(\n 'grid gap-3',\n // 9.375rem is 150px at the default root size: room for a label of about\n // twenty characters and a six-digit value before the tile truncates.\n local.fit\n ? 'grid-cols-[repeat(auto-fill,minmax(9.375rem,1fr))]'\n : 'sm:grid-cols-2 lg:grid-cols-4',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/**\n * PropertyList.\n *\n * A definition list for read-only metadata: the details of a file, a session, a\n * build. This is a `<dl>`, not a table and not a grid of divs, because the\n * relationship being expressed is term-and-definition and the platform has an\n * element for exactly that.\n *\n * Values are selectable by default. Metadata is the thing users copy most and\n * get the least often: a commit hash behind a drag-to-select gesture is a small\n * daily failure.\n */\nexport function PropertyList(props: ComponentProps<'dl'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dl\n data-slot=\"property-list\"\n class={cn('grid grid-cols-[auto_1fr] items-baseline gap-x-4 gap-y-2 text-sm', local.class)}\n {...rest}\n />\n )\n}\n\nexport function PropertyTerm(props: ComponentProps<'dt'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dt\n data-slot=\"property-term\"\n class={cn('text-muted-foreground whitespace-nowrap', local.class)}\n {...rest}\n />\n )\n}\n\nexport function PropertyValue(props: ComponentProps<'dd'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <dd\n data-slot=\"property-value\"\n class={cn('min-w-0 break-words select-text', local.class)}\n {...rest}\n />\n )\n}\n\n/** A single term/value pair, for a list built one row at a time. */\nexport function PropertyRow(props: ComponentProps<'div'> & { term: string }) {\n const [local, rest] = splitProps(props, ['class', 'term', 'children'])\n\n return (\n <div\n data-slot=\"property-row\"\n class={cn('flex items-baseline justify-between gap-4 text-sm', local.class)}\n {...rest}\n >\n <span class=\"text-muted-foreground shrink-0\">{local.term}</span>\n <span class=\"min-w-0 truncate text-end select-text\">{local.children}</span>\n </div>\n )\n}\n"],"mappings":"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"}
@@ -24,6 +24,15 @@ export declare const cardVariants: (props?: ({
24
24
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
25
25
  export type CardProps = ComponentProps<'div'> & VariantProps<typeof cardVariants>;
26
26
  export declare function Card(props: CardProps): import("solid-js").JSX.Element;
27
+ /**
28
+ * The header is a grid so that `CardAction` has tracks to be placed in: the
29
+ * action is a direct child that takes a second column spanning the title and
30
+ * description rows. A flex column gives `col-start-2` nothing to land on and
31
+ * the action would simply stack under the copy. The extra column exists only
32
+ * for a direct-child action: a wrapper around it is then the header's single
33
+ * grid item, so it keeps the full width. A header without an action lays out as
34
+ * it always did (one full-width column, 4px between its children).
35
+ */
27
36
  export declare function CardHeader(props: ComponentProps<'div'>): import("solid-js").JSX.Element;
28
37
  export declare function CardTitle(props: ComponentProps<'div'>): import("solid-js").JSX.Element;
29
38
  export declare function CardDescription(props: ComponentProps<'div'>): import("solid-js").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/card/card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAItD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY;;8EAexB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,YAAY,CAAC,CAAA;AAEjF,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,kCAKpC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAKtD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCASrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAS3D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAStD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAGvD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAGtD"}
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/card/card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAItD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY;;8EAexB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,YAAY,CAAC,CAAA;AAEjF,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,kCAKpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAYtD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCASrD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAS3D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAStD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAGvD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAGtD"}
@@ -51,12 +51,21 @@ function Card(props) {
51
51
  return _el$;
52
52
  })();
53
53
  }
54
+ /**
55
+ * The header is a grid so that `CardAction` has tracks to be placed in: the
56
+ * action is a direct child that takes a second column spanning the title and
57
+ * description rows. A flex column gives `col-start-2` nothing to land on and
58
+ * the action would simply stack under the copy. The extra column exists only
59
+ * for a direct-child action: a wrapper around it is then the header's single
60
+ * grid item, so it keeps the full width. A header without an action lays out as
61
+ * it always did (one full-width column, 4px between its children).
62
+ */
54
63
  function CardHeader(props) {
55
64
  const [local, rest] = splitProps(props, ["class"]);
56
65
  return (() => {
57
66
  var _el$2 = _tmpl$2();
58
67
  spread(_el$2, mergeProps({ get ["class"]() {
59
- return cn("flex flex-col gap-1 px-4", local.class);
68
+ return cn("grid grid-cols-1 auto-rows-min gap-x-4 gap-y-1 px-4 has-[>[data-slot=card-action]]:grid-cols-[minmax(0,1fr)_auto]", local.class);
60
69
  } }, rest), false, false);
61
70
  return _el$2;
62
71
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"card.js","names":["ComponentProps","splitProps","cva","VariantProps","cn","headingVariants","cardVariants","variants","size","default","sm","flush","defaultVariants","CardProps","Card","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","CardHeader","_el$2","_tmpl$2","CardTitle","_el$3","_tmpl$3","leading","CardDescription","_el$4","_tmpl$4","CardAction","_el$5","_tmpl$5","CardContent","_el$6","_tmpl$6","CardFooter","_el$7","_tmpl$7","_$mergeProps"],"sources":["../../../../src/components/ui/card/card.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { splitProps } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\nimport { headingVariants } from '../typography'\n\n/**\n * Card.\n *\n * A surface that sits on the canvas. Every card carries its own edge — a\n * border — because in the light theme `--card` is one faint step off a white\n * `--background`, and a borderless card there all but disappears. Making the border\n * structural rather than optional is what stops one app's cards from reading\n * as floating panels and another's as nothing at all.\n *\n * The parts are separate components rather than a template because a card's\n * header, content and footer have different padding on purpose: the header and\n * footer are tight, the content breathes.\n *\n * `size` is the card's padding rung, so a caller never sets `p-*` or `gap-*` on\n * a card to make it denser. `sm` tightens the card and its header, content and\n * footer together (the parts' inline padding follows the card, so they stay\n * aligned with each other); `flush` removes the block padding and gap for a\n * card whose body is a list of rows or a scroll area that runs edge to edge.\n */\nexport const cardVariants = cva(\n 'bg-card text-card-foreground flex flex-col rounded-xl border border-border shadow-xs',\n {\n variants: {\n size: {\n /** 16px block padding and gap; the parts carry 16px inline padding. */\n default: 'gap-4 py-4',\n /** 12px everywhere: a dense settings or status card. */\n sm: 'gap-3 py-3 *:data-[slot=card-content]:px-3 *:data-[slot=card-footer]:px-3 *:data-[slot=card-header]:px-3',\n /** No block padding or gap: the card hosts a row list or a scroller. */\n flush: 'gap-0 py-0',\n },\n },\n defaultVariants: { size: 'default' },\n }\n)\n\nexport type CardProps = ComponentProps<'div'> & VariantProps<typeof cardVariants>\n\nexport function Card(props: CardProps) {\n const [local, rest] = splitProps(props, ['class', 'size'])\n return (\n <div data-slot=\"card\" class={cn(cardVariants({ size: local.size }), local.class)} {...rest} />\n )\n}\n\nexport function CardHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div data-slot=\"card-header\" class={cn('flex flex-col gap-1 px-4', local.class)} {...rest} />\n )\n}\n\nexport function CardTitle(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-title\"\n class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardDescription(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-description\"\n class={cn('text-muted-foreground text-sm', local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardAction(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-action\"\n class={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return <div data-slot=\"card-content\" class={cn('px-4', local.class)} {...rest} />\n}\n\nexport function CardFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return <div data-slot=\"card-footer\" class={cn('flex items-center px-4', local.class)} {...rest} />\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"card.js","names":["ComponentProps","splitProps","cva","VariantProps","cn","headingVariants","cardVariants","variants","size","default","sm","flush","defaultVariants","CardProps","Card","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","CardHeader","_el$2","_tmpl$2","CardTitle","_el$3","_tmpl$3","leading","CardDescription","_el$4","_tmpl$4","CardAction","_el$5","_tmpl$5","CardContent","_el$6","_tmpl$6","CardFooter","_el$7","_tmpl$7","_$mergeProps"],"sources":["../../../../src/components/ui/card/card.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { splitProps } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\nimport { headingVariants } from '../typography'\n\n/**\n * Card.\n *\n * A surface that sits on the canvas. Every card carries its own edge — a\n * border — because in the light theme `--card` is one faint step off a white\n * `--background`, and a borderless card there all but disappears. Making the border\n * structural rather than optional is what stops one app's cards from reading\n * as floating panels and another's as nothing at all.\n *\n * The parts are separate components rather than a template because a card's\n * header, content and footer have different padding on purpose: the header and\n * footer are tight, the content breathes.\n *\n * `size` is the card's padding rung, so a caller never sets `p-*` or `gap-*` on\n * a card to make it denser. `sm` tightens the card and its header, content and\n * footer together (the parts' inline padding follows the card, so they stay\n * aligned with each other); `flush` removes the block padding and gap for a\n * card whose body is a list of rows or a scroll area that runs edge to edge.\n */\nexport const cardVariants = cva(\n 'bg-card text-card-foreground flex flex-col rounded-xl border border-border shadow-xs',\n {\n variants: {\n size: {\n /** 16px block padding and gap; the parts carry 16px inline padding. */\n default: 'gap-4 py-4',\n /** 12px everywhere: a dense settings or status card. */\n sm: 'gap-3 py-3 *:data-[slot=card-content]:px-3 *:data-[slot=card-footer]:px-3 *:data-[slot=card-header]:px-3',\n /** No block padding or gap: the card hosts a row list or a scroller. */\n flush: 'gap-0 py-0',\n },\n },\n defaultVariants: { size: 'default' },\n }\n)\n\nexport type CardProps = ComponentProps<'div'> & VariantProps<typeof cardVariants>\n\nexport function Card(props: CardProps) {\n const [local, rest] = splitProps(props, ['class', 'size'])\n return (\n <div data-slot=\"card\" class={cn(cardVariants({ size: local.size }), local.class)} {...rest} />\n )\n}\n\n/**\n * The header is a grid so that `CardAction` has tracks to be placed in: the\n * action is a direct child that takes a second column spanning the title and\n * description rows. A flex column gives `col-start-2` nothing to land on and\n * the action would simply stack under the copy. The extra column exists only\n * for a direct-child action: a wrapper around it is then the header's single\n * grid item, so it keeps the full width. A header without an action lays out as\n * it always did (one full-width column, 4px between its children).\n */\nexport function CardHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-header\"\n class={cn(\n 'grid grid-cols-1 auto-rows-min gap-x-4 gap-y-1 px-4 has-[>[data-slot=card-action]]:grid-cols-[minmax(0,1fr)_auto]',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport function CardTitle(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-title\"\n class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardDescription(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-description\"\n class={cn('text-muted-foreground text-sm', local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardAction(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"card-action\"\n class={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', local.class)}\n {...rest}\n />\n )\n}\n\nexport function CardContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return <div data-slot=\"card-content\" class={cn('px-4', local.class)} {...rest} />\n}\n\nexport function CardFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return <div data-slot=\"card-footer\" class={cn('flex items-center px-4', local.class)} {...rest} />\n}\n"],"mappings":"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"}
@@ -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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"}
@@ -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',
@@ -83,13 +83,31 @@ export function Stat(props: StatProps) {
83
83
  )
84
84
  }
85
85
 
86
- /** A row of stats that shares one gap and wraps as the window narrows. */
87
- export function StatGroup(props: ComponentProps<'div'>) {
88
- const [local, rest] = splitProps(props, ['class'])
86
+ /**
87
+ * A row of stats that shares one gap and wraps as the window narrows.
88
+ *
89
+ * By default the column count steps with the *viewport* (two from `sm`, four from
90
+ * `lg`), which is right for a group that spans the page. A group inside a pane
91
+ * whose width is not the window's (a docked panel, a resizable column, a view
92
+ * beside a sidebar) gets the wrong count from a viewport breakpoint. `fit` sizes
93
+ * from the group's own width instead: as many tiles per row as fit at the
94
+ * minimum tile width, each growing to share the remainder, however many stats
95
+ * there are.
96
+ */
97
+ export function StatGroup(props: ComponentProps<'div'> & { fit?: boolean }) {
98
+ const [local, rest] = splitProps(props, ['class', 'fit'])
89
99
  return (
90
100
  <div
91
101
  data-slot="stat-group"
92
- class={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-4', local.class)}
102
+ class={cn(
103
+ 'grid gap-3',
104
+ // 9.375rem is 150px at the default root size: room for a label of about
105
+ // twenty characters and a six-digit value before the tile truncates.
106
+ local.fit
107
+ ? 'grid-cols-[repeat(auto-fill,minmax(9.375rem,1fr))]'
108
+ : 'sm:grid-cols-2 lg:grid-cols-4',
109
+ local.class
110
+ )}
93
111
  {...rest}
94
112
  />
95
113
  )
@@ -49,10 +49,26 @@ export function Card(props: CardProps) {
49
49
  )
50
50
  }
51
51
 
52
+ /**
53
+ * The header is a grid so that `CardAction` has tracks to be placed in: the
54
+ * action is a direct child that takes a second column spanning the title and
55
+ * description rows. A flex column gives `col-start-2` nothing to land on and
56
+ * the action would simply stack under the copy. The extra column exists only
57
+ * for a direct-child action: a wrapper around it is then the header's single
58
+ * grid item, so it keeps the full width. A header without an action lays out as
59
+ * it always did (one full-width column, 4px between its children).
60
+ */
52
61
  export function CardHeader(props: ComponentProps<'div'>) {
53
62
  const [local, rest] = splitProps(props, ['class'])
54
63
  return (
55
- <div data-slot="card-header" class={cn('flex flex-col gap-1 px-4', local.class)} {...rest} />
64
+ <div
65
+ data-slot="card-header"
66
+ class={cn(
67
+ 'grid grid-cols-1 auto-rows-min gap-x-4 gap-y-1 px-4 has-[>[data-slot=card-action]]:grid-cols-[minmax(0,1fr)_auto]',
68
+ local.class
69
+ )}
70
+ {...rest}
71
+ />
56
72
  )
57
73
  }
58
74
 
@@ -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.133.0",
3
+ "version": "0.135.1",
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",
@@ -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',
@@ -83,13 +83,31 @@ export function Stat(props: StatProps) {
83
83
  )
84
84
  }
85
85
 
86
- /** A row of stats that shares one gap and wraps as the window narrows. */
87
- export function StatGroup(props: ComponentProps<'div'>) {
88
- const [local, rest] = splitProps(props, ['class'])
86
+ /**
87
+ * A row of stats that shares one gap and wraps as the window narrows.
88
+ *
89
+ * By default the column count steps with the *viewport* (two from `sm`, four from
90
+ * `lg`), which is right for a group that spans the page. A group inside a pane
91
+ * whose width is not the window's (a docked panel, a resizable column, a view
92
+ * beside a sidebar) gets the wrong count from a viewport breakpoint. `fit` sizes
93
+ * from the group's own width instead: as many tiles per row as fit at the
94
+ * minimum tile width, each growing to share the remainder, however many stats
95
+ * there are.
96
+ */
97
+ export function StatGroup(props: ComponentProps<'div'> & { fit?: boolean }) {
98
+ const [local, rest] = splitProps(props, ['class', 'fit'])
89
99
  return (
90
100
  <div
91
101
  data-slot="stat-group"
92
- class={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-4', local.class)}
102
+ class={cn(
103
+ 'grid gap-3',
104
+ // 9.375rem is 150px at the default root size: room for a label of about
105
+ // twenty characters and a six-digit value before the tile truncates.
106
+ local.fit
107
+ ? 'grid-cols-[repeat(auto-fill,minmax(9.375rem,1fr))]'
108
+ : 'sm:grid-cols-2 lg:grid-cols-4',
109
+ local.class
110
+ )}
93
111
  {...rest}
94
112
  />
95
113
  )
@@ -49,10 +49,26 @@ export function Card(props: CardProps) {
49
49
  )
50
50
  }
51
51
 
52
+ /**
53
+ * The header is a grid so that `CardAction` has tracks to be placed in: the
54
+ * action is a direct child that takes a second column spanning the title and
55
+ * description rows. A flex column gives `col-start-2` nothing to land on and
56
+ * the action would simply stack under the copy. The extra column exists only
57
+ * for a direct-child action: a wrapper around it is then the header's single
58
+ * grid item, so it keeps the full width. A header without an action lays out as
59
+ * it always did (one full-width column, 4px between its children).
60
+ */
52
61
  export function CardHeader(props: ComponentProps<'div'>) {
53
62
  const [local, rest] = splitProps(props, ['class'])
54
63
  return (
55
- <div data-slot="card-header" class={cn('flex flex-col gap-1 px-4', local.class)} {...rest} />
64
+ <div
65
+ data-slot="card-header"
66
+ class={cn(
67
+ 'grid grid-cols-1 auto-rows-min gap-x-4 gap-y-1 px-4 has-[>[data-slot=card-action]]:grid-cols-[minmax(0,1fr)_auto]',
68
+ local.class
69
+ )}
70
+ {...rest}
71
+ />
56
72
  )
57
73
  }
58
74
 
@@ -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':