@adea-ai/ui 0.118.3 → 0.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/components/composites/settings/index.d.ts +1 -1
- package/dist/components/composites/settings/index.d.ts.map +1 -1
- package/dist/components/composites/settings/settings-layout.d.ts +18 -2
- package/dist/components/composites/settings/settings-layout.d.ts.map +1 -1
- package/dist/components/composites/settings/settings-layout.js +40 -5
- package/dist/components/composites/settings/settings-layout.js.map +1 -1
- package/dist/components/composites/settings/settings-navigation.d.ts.map +1 -1
- package/dist/components/composites/settings/settings-navigation.js +36 -14
- package/dist/components/composites/settings/settings-navigation.js.map +1 -1
- package/dist/components/ui/code-block/code-block.d.ts +13 -0
- package/dist/components/ui/code-block/code-block.d.ts.map +1 -1
- package/dist/components/ui/code-block/code-block.js +22 -3
- package/dist/components/ui/code-block/code-block.js.map +1 -1
- package/dist/components/ui/item/index.d.ts +1 -1
- package/dist/components/ui/item/index.d.ts.map +1 -1
- package/dist/components/ui/item/item.d.ts +17 -1
- package/dist/components/ui/item/item.d.ts.map +1 -1
- package/dist/components/ui/item/item.js +17 -10
- package/dist/components/ui/item/item.js.map +1 -1
- package/dist/r/registry.json +3 -1
- package/dist/r/settings.json +3 -1
- package/dist/r/src/components/composites/settings/index.ts +5 -1
- package/dist/r/src/components/composites/settings/settings-layout.tsx +48 -5
- package/dist/r/src/components/composites/settings/settings-navigation.tsx +44 -16
- package/dist/r/src/components/ui/code-block/code-block.tsx +27 -2
- package/dist/r/src/components/ui/item/index.ts +1 -0
- package/dist/r/src/components/ui/item/item.tsx +41 -5
- package/package.json +1 -1
- package/registry.json +3 -1
- package/src/components/composites/settings/index.ts +5 -1
- package/src/components/composites/settings/settings-layout.tsx +48 -5
- package/src/components/composites/settings/settings-navigation.tsx +44 -16
- package/src/components/ui/code-block/code-block.tsx +27 -2
- package/src/components/ui/item/index.ts +1 -0
- package/src/components/ui/item/item.tsx +41 -5
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { cva } from "../../../lib/variants.js";
|
|
3
|
-
import { createComponent, effect, insert, mergeProps, setAttribute, spread, template } from "solid-js/web";
|
|
3
|
+
import { className, createComponent, effect, insert, mergeProps, setAttribute, spread, template } from "solid-js/web";
|
|
4
4
|
import { Show, splitProps } from "solid-js";
|
|
5
5
|
//#region src/components/ui/item/item.tsx
|
|
6
6
|
var _tmpl$ = /*#__PURE__*/ template(`<span data-slot=item-media class="flex size-8 shrink-0 items-center justify-center [&_svg]:size-4">`);
|
|
7
|
-
var _tmpl$2 = /*#__PURE__*/ template(`<span data-slot=item-trailing
|
|
7
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<span data-slot=item-trailing>`);
|
|
8
8
|
var _tmpl$3 = /*#__PURE__*/ template(`<div data-slot=item><div data-slot=item-body class="flex min-w-0 flex-1 flex-col gap-0.5">`);
|
|
9
9
|
var _tmpl$4 = /*#__PURE__*/ template(`<div data-slot=item-title>`);
|
|
10
10
|
var _tmpl$5 = /*#__PURE__*/ template(`<div data-slot=item-description>`);
|
|
@@ -56,16 +56,22 @@ function Item(props) {
|
|
|
56
56
|
"size",
|
|
57
57
|
"media",
|
|
58
58
|
"trailing",
|
|
59
|
+
"stackTrailing",
|
|
59
60
|
"children"
|
|
60
61
|
]);
|
|
61
62
|
return (() => {
|
|
62
63
|
var _el$ = _tmpl$3(), _el$3 = _el$.firstChild;
|
|
63
|
-
spread(_el$, mergeProps({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
spread(_el$, mergeProps({
|
|
65
|
+
get ["data-stack-trailing"]() {
|
|
66
|
+
return local.stackTrailing ? "" : void 0;
|
|
67
|
+
},
|
|
68
|
+
get ["class"]() {
|
|
69
|
+
return cn(itemVariants({
|
|
70
|
+
variant: local.variant,
|
|
71
|
+
size: local.size
|
|
72
|
+
}), { "@container/item flex-wrap": local.stackTrailing }, local.class);
|
|
73
|
+
}
|
|
74
|
+
}, rest), false, true);
|
|
69
75
|
insert(_el$, createComponent(Show, {
|
|
70
76
|
get when() {
|
|
71
77
|
return local.media;
|
|
@@ -84,6 +90,7 @@ function Item(props) {
|
|
|
84
90
|
get children() {
|
|
85
91
|
var _el$4 = _tmpl$2();
|
|
86
92
|
insert(_el$4, () => local.trailing);
|
|
93
|
+
effect(() => className(_el$4, cn("flex shrink-0 items-center gap-2", { "@max-md/item:w-full @max-md/item:flex-wrap @max-md/item:justify-end": local.stackTrailing })));
|
|
87
94
|
return _el$4;
|
|
88
95
|
}
|
|
89
96
|
}), null);
|
|
@@ -101,11 +108,11 @@ function ItemTitle(props) {
|
|
|
101
108
|
})();
|
|
102
109
|
}
|
|
103
110
|
function ItemDescription(props) {
|
|
104
|
-
const [local, rest] = splitProps(props, ["class"]);
|
|
111
|
+
const [local, rest] = splitProps(props, ["class", "clamp"]);
|
|
105
112
|
return (() => {
|
|
106
113
|
var _el$6 = _tmpl$5();
|
|
107
114
|
spread(_el$6, mergeProps({ get ["class"]() {
|
|
108
|
-
return cn("text-muted-foreground
|
|
115
|
+
return cn("text-muted-foreground text-xs text-pretty", { "line-clamp-2": local.clamp !== false }, local.class);
|
|
109
116
|
} }, rest), false, false);
|
|
110
117
|
return _el$6;
|
|
111
118
|
})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item.js","names":["ComponentProps","JSX","Show","splitProps","cva","VariantProps","cn","itemVariants","variants","variant","default","outline","muted","size","sm","md","lg","defaultVariants","ItemVariantProps","ItemProps","media","Element","trailing","Item","props","local","rest","_el$","_tmpl$3","_el$3","firstChild","_$spread","_$mergeProps","class","_$insert","_$createComponent","when","children","_el$2","_tmpl$","_el$4","_tmpl$2","ItemTitle","_el$5","_tmpl$4","ItemDescription","_el$6","_tmpl$5","ItemGroup","label","listLabel","_el$7","_tmpl$7","_el$9","_el$8","_tmpl$6","_$
|
|
1
|
+
{"version":3,"file":"item.js","names":["ComponentProps","JSX","Show","splitProps","cva","VariantProps","cn","itemVariants","variants","variant","default","outline","muted","size","sm","md","lg","defaultVariants","ItemVariantProps","ItemProps","media","Element","trailing","stackTrailing","Item","props","local","rest","_el$","_tmpl$3","_el$3","firstChild","_$spread","_$mergeProps","data-stack-trailing","undefined","class","_$insert","_$createComponent","when","children","_el$2","_tmpl$","_el$4","_tmpl$2","_$effect","_$className","ItemTitle","_el$5","_tmpl$4","ItemDescriptionProps","clamp","ItemDescription","_el$6","_tmpl$5","ItemGroup","label","listLabel","_el$7","_tmpl$7","_el$9","_el$8","_tmpl$6","_$setAttribute","ItemGroupEntry","role","ItemEmpty","_el$0","_tmpl$8","_$mergeProps","_$createComponent","_$className","_$setAttribute"],"sources":["../../../../src/components/ui/item/item.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\n\n/**\n * Item.\n *\n * The general row: a leading slot, a body, and a trailing slot. It is the shape\n * almost every list in an application takes — a file, a member, a setting, a\n * search result — and it exists as a component because a row is where inconsistency\n * is most visible: twenty of them make any drift in height, padding or alignment\n * obvious at a glance.\n *\n * The difference from `ListRow` is what it is *for*. `ListRow` is one row in a list\n * of like things, sized by the row ladder and meant to be scanned down a column.\n * `Item` is a self-contained block — it has a border, its own surface, an optional\n * title and description — and is meant to stand alone or sit in a grid. A list uses\n * `ListRow`; a search result or a settings card uses `Item`.\n *\n * `variant=\"outline\"` and `\"muted\"` exist because the two questions a caller\n * actually has are \"does this need an edge\" and \"is it on the canvas or inset\".\n */\nexport const itemVariants = cva('flex w-full items-center gap-3 rounded-lg text-sm', {\n variants: {\n variant: {\n default: 'bg-transparent',\n /** On the canvas, with an edge — the search-result shape. */\n outline: 'border border-border bg-card',\n /** Inset: a code well, a disabled row, a quoted message. */\n muted: 'bg-muted',\n },\n size: {\n sm: 'min-h-row-sm px-3 py-1.5',\n md: 'min-h-row-md px-3 py-2',\n lg: 'min-h-row-lg px-4 py-2.5',\n },\n },\n defaultVariants: { variant: 'default', size: 'md' },\n})\n\ntype ItemVariantProps = VariantProps<typeof itemVariants>\n\nexport type ItemProps = ComponentProps<'div'> &\n ItemVariantProps & {\n /** A media slot: an icon, an avatar, a thumbnail. */\n media?: JSX.Element\n /** A trailing slot: a badge, a timestamp, a menu trigger. */\n trailing?: JSX.Element\n /**\n * Move the trailing slot onto its own line, end-aligned under the body, once\n * the item is narrower than 28rem — the width `ListRow` stacks its trailing\n * controls at. Off by default: a row of badges and an icon button fits beside\n * the body, but a pair of labelled buttons squeezes a phone-width body to a\n * few characters.\n */\n stackTrailing?: boolean\n }\n\nexport function Item(props: ItemProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'variant',\n 'size',\n 'media',\n 'trailing',\n 'stackTrailing',\n 'children',\n ])\n\n return (\n <div\n data-slot=\"item\"\n data-stack-trailing={local.stackTrailing ? '' : undefined}\n class={cn(\n itemVariants({ variant: local.variant, size: local.size }),\n // The item is its own query container: the trailing slot answers to the\n // width the item was given, not the viewport, so an item in a narrow\n // column of a wide window stacks too.\n { '@container/item flex-wrap': local.stackTrailing },\n local.class\n )}\n {...rest}\n >\n <Show when={local.media}>\n <span\n data-slot=\"item-media\"\n class=\"flex size-8 shrink-0 items-center justify-center [&_svg]:size-4\"\n >\n {local.media}\n </span>\n </Show>\n <div data-slot=\"item-body\" class=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n {local.children}\n </div>\n <Show when={local.trailing}>\n <span\n data-slot=\"item-trailing\"\n class={cn('flex shrink-0 items-center gap-2', {\n '@max-md/item:w-full @max-md/item:flex-wrap @max-md/item:justify-end':\n local.stackTrailing,\n })}\n >\n {local.trailing}\n </span>\n </Show>\n </div>\n )\n}\n\nexport function ItemTitle(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return <div data-slot=\"item-title\" class={cn('truncate font-medium', local.class)} {...rest} />\n}\n\nexport type ItemDescriptionProps = ComponentProps<'div'> & {\n /**\n * Clamp to two lines. On by default, so a grid of items keeps one height; pass\n * `false` where the description is the content — a setting's explanation, an\n * error the reader has to read in full.\n */\n clamp?: boolean\n}\n\nexport function ItemDescription(props: ItemDescriptionProps) {\n const [local, rest] = splitProps(props, ['class', 'clamp'])\n return (\n <div\n data-slot=\"item-description\"\n class={cn(\n 'text-muted-foreground text-xs text-pretty',\n { 'line-clamp-2': local.clamp !== false },\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/**\n * A group of items with an optional heading. It is a real `role=\"list\"` with\n * `role=\"listitem\"` children, so a screen reader reports the count — which is how a\n * user knows how many results there are without counting them.\n */\nexport function ItemGroup(\n props: ComponentProps<'div'> & {\n label?: string\n /** The list's accessible name. */ listLabel?: string\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'label', 'listLabel', 'children'])\n\n return (\n <div data-slot=\"item-group\" class={cn('flex flex-col gap-2', local.class)} {...rest}>\n <Show when={local.label}>\n <div class=\"text-muted-foreground text-2xs font-medium tracking-wide uppercase\">\n {local.label}\n </div>\n </Show>\n <div role=\"list\" aria-label={local.listLabel ?? local.label} class=\"flex flex-col gap-2\">\n {local.children}\n </div>\n </div>\n )\n}\n\n/** An item inside an `ItemGroup`, which is what makes it a list item. */\nexport function ItemGroupEntry(props: ItemProps) {\n return <Item role=\"listitem\" {...props} />\n}\n\n/** A centred message for a group with nothing in it. */\nexport function ItemEmpty(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"item-empty\"\n class={cn('text-muted-foreground py-6 text-center text-sm', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":"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"}
|
package/dist/r/registry.json
CHANGED
|
@@ -2186,7 +2186,9 @@
|
|
|
2186
2186
|
"type": "registry:ui",
|
|
2187
2187
|
"title": "Settings",
|
|
2188
2188
|
"description": "",
|
|
2189
|
-
"dependencies": [
|
|
2189
|
+
"dependencies": [
|
|
2190
|
+
"@kobalte/core@^0.13.14"
|
|
2191
|
+
],
|
|
2190
2192
|
"registryDependencies": [
|
|
2191
2193
|
"@adea-ai/ui/sidebar-nav",
|
|
2192
2194
|
"@adea-ai/ui/tabs",
|
package/dist/r/settings.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { SettingsField, SettingsPage, SettingsRow, SettingsSection } from './settings'
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
SettingsLayout,
|
|
4
|
+
type SettingsLayoutProps,
|
|
5
|
+
type SettingsNavigationLayout,
|
|
6
|
+
} from './settings-layout'
|
|
3
7
|
export {
|
|
4
8
|
SettingsNavigation,
|
|
5
9
|
type SettingsNavigationGroup,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentProps, JSX } from 'solid-js'
|
|
2
|
-
import { splitProps } from 'solid-js'
|
|
2
|
+
import { createSignal, onCleanup, onMount, splitProps } from 'solid-js'
|
|
3
3
|
import { cn } from '../../../lib/utils'
|
|
4
4
|
import { Tabs } from '../../ui/tabs'
|
|
5
5
|
import { SettingsNavigation, type SettingsNavigationGroup } from './settings-navigation'
|
|
@@ -10,7 +10,7 @@ export type SettingsLayoutProps = Omit<
|
|
|
10
10
|
> & {
|
|
11
11
|
/** Grouped navigation entries; the host owns their values and panel content. */
|
|
12
12
|
groups: readonly SettingsNavigationGroup[]
|
|
13
|
-
/** Accessible name for the
|
|
13
|
+
/** Accessible name for the tab list. */
|
|
14
14
|
'aria-label': string
|
|
15
15
|
/** The host-controlled selected destination. */
|
|
16
16
|
value: string
|
|
@@ -20,16 +20,42 @@ export type SettingsLayoutProps = Omit<
|
|
|
20
20
|
onReselect?: (value: string) => void
|
|
21
21
|
/** Reveal the selected row on value changes and window resizes. Defaults to nearest scroll. */
|
|
22
22
|
revealSelected?: boolean | ((trigger: HTMLButtonElement | undefined) => void)
|
|
23
|
+
/**
|
|
24
|
+
* How the section navigation sits beside the panels. `auto` (the default) is
|
|
25
|
+
* the vertical rail from 48rem of viewport width up and a horizontal,
|
|
26
|
+
* scrollable strip of tabs above the panels below it. `rail` and `strip` pin
|
|
27
|
+
* one shape at every width.
|
|
28
|
+
*/
|
|
29
|
+
navigationLayout?: SettingsNavigationLayout
|
|
23
30
|
/** Panels must be rendered as `TabsContent` descendants. */
|
|
24
31
|
children?: JSX.Element
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
export type SettingsNavigationLayout = 'auto' | 'rail' | 'strip'
|
|
35
|
+
|
|
36
|
+
/** The `md` breakpoint the rail has always widened at; below it the rail becomes a strip. */
|
|
37
|
+
const RAIL_MEDIA_QUERY = '(min-width: 48rem)'
|
|
38
|
+
|
|
39
|
+
function railQuery(): MediaQueryList | undefined {
|
|
40
|
+
return typeof window === 'undefined' || typeof window.matchMedia !== 'function'
|
|
41
|
+
? undefined
|
|
42
|
+
: window.matchMedia(RAIL_MEDIA_QUERY)
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
/**
|
|
28
46
|
* SettingsLayout.
|
|
29
47
|
*
|
|
30
48
|
* A controlled, vertical settings rail beside its scrollable panel viewport.
|
|
31
49
|
* The host keeps ownership of selection, URL/hash state and panel content.
|
|
32
|
-
*
|
|
50
|
+
*
|
|
51
|
+
* Below 48rem the rail becomes a horizontal strip of tabs above the panels.
|
|
52
|
+
* A 6rem rail at phone width clips every label, so this is the default rather
|
|
53
|
+
* than an opt-in. It changes the tab root's orientation, not just its
|
|
54
|
+
* styling: the list's `aria-orientation` and Kobalte's arrow keys (Up/Down on
|
|
55
|
+
* the rail, Left/Right on the strip) follow the axis the reader sees. The
|
|
56
|
+
* breakpoint is the viewport's, like CatalogBrowser's and ContextualSidebar's,
|
|
57
|
+
* and is read before the first render so a phone never paints the rail. A
|
|
58
|
+
* server render, which has no viewport, renders the rail.
|
|
33
59
|
*/
|
|
34
60
|
export function SettingsLayout(props: SettingsLayoutProps) {
|
|
35
61
|
const [local, rest] = splitProps(props, [
|
|
@@ -40,12 +66,29 @@ export function SettingsLayout(props: SettingsLayoutProps) {
|
|
|
40
66
|
'value',
|
|
41
67
|
'onReselect',
|
|
42
68
|
'revealSelected',
|
|
69
|
+
'navigationLayout',
|
|
43
70
|
])
|
|
44
71
|
|
|
72
|
+
const [wide, setWide] = createSignal(railQuery()?.matches ?? true)
|
|
73
|
+
onMount(() => {
|
|
74
|
+
const query = railQuery()
|
|
75
|
+
if (!query) return
|
|
76
|
+
const update = () => setWide(query.matches)
|
|
77
|
+
update()
|
|
78
|
+
query.addEventListener('change', update)
|
|
79
|
+
onCleanup(() => query.removeEventListener('change', update))
|
|
80
|
+
})
|
|
81
|
+
const layout = (): 'rail' | 'strip' => {
|
|
82
|
+
const requested = local.navigationLayout ?? 'auto'
|
|
83
|
+
if (requested !== 'auto') return requested
|
|
84
|
+
return wide() ? 'rail' : 'strip'
|
|
85
|
+
}
|
|
86
|
+
|
|
45
87
|
return (
|
|
46
88
|
<Tabs
|
|
47
89
|
{...rest}
|
|
48
|
-
orientation=
|
|
90
|
+
orientation={layout() === 'rail' ? 'vertical' : 'horizontal'}
|
|
91
|
+
data-navigation-layout={layout()}
|
|
49
92
|
value={local.value}
|
|
50
93
|
class={cn('min-h-0 min-w-0 flex-1 gap-0', local.class)}
|
|
51
94
|
>
|
|
@@ -55,7 +98,7 @@ export function SettingsLayout(props: SettingsLayoutProps) {
|
|
|
55
98
|
value={local.value}
|
|
56
99
|
onReselect={local.onReselect}
|
|
57
100
|
revealSelected={local.revealSelected}
|
|
58
|
-
class=
|
|
101
|
+
class={layout() === 'rail' ? 'w-24 md:w-sidebar' : undefined}
|
|
59
102
|
/>
|
|
60
103
|
<div data-slot="settings-layout-panels" class="min-h-0 min-w-0 flex-1 overflow-y-auto p-4">
|
|
61
104
|
{local.children}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { useTabsContext } from '@kobalte/core/tabs'
|
|
1
2
|
import type { JSX } from 'solid-js'
|
|
2
|
-
import { createEffect, createUniqueId, onCleanup, splitProps } from 'solid-js'
|
|
3
|
+
import { createEffect, createUniqueId, onCleanup, Show, splitProps } from 'solid-js'
|
|
3
4
|
import { cn } from '../../../lib/utils'
|
|
4
5
|
import { sidebarNavItemClass, sidebarNavItemStateClass } from '../../layout/sidebar-nav/sidebar-nav'
|
|
5
6
|
import { TabsList, TabsTrigger, type TabsListProps } from '../../ui/tabs'
|
|
@@ -47,6 +48,14 @@ export type SettingsNavigationProps = Omit<
|
|
|
47
48
|
* the controlled value, panel content, icons and any persistence or routing.
|
|
48
49
|
* Use ordinary tabs for a short, ungrouped row of view choices.
|
|
49
50
|
*
|
|
51
|
+
* The shape follows the enclosing `Tabs` orientation rather than a prop of its
|
|
52
|
+
* own, so the visual axis can never disagree with the arrow keys Kobalte
|
|
53
|
+
* binds: `vertical` is the grouped rail, `horizontal` is a single scrollable
|
|
54
|
+
* strip of tabs (what `SettingsLayout` shows below 48rem). The strip keeps the
|
|
55
|
+
* groups in order, separated by a rule; their visible headings drop out, since
|
|
56
|
+
* an overline between tabs reads as another tab, but each tab still carries its
|
|
57
|
+
* group as its accessible description.
|
|
58
|
+
*
|
|
50
59
|
* Kobalte owns roving focus, arrow keys, selection and tab/panel ARIA. This
|
|
51
60
|
* component adds no URL, storage or host-specific state. Set
|
|
52
61
|
* `revealSelected={false}` when the surrounding layout does not use an internal
|
|
@@ -88,12 +97,16 @@ export function SettingsNavigation(props: SettingsNavigationProps) {
|
|
|
88
97
|
'onReselect',
|
|
89
98
|
'revealSelected',
|
|
90
99
|
])
|
|
100
|
+
const tabs = useTabsContext()
|
|
101
|
+
const strip = () => tabs.orientation() === 'horizontal'
|
|
91
102
|
const triggers = new Map<string, HTMLButtonElement>()
|
|
92
103
|
const groupIdPrefix = `settings-navigation-${createUniqueId()}`
|
|
93
104
|
|
|
94
105
|
createEffect(() => {
|
|
95
106
|
const value = local.value
|
|
96
107
|
const reveal = local.revealSelected
|
|
108
|
+
// Tracked so a rail that becomes a strip (or back) re-reveals on the new axis.
|
|
109
|
+
strip()
|
|
97
110
|
if (reveal === false) return
|
|
98
111
|
|
|
99
112
|
const revealCurrent = () => {
|
|
@@ -111,9 +124,17 @@ export function SettingsNavigation(props: SettingsNavigationProps) {
|
|
|
111
124
|
<TabsList
|
|
112
125
|
data-slot="settings-navigation"
|
|
113
126
|
aria-label={local['aria-label']}
|
|
114
|
-
orientation=
|
|
127
|
+
orientation={tabs.orientation()}
|
|
115
128
|
class={cn(
|
|
116
|
-
|
|
129
|
+
// Positioned so the visually hidden group names are laid out (and
|
|
130
|
+
// clipped) inside the list: unpositioned, their absolute boxes escape a
|
|
131
|
+
// scrolled strip and widen the page by however far it has scrolled.
|
|
132
|
+
'relative flex min-w-0 shrink-0 justify-start',
|
|
133
|
+
strip()
|
|
134
|
+
? // The block padding is the room the focus ring needs: a scroller
|
|
135
|
+
// clips at its padding edge, and this one scrolls sideways only.
|
|
136
|
+
'scroll-fade w-full flex-row items-center gap-1 overflow-x-auto overflow-y-hidden border-b border-border px-2 py-1.5'
|
|
137
|
+
: 'max-h-full min-h-0 flex-col items-stretch gap-2 overflow-x-hidden overflow-y-auto px-2 py-2',
|
|
117
138
|
local.class
|
|
118
139
|
)}
|
|
119
140
|
{...rest}
|
|
@@ -124,24 +145,30 @@ export function SettingsNavigation(props: SettingsNavigationProps) {
|
|
|
124
145
|
<div
|
|
125
146
|
role="none"
|
|
126
147
|
data-slot="settings-navigation-group"
|
|
127
|
-
class=
|
|
148
|
+
class={cn(
|
|
149
|
+
'flex gap-0.5',
|
|
150
|
+
strip()
|
|
151
|
+
? 'shrink-0 flex-row items-center not-first:ms-1 not-first:border-s not-first:border-border not-first:ps-2'
|
|
152
|
+
: 'min-w-0 flex-col'
|
|
153
|
+
)}
|
|
128
154
|
>
|
|
129
155
|
<span id={groupId} class="sr-only">
|
|
130
156
|
{group.label}
|
|
131
157
|
</span>
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
158
|
+
<Show when={!strip()}>
|
|
159
|
+
{/* The overline role on the sidebar's own muted foreground. */}
|
|
160
|
+
<Text
|
|
161
|
+
variant="overline"
|
|
162
|
+
tone="inherit"
|
|
163
|
+
aria-hidden="true"
|
|
164
|
+
class="px-2 py-1.5 text-sidebar-muted-foreground"
|
|
165
|
+
>
|
|
166
|
+
{group.label}
|
|
167
|
+
</Text>
|
|
168
|
+
</Show>
|
|
141
169
|
{group.items.map((item) => (
|
|
142
170
|
<TabsTrigger
|
|
143
171
|
data-slot="settings-navigation-trigger"
|
|
144
|
-
orientation="vertical"
|
|
145
172
|
aria-describedby={groupId}
|
|
146
173
|
value={item.value}
|
|
147
174
|
disabled={item.disabled}
|
|
@@ -155,11 +182,12 @@ export function SettingsNavigation(props: SettingsNavigationProps) {
|
|
|
155
182
|
class={cn(
|
|
156
183
|
sidebarNavItemClass,
|
|
157
184
|
sidebarNavItemStateClass.idle,
|
|
158
|
-
'h-row-lg
|
|
185
|
+
'h-row-lg justify-start border-0 px-3 py-2 text-left data-[selected]:bg-primary-subtle data-[selected]:text-foreground',
|
|
186
|
+
strip() ? 'w-auto shrink-0' : 'w-full'
|
|
159
187
|
)}
|
|
160
188
|
>
|
|
161
189
|
{item.icon}
|
|
162
|
-
<span class=
|
|
190
|
+
<span class={cn('min-w-0', { truncate: !strip() })}>{item.label}</span>
|
|
163
191
|
</TabsTrigger>
|
|
164
192
|
))}
|
|
165
193
|
</div>
|
|
@@ -69,6 +69,19 @@ export type CodeBlockProps = Omit<ComponentProps<'div'>, 'children'> & {
|
|
|
69
69
|
showLineNumbers?: boolean
|
|
70
70
|
/** The height above which the action row is repeated below the block. */
|
|
71
71
|
tallThreshold?: number
|
|
72
|
+
/**
|
|
73
|
+
* Cap the code body's height and scroll it vertically past that. A number is
|
|
74
|
+
* pixels; a string is any CSS length (`'20rem'`, `'50vh'`). Unset, the block
|
|
75
|
+
* grows with its content.
|
|
76
|
+
*/
|
|
77
|
+
maxHeight?: number | string
|
|
78
|
+
/**
|
|
79
|
+
* Soft-wrap long lines inside the block instead of scrolling sideways. Off by
|
|
80
|
+
* default: wrapped code loses its column alignment, so it is for prose-like
|
|
81
|
+
* output (logs, long commands) in a narrow column. Wraps the plain renderer and
|
|
82
|
+
* any `pre` inside `highlight` output; line numbers stay on the first visual line.
|
|
83
|
+
*/
|
|
84
|
+
wrap?: boolean
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
/** The height a block must exceed before its actions are repeated. */
|
|
@@ -103,6 +116,8 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
103
116
|
'lineHeight',
|
|
104
117
|
'showLineNumbers',
|
|
105
118
|
'tallThreshold',
|
|
119
|
+
'maxHeight',
|
|
120
|
+
'wrap',
|
|
106
121
|
'class',
|
|
107
122
|
'ref',
|
|
108
123
|
])
|
|
@@ -114,6 +129,8 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
114
129
|
const label = () => local.title ?? local.language ?? 'code'
|
|
115
130
|
const regionLabel = () => (local.language ? `${local.language} code` : 'code')
|
|
116
131
|
const lines = createMemo(() => local.code.replace(/\n$/, '').split('\n'))
|
|
132
|
+
const maxHeight = () =>
|
|
133
|
+
typeof local.maxHeight === 'number' ? `${local.maxHeight}px` : local.maxHeight
|
|
117
134
|
|
|
118
135
|
const copy = async () => {
|
|
119
136
|
const ok = await copyText(local.code)
|
|
@@ -160,7 +177,15 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
160
177
|
<Actions />
|
|
161
178
|
</div>
|
|
162
179
|
<div
|
|
163
|
-
|
|
180
|
+
data-slot="code-block-body"
|
|
181
|
+
data-wrap={local.wrap ? '' : undefined}
|
|
182
|
+
class={cn('scroll-fade overflow-x-auto', {
|
|
183
|
+
// A capped body scrolls on both axes; the region is already a tab stop,
|
|
184
|
+
// so the keyboard reaches the vertical overflow the same way.
|
|
185
|
+
'overflow-y-auto': local.maxHeight !== undefined,
|
|
186
|
+
'[&_pre]:whitespace-pre-wrap [&_pre]:[overflow-wrap:anywhere]': local.wrap,
|
|
187
|
+
})}
|
|
188
|
+
style={maxHeight() === undefined ? undefined : { 'max-height': maxHeight() }}
|
|
164
189
|
// A region that scrolls must be focusable, and a tab stop needs a name.
|
|
165
190
|
tabindex="0"
|
|
166
191
|
role="region"
|
|
@@ -181,7 +206,7 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
181
206
|
>
|
|
182
207
|
{index() + 1}
|
|
183
208
|
</span>
|
|
184
|
-
<span>{line || ' '}</span>
|
|
209
|
+
<span class={cn({ 'min-w-0': local.wrap })}>{line || ' '}</span>
|
|
185
210
|
</span>
|
|
186
211
|
)}
|
|
187
212
|
</For>
|
|
@@ -47,6 +47,14 @@ export type ItemProps = ComponentProps<'div'> &
|
|
|
47
47
|
media?: JSX.Element
|
|
48
48
|
/** A trailing slot: a badge, a timestamp, a menu trigger. */
|
|
49
49
|
trailing?: JSX.Element
|
|
50
|
+
/**
|
|
51
|
+
* Move the trailing slot onto its own line, end-aligned under the body, once
|
|
52
|
+
* the item is narrower than 28rem — the width `ListRow` stacks its trailing
|
|
53
|
+
* controls at. Off by default: a row of badges and an icon button fits beside
|
|
54
|
+
* the body, but a pair of labelled buttons squeezes a phone-width body to a
|
|
55
|
+
* few characters.
|
|
56
|
+
*/
|
|
57
|
+
stackTrailing?: boolean
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
export function Item(props: ItemProps) {
|
|
@@ -56,13 +64,22 @@ export function Item(props: ItemProps) {
|
|
|
56
64
|
'size',
|
|
57
65
|
'media',
|
|
58
66
|
'trailing',
|
|
67
|
+
'stackTrailing',
|
|
59
68
|
'children',
|
|
60
69
|
])
|
|
61
70
|
|
|
62
71
|
return (
|
|
63
72
|
<div
|
|
64
73
|
data-slot="item"
|
|
65
|
-
|
|
74
|
+
data-stack-trailing={local.stackTrailing ? '' : undefined}
|
|
75
|
+
class={cn(
|
|
76
|
+
itemVariants({ variant: local.variant, size: local.size }),
|
|
77
|
+
// The item is its own query container: the trailing slot answers to the
|
|
78
|
+
// width the item was given, not the viewport, so an item in a narrow
|
|
79
|
+
// column of a wide window stacks too.
|
|
80
|
+
{ '@container/item flex-wrap': local.stackTrailing },
|
|
81
|
+
local.class
|
|
82
|
+
)}
|
|
66
83
|
{...rest}
|
|
67
84
|
>
|
|
68
85
|
<Show when={local.media}>
|
|
@@ -77,7 +94,13 @@ export function Item(props: ItemProps) {
|
|
|
77
94
|
{local.children}
|
|
78
95
|
</div>
|
|
79
96
|
<Show when={local.trailing}>
|
|
80
|
-
<span
|
|
97
|
+
<span
|
|
98
|
+
data-slot="item-trailing"
|
|
99
|
+
class={cn('flex shrink-0 items-center gap-2', {
|
|
100
|
+
'@max-md/item:w-full @max-md/item:flex-wrap @max-md/item:justify-end':
|
|
101
|
+
local.stackTrailing,
|
|
102
|
+
})}
|
|
103
|
+
>
|
|
81
104
|
{local.trailing}
|
|
82
105
|
</span>
|
|
83
106
|
</Show>
|
|
@@ -90,12 +113,25 @@ export function ItemTitle(props: ComponentProps<'div'>) {
|
|
|
90
113
|
return <div data-slot="item-title" class={cn('truncate font-medium', local.class)} {...rest} />
|
|
91
114
|
}
|
|
92
115
|
|
|
93
|
-
export
|
|
94
|
-
|
|
116
|
+
export type ItemDescriptionProps = ComponentProps<'div'> & {
|
|
117
|
+
/**
|
|
118
|
+
* Clamp to two lines. On by default, so a grid of items keeps one height; pass
|
|
119
|
+
* `false` where the description is the content — a setting's explanation, an
|
|
120
|
+
* error the reader has to read in full.
|
|
121
|
+
*/
|
|
122
|
+
clamp?: boolean
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function ItemDescription(props: ItemDescriptionProps) {
|
|
126
|
+
const [local, rest] = splitProps(props, ['class', 'clamp'])
|
|
95
127
|
return (
|
|
96
128
|
<div
|
|
97
129
|
data-slot="item-description"
|
|
98
|
-
class={cn(
|
|
130
|
+
class={cn(
|
|
131
|
+
'text-muted-foreground text-xs text-pretty',
|
|
132
|
+
{ 'line-clamp-2': local.clamp !== false },
|
|
133
|
+
local.class
|
|
134
|
+
)}
|
|
99
135
|
{...rest}
|
|
100
136
|
/>
|
|
101
137
|
)
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -2186,7 +2186,9 @@
|
|
|
2186
2186
|
"type": "registry:ui",
|
|
2187
2187
|
"title": "Settings",
|
|
2188
2188
|
"description": "",
|
|
2189
|
-
"dependencies": [
|
|
2189
|
+
"dependencies": [
|
|
2190
|
+
"@kobalte/core@^0.13.14"
|
|
2191
|
+
],
|
|
2190
2192
|
"registryDependencies": [
|
|
2191
2193
|
"@adea-ai/ui/sidebar-nav",
|
|
2192
2194
|
"@adea-ai/ui/tabs",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { SettingsField, SettingsPage, SettingsRow, SettingsSection } from './settings'
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
SettingsLayout,
|
|
4
|
+
type SettingsLayoutProps,
|
|
5
|
+
type SettingsNavigationLayout,
|
|
6
|
+
} from './settings-layout'
|
|
3
7
|
export {
|
|
4
8
|
SettingsNavigation,
|
|
5
9
|
type SettingsNavigationGroup,
|