@adea-ai/ui 0.118.2 → 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/combobox/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox/combobox.js +1 -0
- package/dist/components/ui/combobox/combobox.js.map +1 -1
- package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
- package/dist/components/ui/context-menu/context-menu.js +2 -0
- package/dist/components/ui/context-menu/context-menu.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/components/ui/menubar/menubar.d.ts.map +1 -1
- package/dist/components/ui/menubar/menubar.js +2 -0
- package/dist/components/ui/menubar/menubar.js.map +1 -1
- package/dist/components/ui/select/select.d.ts.map +1 -1
- package/dist/components/ui/select/select.js +1 -0
- package/dist/components/ui/select/select.js.map +1 -1
- package/dist/lib/overlay.d.ts +6 -0
- package/dist/lib/overlay.d.ts.map +1 -1
- package/dist/lib/overlay.js +9 -2
- package/dist/lib/overlay.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/combobox/combobox.tsx +4 -1
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +8 -2
- package/dist/r/src/components/ui/item/index.ts +1 -0
- package/dist/r/src/components/ui/item/item.tsx +41 -5
- package/dist/r/src/components/ui/menubar/menubar.tsx +8 -2
- package/dist/r/src/components/ui/select/select.tsx +4 -1
- package/dist/r/src/lib/overlay.ts +9 -2
- 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/combobox/combobox.tsx +4 -1
- package/src/components/ui/context-menu/context-menu.tsx +8 -2
- package/src/components/ui/item/index.ts +1 -0
- package/src/components/ui/item/item.tsx +41 -5
- package/src/components/ui/menubar/menubar.tsx +8 -2
- package/src/components/ui/select/select.tsx +4 -1
- package/src/lib/overlay.ts +9 -2
package/dist/lib/overlay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.js","names":[],"sources":["../../src/lib/overlay.ts"],"sourcesContent":["/**\n * Shared strings for the overlay layer: dialogs, sheets, menus, popovers,\n * tooltips and the command palette.\n *\n * These live here rather than in each component because the overlay family is\n * where consistency is most visible and least likely to survive duplication.\n * A dialog, a sheet and a dropdown that disagree about their border, radius,\n * motion or shadow read as three different apps — and an app that adopts this\n * library would inherit all three. Declaring the surface once is what makes\n * \"an overlay looks like this\" a fact rather than a convention.\n */\n\nimport { cva, type VariantProps } from '#lib/variants'\n\n/** The panel itself: fill, hairline edge, radius, elevation. Menus, popovers\n * and the command palette float on the elevated overlay rung (`--popover`). */\nexport const overlaySurface =\n 'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg'\n\n/**\n * The modal panel: the same hairline edge, radius and elevation, painted on the\n * *neutral* theme surface instead of the elevated overlay rung.\n *\n * `--popover` is the catalogue's `surfaceElevated`, whose derivation amplifies\n * the canvas hue's chroma on the elevated step. On the dark default's violet\n * canvas that makes the dialog the most chromatic surface in the ladder —\n * `oklch(0.2406 0.0088 300.91)`, red above green in sRGB — and on a full dialog\n * panel that cast reads as a warm tint the canvas never has. shadcn's own\n * convention already splits the two: a Dialog paints `bg-background`, menus and\n * popovers paint `bg-popover`. The veil, the hairline and the elevation shadow\n * carry the separation the lighter fill no longer provides.\n */\nexport const dialogSurface =\n 'bg-background text-foreground rounded-xl border border-border shadow-lg'\n\n/**\n * Overlay titles have one fixed typography role. Keep this small recipe shared\n * without loading the configurable Heading component into every dialog.\n */\nexport const overlayTitle = 'font-semibold text-base tracking-tight leading-none'\n\n/** The span of the portal wrapper that centres a dialog-like overlay. */\nexport const overlayPositioner = 'fixed inset-0 z-(--z-dialog) grid place-items-center p-4'\n\n/** The dimming layer behind a modal overlay. */\nexport const overlayScrim =\n 'fixed inset-0 z-(--z-dialog) bg-scrim/50 supports-[backdrop-filter]:backdrop-blur-xs'\n\n/**\n * The tip: the card-toned bubble an explanation shows in. The side rail draws\n * it flush to a collapsed row and every floating tooltip composes the same\n * fill, edge, and type, so a control never explains itself in two visual\n * languages.\n */\nexport const tooltipTip =\n 'bg-card text-card-foreground rounded-md border border-border text-sm font-medium shadow-lg'\n\n/**\n * Entrance and exit motion, expressed as data-state classes.\n *\n * `data-expanded` / `data-closed` are Kobalte's; `data-side-*` come from its\n * popper positioning and let a menu slide in from the edge it is anchored to,\n * which is what makes a dropdown feel attached to its trigger rather than\n * dropped on the screen. Exit motion is shorter than entry, because a\n * dismissal should feel immediate while an arrival benefits from being seen.\n */\nexport const overlayMotion = [\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-200 data-closed:duration-150',\n].join(' ')\n\n/** Popover-style motion: no zoom, so a panel anchored to a control does not\n * appear to breathe away from it. */\nexport const popoverMotion = [\n 'data-expanded:animate-in data-expanded:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n 'data-expanded:duration-150 data-closed:duration-100',\n].join(' ')\n\n/** The popper arrow. Sized in the component; coloured here. */\nexport const popoverArrow = 'fill-popover stroke-border'\n\n/**\n * A menu row: the shared height, radius, hover fill and focus treatment for\n * every item inside a menu, listbox or command list.\n */\nexport const menuItem = [\n 'relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none',\n 'transition-colors ease-out',\n 'data-[highlighted]:bg-surface-hover data-[highlighted]:text-foreground',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0
|
|
1
|
+
{"version":3,"file":"overlay.js","names":[],"sources":["../../src/lib/overlay.ts"],"sourcesContent":["/**\n * Shared strings for the overlay layer: dialogs, sheets, menus, popovers,\n * tooltips and the command palette.\n *\n * These live here rather than in each component because the overlay family is\n * where consistency is most visible and least likely to survive duplication.\n * A dialog, a sheet and a dropdown that disagree about their border, radius,\n * motion or shadow read as three different apps — and an app that adopts this\n * library would inherit all three. Declaring the surface once is what makes\n * \"an overlay looks like this\" a fact rather than a convention.\n */\n\nimport { cva, type VariantProps } from '#lib/variants'\n\n/** The panel itself: fill, hairline edge, radius, elevation. Menus, popovers\n * and the command palette float on the elevated overlay rung (`--popover`). */\nexport const overlaySurface =\n 'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg'\n\n/**\n * The modal panel: the same hairline edge, radius and elevation, painted on the\n * *neutral* theme surface instead of the elevated overlay rung.\n *\n * `--popover` is the catalogue's `surfaceElevated`, whose derivation amplifies\n * the canvas hue's chroma on the elevated step. On the dark default's violet\n * canvas that makes the dialog the most chromatic surface in the ladder —\n * `oklch(0.2406 0.0088 300.91)`, red above green in sRGB — and on a full dialog\n * panel that cast reads as a warm tint the canvas never has. shadcn's own\n * convention already splits the two: a Dialog paints `bg-background`, menus and\n * popovers paint `bg-popover`. The veil, the hairline and the elevation shadow\n * carry the separation the lighter fill no longer provides.\n */\nexport const dialogSurface =\n 'bg-background text-foreground rounded-xl border border-border shadow-lg'\n\n/**\n * Overlay titles have one fixed typography role. Keep this small recipe shared\n * without loading the configurable Heading component into every dialog.\n */\nexport const overlayTitle = 'font-semibold text-base tracking-tight leading-none'\n\n/** The span of the portal wrapper that centres a dialog-like overlay. */\nexport const overlayPositioner = 'fixed inset-0 z-(--z-dialog) grid place-items-center p-4'\n\n/** The dimming layer behind a modal overlay. */\nexport const overlayScrim =\n 'fixed inset-0 z-(--z-dialog) bg-scrim/50 supports-[backdrop-filter]:backdrop-blur-xs'\n\n/**\n * The tip: the card-toned bubble an explanation shows in. The side rail draws\n * it flush to a collapsed row and every floating tooltip composes the same\n * fill, edge, and type, so a control never explains itself in two visual\n * languages.\n */\nexport const tooltipTip =\n 'bg-card text-card-foreground rounded-md border border-border text-sm font-medium shadow-lg'\n\n/**\n * Entrance and exit motion, expressed as data-state classes.\n *\n * `data-expanded` / `data-closed` are Kobalte's; `data-side-*` come from its\n * popper positioning and let a menu slide in from the edge it is anchored to,\n * which is what makes a dropdown feel attached to its trigger rather than\n * dropped on the screen. Exit motion is shorter than entry, because a\n * dismissal should feel immediate while an arrival benefits from being seen.\n */\nexport const overlayMotion = [\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-200 data-closed:duration-150',\n].join(' ')\n\n/** Popover-style motion: no zoom, so a panel anchored to a control does not\n * appear to breathe away from it. */\nexport const popoverMotion = [\n 'data-expanded:animate-in data-expanded:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n 'data-expanded:duration-150 data-closed:duration-100',\n].join(' ')\n\n/** The popper arrow. Sized in the component; coloured here. */\nexport const popoverArrow = 'fill-popover stroke-border'\n\n/**\n * A menu row: the shared height, radius, hover fill and focus treatment for\n * every item inside a menu, listbox or command list.\n *\n * The row sizes and dims the icons a caller puts in it, but not the glyph of\n * its selection indicator (any `[data-slot$=-indicator]`): a descendant `svg`\n * rule outranks a single utility on the glyph, so without the exclusion a\n * radio item's `size-2` dot drew as a 16px muted disc and every check lost the\n * indicator's accent.\n */\nexport const menuItem = [\n 'relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none',\n 'transition-colors ease-out',\n 'data-[highlighted]:bg-surface-hover data-[highlighted]:text-foreground',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',\n '[&_svg]:pointer-events-none [&_svg]:shrink-0',\n '[&_svg:not([data-slot$=-indicator]_*)]:size-4',\n '[&_svg:not([class*=text-]):not([data-slot$=-indicator]_*)]:text-muted-foreground',\n].join(' ')\n\n/** A destructive action keeps readable text and marks its row with a status edge. */\nexport const destructiveMenuItem =\n 'border-s-2 border-destructive ps-1.5 text-foreground data-[highlighted]:bg-destructive-subtle'\n\n/** The label above a group of menu rows. */\nexport const menuLabel =\n 'px-2 py-1.5 text-2xs font-medium tracking-wide text-muted-foreground uppercase'\n\n/** The rule between menu groups. */\nexport const menuSeparator = 'bg-border -mx-1 my-1 h-px'\n\n/**\n * A leading colour swatch on a menu or select row: the sanctioned way to carry\n * a card-tone colour into a list of options. The tones are the system's accent\n * and status roles — the vocabulary a Badge already speaks — so the colour an\n * option shows in a menu is the colour its badge carries on a card, and a\n * caller never passes a raw colour into a list. The dot is `aria-hidden`; the\n * label carries the meaning, so a tone is never the only signal.\n */\nexport const menuSwatchTone = cva('size-2 shrink-0 rounded-full', {\n variants: {\n tone: {\n neutral: 'bg-muted-foreground',\n primary: 'bg-primary',\n success: 'bg-success',\n warning: 'bg-warning',\n destructive: 'bg-destructive',\n info: 'bg-info',\n },\n },\n defaultVariants: { tone: 'neutral' },\n})\n\n/** The tones a menu row's swatch can take. */\nexport type MenuSwatchTone = NonNullable<VariantProps<typeof menuSwatchTone>['tone']>\n\n/** Padding shared by every menu-list surface. */\nexport const menuContentPadding = 'p-1'\n\n/**\n * Marks a floating layer — a menu, a listbox, a popover — as part of the top\n * layer.\n *\n * A modal dialog hides everything outside itself from assistive technology\n * (`aria-hidden` on its siblings) and keeps watching `<body>` for new nodes.\n * A menu or select opened from inside the dialog portals to `<body>`, so the\n * dialog hid it too: the list was on screen, focused, and invisible to a\n * screen reader, and an outside-click check could treat a click on it as a\n * click away from the dialog. Kobalte exempts nodes carrying this attribute\n * from both, so every floating layer carries it.\n */\nexport const topLayerProps = { 'data-kb-top-layer': '' } as const\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,IAAa,iBACX;;;;;;;;;;;;;;AAeF,IAAa,gBACX;;;;;AAMF,IAAa,eAAe;;AAG5B,IAAa,oBAAoB;;AAGjC,IAAa,eACX;;;;;;;AAQF,IAAa,aACX;;;;;;;;;;AAWF,IAAa,gBAAgB;CAC3B;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;;;AAIV,IAAa,gBAAgB,CAC3B,mGACA,qDACF,CAAC,CAAC,KAAK,GAAG;;AAGV,IAAa,eAAe;;;;;;;;;;;AAY5B,IAAa,WAAW;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;;AAGV,IAAa,sBACX;;AAGF,IAAa,YACX;;AAGF,IAAa,gBAAgB;;;;;;;;;AAU7B,IAAa,iBAAiB,IAAI,gCAAgC;CAChE,UAAU,EACR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT,aAAa;EACb,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CAAC;;;;;;;;;;;;;AAoBD,IAAa,gBAAgB,EAAE,qBAAqB,GAAG"}
|
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>
|
|
@@ -135,7 +135,10 @@ export function ComboboxItem(props: ComponentProps<typeof KobalteCombobox.Item>)
|
|
|
135
135
|
<KobalteCombobox.ItemLabel>{local.children}</KobalteCombobox.ItemLabel>
|
|
136
136
|
{/* The indicator reads in the accent, like every other selected state in
|
|
137
137
|
the system. */}
|
|
138
|
-
<KobalteCombobox.ItemIndicator
|
|
138
|
+
<KobalteCombobox.ItemIndicator
|
|
139
|
+
data-slot="combobox-indicator"
|
|
140
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
141
|
+
>
|
|
139
142
|
<Check class="size-4" />
|
|
140
143
|
</KobalteCombobox.ItemIndicator>
|
|
141
144
|
</KobalteCombobox.Item>
|
|
@@ -111,7 +111,10 @@ export function ContextMenuCheckboxItem(
|
|
|
111
111
|
{local.children}
|
|
112
112
|
{/* The indicator reads in the accent, like every other selected state in
|
|
113
113
|
the system. */}
|
|
114
|
-
<KobalteContextMenu.ItemIndicator
|
|
114
|
+
<KobalteContextMenu.ItemIndicator
|
|
115
|
+
data-slot="context-menu-indicator"
|
|
116
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
117
|
+
>
|
|
115
118
|
<Check class="size-4" />
|
|
116
119
|
</KobalteContextMenu.ItemIndicator>
|
|
117
120
|
</KobalteContextMenu.CheckboxItem>
|
|
@@ -130,7 +133,10 @@ export function ContextMenuRadioItem(props: ComponentProps<typeof KobalteContext
|
|
|
130
133
|
{local.children}
|
|
131
134
|
{/* The indicator reads in the accent, like every other selected state in
|
|
132
135
|
the system. */}
|
|
133
|
-
<KobalteContextMenu.ItemIndicator
|
|
136
|
+
<KobalteContextMenu.ItemIndicator
|
|
137
|
+
data-slot="context-menu-indicator"
|
|
138
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
139
|
+
>
|
|
134
140
|
<Circle class="size-2 fill-current" />
|
|
135
141
|
</KobalteContextMenu.ItemIndicator>
|
|
136
142
|
</KobalteContextMenu.RadioItem>
|
|
@@ -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
|
)
|
|
@@ -141,7 +141,10 @@ export function MenubarCheckboxItem(props: ComponentProps<typeof KobalteMenubar.
|
|
|
141
141
|
{local.children}
|
|
142
142
|
{/* The indicator reads in the accent, like every other selected state in
|
|
143
143
|
the system. */}
|
|
144
|
-
<KobalteMenubar.ItemIndicator
|
|
144
|
+
<KobalteMenubar.ItemIndicator
|
|
145
|
+
data-slot="menubar-indicator"
|
|
146
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
147
|
+
>
|
|
145
148
|
<Check class="size-4" />
|
|
146
149
|
</KobalteMenubar.ItemIndicator>
|
|
147
150
|
</KobalteMenubar.CheckboxItem>
|
|
@@ -159,7 +162,10 @@ export function MenubarRadioItem(props: ComponentProps<typeof KobalteMenubar.Rad
|
|
|
159
162
|
{local.children}
|
|
160
163
|
{/* The indicator reads in the accent, like every other selected state in
|
|
161
164
|
the system. */}
|
|
162
|
-
<KobalteMenubar.ItemIndicator
|
|
165
|
+
<KobalteMenubar.ItemIndicator
|
|
166
|
+
data-slot="menubar-indicator"
|
|
167
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
168
|
+
>
|
|
163
169
|
<Circle class="size-2 fill-current" />
|
|
164
170
|
</KobalteMenubar.ItemIndicator>
|
|
165
171
|
</KobalteMenubar.RadioItem>
|
|
@@ -192,7 +192,10 @@ export function SelectItem(
|
|
|
192
192
|
<KobalteSelect.ItemLabel>{local.children}</KobalteSelect.ItemLabel>
|
|
193
193
|
{/* The indicator reads in the accent, like every other selected state in
|
|
194
194
|
the system — the row's own foreground reads as decoration. */}
|
|
195
|
-
<KobalteSelect.ItemIndicator
|
|
195
|
+
<KobalteSelect.ItemIndicator
|
|
196
|
+
data-slot="select-indicator"
|
|
197
|
+
class="absolute end-2 flex size-4 items-center justify-center text-primary"
|
|
198
|
+
>
|
|
196
199
|
<Check class="size-4" />
|
|
197
200
|
</KobalteSelect.ItemIndicator>
|
|
198
201
|
</KobalteSelect.Item>
|
|
@@ -83,14 +83,21 @@ export const popoverArrow = 'fill-popover stroke-border'
|
|
|
83
83
|
/**
|
|
84
84
|
* A menu row: the shared height, radius, hover fill and focus treatment for
|
|
85
85
|
* every item inside a menu, listbox or command list.
|
|
86
|
+
*
|
|
87
|
+
* The row sizes and dims the icons a caller puts in it, but not the glyph of
|
|
88
|
+
* its selection indicator (any `[data-slot$=-indicator]`): a descendant `svg`
|
|
89
|
+
* rule outranks a single utility on the glyph, so without the exclusion a
|
|
90
|
+
* radio item's `size-2` dot drew as a 16px muted disc and every check lost the
|
|
91
|
+
* indicator's accent.
|
|
86
92
|
*/
|
|
87
93
|
export const menuItem = [
|
|
88
94
|
'relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none',
|
|
89
95
|
'transition-colors ease-out',
|
|
90
96
|
'data-[highlighted]:bg-surface-hover data-[highlighted]:text-foreground',
|
|
91
97
|
'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',
|
|
92
|
-
'[&_svg]:pointer-events-none [&_svg]:shrink-0
|
|
93
|
-
'[&_svg:not([
|
|
98
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0',
|
|
99
|
+
'[&_svg:not([data-slot$=-indicator]_*)]:size-4',
|
|
100
|
+
'[&_svg:not([class*=text-]):not([data-slot$=-indicator]_*)]:text-muted-foreground',
|
|
94
101
|
].join(' ')
|
|
95
102
|
|
|
96
103
|
/** A destructive action keeps readable text and marks its row with a status edge. */
|
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,
|