@adea-ai/ui 0.106.2 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/composites/appearance-editor/appearance-editor.d.ts +9 -3
- package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-editor.js +61 -29
- package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-types.d.ts +2 -0
- package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/index.d.ts +1 -1
- package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/index.js +2 -2
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +55 -0
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -0
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +199 -0
- package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -0
- package/dist/components/layout/contextual-sidebar/index.d.ts +3 -0
- package/dist/components/layout/contextual-sidebar/index.d.ts.map +1 -0
- package/dist/components/layout/contextual-sidebar/index.js +3 -0
- package/dist/components/layout/pixel-resize-handle/index.d.ts +2 -0
- package/dist/components/layout/pixel-resize-handle/index.d.ts.map +1 -0
- package/dist/components/layout/pixel-resize-handle/index.js +2 -0
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +29 -0
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -0
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +183 -0
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts +3 -19
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js +8 -76
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js.map +1 -1
- package/dist/components/ui/badge/badge.d.ts.map +1 -1
- package/dist/components/ui/badge/badge.js +9 -5
- package/dist/components/ui/badge/badge.js.map +1 -1
- package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox/combobox.js +2 -2
- 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 +3 -3
- package/dist/components/ui/context-menu/context-menu.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -4
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
- package/dist/components/ui/menubar/menubar.js +3 -3
- package/dist/components/ui/menubar/menubar.js.map +1 -1
- package/dist/components/ui/popover/popover.d.ts.map +1 -1
- package/dist/components/ui/popover/popover.js +2 -2
- package/dist/components/ui/popover/popover.js.map +1 -1
- package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
- package/dist/components/ui/resizable/resizable.js +2 -2
- package/dist/components/ui/resizable/resizable.js.map +1 -1
- package/dist/components/ui/select/select.d.ts.map +1 -1
- package/dist/components/ui/select/select.js +2 -2
- package/dist/components/ui/select/select.js.map +1 -1
- package/dist/components/ui/sheet/sheet.d.ts +16 -0
- package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet/sheet.js +82 -16
- package/dist/components/ui/sheet/sheet.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/overlay.d.ts +15 -0
- package/dist/lib/overlay.d.ts.map +1 -1
- package/dist/lib/overlay.js +14 -1
- package/dist/lib/overlay.js.map +1 -1
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint.js +154 -20
- package/dist/lint.js.map +1 -1
- package/dist/r/appearance-editor.json +1 -1
- package/dist/r/contextual-sidebar.json +25 -0
- package/dist/r/pixel-resize-handle.json +23 -0
- package/dist/r/registry.json +50 -2
- package/dist/r/sidebar-nav.json +1 -1
- package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
- package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +2 -0
- package/dist/r/src/components/composites/appearance-editor/index.ts +1 -1
- package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
- package/dist/r/src/components/layout/contextual-sidebar/index.ts +6 -0
- package/dist/r/src/components/layout/pixel-resize-handle/index.ts +1 -0
- package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
- package/dist/r/src/components/ui/badge/badge.tsx +9 -5
- package/dist/r/src/components/ui/combobox/combobox.tsx +2 -1
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +3 -0
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
- package/dist/r/src/components/ui/menubar/menubar.tsx +3 -0
- package/dist/r/src/components/ui/popover/popover.tsx +8 -1
- package/dist/r/src/components/ui/resizable/resizable.tsx +4 -2
- package/dist/r/src/components/ui/select/select.tsx +2 -1
- package/dist/r/src/components/ui/sheet/sheet.tsx +78 -15
- package/dist/r/src/lib/overlay.ts +14 -0
- package/dist/r/src/styles/base.css +13 -0
- package/package.json +3 -1
- package/registry.json +50 -2
- package/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
- package/src/components/composites/appearance-editor/appearance-types.ts +2 -0
- package/src/components/composites/appearance-editor/index.ts +1 -1
- package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
- package/src/components/layout/contextual-sidebar/index.ts +6 -0
- package/src/components/layout/pixel-resize-handle/index.ts +1 -0
- package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
- package/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
- package/src/components/ui/badge/badge.tsx +9 -5
- package/src/components/ui/combobox/combobox.tsx +2 -1
- package/src/components/ui/context-menu/context-menu.tsx +3 -0
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
- package/src/components/ui/menubar/menubar.tsx +3 -0
- package/src/components/ui/popover/popover.tsx +8 -1
- package/src/components/ui/resizable/resizable.tsx +4 -2
- package/src/components/ui/select/select.tsx +2 -1
- package/src/components/ui/sheet/sheet.tsx +78 -15
- package/src/index.ts +1 -0
- package/src/lib/overlay.ts +14 -0
- package/src/lint/index.ts +234 -22
- package/src/styles/base.css +13 -0
|
@@ -24,31 +24,79 @@ import { headingVariants } from '../typography'
|
|
|
24
24
|
* `end`, and a full-width row for `top` and `bottom`. The slide-in direction
|
|
25
25
|
* follows from the side, so a caller never pairs them by hand.
|
|
26
26
|
*
|
|
27
|
+
* `variant` decides how it meets that edge. `inset` — the default for `end` —
|
|
28
|
+
* docks the panel inside the main view: below the top bar, one equal gap from
|
|
29
|
+
* the top bar, the end edge and the bottom, with no scrim, so the work it edits
|
|
30
|
+
* stays visible and the top bar stays reachable. It is the shape for editing one
|
|
31
|
+
* thing beside its context (appearance settings, a task, a record). `edge` runs
|
|
32
|
+
* edge to edge over a scrim — the shape of a navigation drawer, and the default
|
|
33
|
+
* for every other side.
|
|
34
|
+
*
|
|
35
|
+
* Its parts read alike everywhere: `SheetHeader` holds a heading, a description
|
|
36
|
+
* and a full-width rule beneath, with the close button in the corner;
|
|
37
|
+
* `SheetBody` scrolls on its own; `SheetFooter` is a full-width tinted band that
|
|
38
|
+
* holds the decision.
|
|
39
|
+
*
|
|
27
40
|
* Controlled sheets opened from outside a `SheetTrigger` restore focus to the
|
|
28
41
|
* element focused before opening. Pass `restoreFocusRef` when a stable external
|
|
29
42
|
* opener must be used instead, such as when another layer closes before the
|
|
30
43
|
* sheet opens.
|
|
31
44
|
*/
|
|
45
|
+
// `overflow-clip`, not `overflow-hidden`: a hidden overflow can still be
|
|
46
|
+
// scrolled programmatically (focus, scrollIntoView, scrollTop), which slid the
|
|
47
|
+
// pinned footer out of view. Only SheetBody scrolls.
|
|
32
48
|
const sheetVariants = cva(
|
|
33
|
-
'bg-popover text-popover-foreground fixed z-(--z-dialog) flex flex-col
|
|
49
|
+
'bg-popover text-popover-foreground fixed z-(--z-dialog) flex flex-col overflow-clip border-border shadow-lg',
|
|
34
50
|
{
|
|
35
51
|
variants: {
|
|
36
52
|
side: {
|
|
37
|
-
top: '
|
|
38
|
-
bottom:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
top: 'data-expanded:slide-in-from-top data-closed:slide-out-to-top',
|
|
54
|
+
bottom: 'data-expanded:slide-in-from-bottom data-closed:slide-out-to-bottom',
|
|
55
|
+
start: 'data-expanded:slide-in-from-left data-closed:slide-out-to-left',
|
|
56
|
+
end: 'data-expanded:slide-in-from-right data-closed:slide-out-to-right',
|
|
57
|
+
},
|
|
58
|
+
variant: {
|
|
59
|
+
edge: '',
|
|
60
|
+
inset: 'panel-dialog-inset w-lg rounded-xl border',
|
|
43
61
|
},
|
|
44
62
|
},
|
|
45
|
-
|
|
63
|
+
compoundVariants: [
|
|
64
|
+
{
|
|
65
|
+
side: 'top',
|
|
66
|
+
variant: 'edge',
|
|
67
|
+
class: 'inset-x-0 top-0 h-auto max-h-[85vh] rounded-b-xl border-b',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
side: 'bottom',
|
|
71
|
+
variant: 'edge',
|
|
72
|
+
class: 'inset-x-0 bottom-0 h-auto max-h-[85vh] rounded-t-xl border-t',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
side: 'start',
|
|
76
|
+
variant: 'edge',
|
|
77
|
+
class: 'inset-y-0 start-0 h-full w-80 max-w-[85vw] rounded-e-xl border-e',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
side: 'end',
|
|
81
|
+
variant: 'edge',
|
|
82
|
+
class: 'inset-y-0 end-0 h-full w-80 max-w-[85vw] rounded-s-xl border-s',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
defaultVariants: { side: 'end', variant: 'edge' },
|
|
46
86
|
}
|
|
47
87
|
)
|
|
48
88
|
|
|
89
|
+
/** `inset` for the end edge, `edge` for every other side, unless the caller says. */
|
|
90
|
+
const sheetVariantFor = (
|
|
91
|
+
side: SheetContentProps['side'],
|
|
92
|
+
variant: SheetContentProps['variant']
|
|
93
|
+
): 'edge' | 'inset' => variant ?? ((side ?? 'end') === 'end' ? 'inset' : 'edge')
|
|
94
|
+
|
|
49
95
|
export type SheetContentProps = ComponentProps<typeof KobalteDialogContent> &
|
|
50
96
|
VariantProps<typeof sheetVariants> & {
|
|
51
97
|
closeButton?: JSX.Element | false
|
|
98
|
+
/** The close button's accessible name. Defaults to "Close". */
|
|
99
|
+
closeLabel?: string
|
|
52
100
|
/** Supplies the stable external element to focus when the controlled sheet closes. */
|
|
53
101
|
restoreFocusRef?: Accessor<HTMLElement | undefined>
|
|
54
102
|
}
|
|
@@ -57,8 +105,10 @@ export function SheetContent(props: SheetContentProps) {
|
|
|
57
105
|
const [local, rest] = splitProps(props, [
|
|
58
106
|
'class',
|
|
59
107
|
'side',
|
|
108
|
+
'variant',
|
|
60
109
|
'children',
|
|
61
110
|
'closeButton',
|
|
111
|
+
'closeLabel',
|
|
62
112
|
'restoreFocusRef',
|
|
63
113
|
'onOpenAutoFocus',
|
|
64
114
|
'onCloseAutoFocus',
|
|
@@ -71,12 +121,19 @@ export function SheetContent(props: SheetContentProps) {
|
|
|
71
121
|
onCloseAutoFocus: local.onCloseAutoFocus,
|
|
72
122
|
})
|
|
73
123
|
|
|
124
|
+
const variant = () => sheetVariantFor(local.side, local.variant)
|
|
125
|
+
|
|
74
126
|
return (
|
|
75
127
|
<KobalteDialog.Portal>
|
|
76
|
-
|
|
128
|
+
{/* An inset panel is beside the work, so the work stays in view: no scrim.
|
|
129
|
+
It is still modal — focus stays inside and an outside click dismisses. */}
|
|
130
|
+
<Show when={variant() === 'edge'}>
|
|
131
|
+
<DialogOverlay />
|
|
132
|
+
</Show>
|
|
77
133
|
<KobalteDialogContent
|
|
134
|
+
data-variant={variant()}
|
|
78
135
|
class={cn(
|
|
79
|
-
sheetVariants({ side: local.side }),
|
|
136
|
+
sheetVariants({ side: local.side, variant: variant() }),
|
|
80
137
|
'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:duration-200',
|
|
81
138
|
'data-closed:animate-out data-closed:fade-out-0 data-closed:duration-150',
|
|
82
139
|
local.class
|
|
@@ -92,7 +149,7 @@ export function SheetContent(props: SheetContentProps) {
|
|
|
92
149
|
WebKit walks only explicit tabindex and form controls) would skip
|
|
93
150
|
an implicitly-tabbable button and jump past the sheet's end. */}
|
|
94
151
|
<KobalteDialog.CloseButton
|
|
95
|
-
aria-label=
|
|
152
|
+
aria-label={local.closeLabel ?? 'Close'}
|
|
96
153
|
tabindex="0"
|
|
97
154
|
class="absolute top-3.5 end-3.5 rounded-md p-1 text-muted-foreground transition-colors ease-out outline-none hover:bg-surface-hover hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
|
|
98
155
|
>
|
|
@@ -122,7 +179,10 @@ export function SheetHeader(props: ComponentProps<'div'>) {
|
|
|
122
179
|
return (
|
|
123
180
|
<div
|
|
124
181
|
data-slot="sheet-header"
|
|
125
|
-
class={cn(
|
|
182
|
+
class={cn(
|
|
183
|
+
'flex shrink-0 flex-col gap-1.5 border-b border-border p-4 pe-12 text-start',
|
|
184
|
+
local.class
|
|
185
|
+
)}
|
|
126
186
|
{...rest}
|
|
127
187
|
/>
|
|
128
188
|
)
|
|
@@ -133,7 +193,7 @@ export function SheetBody(props: ComponentProps<'div'>) {
|
|
|
133
193
|
return (
|
|
134
194
|
<div
|
|
135
195
|
data-slot="sheet-body"
|
|
136
|
-
class={cn('flex-1 overflow-y-auto
|
|
196
|
+
class={cn('min-h-0 flex-1 overflow-y-auto p-4', local.class)}
|
|
137
197
|
{...rest}
|
|
138
198
|
/>
|
|
139
199
|
)
|
|
@@ -144,7 +204,10 @@ export function SheetFooter(props: ComponentProps<'div'>) {
|
|
|
144
204
|
return (
|
|
145
205
|
<div
|
|
146
206
|
data-slot="sheet-footer"
|
|
147
|
-
class={cn(
|
|
207
|
+
class={cn(
|
|
208
|
+
'flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-border bg-background px-4 py-3',
|
|
209
|
+
local.class
|
|
210
|
+
)}
|
|
148
211
|
{...rest}
|
|
149
212
|
/>
|
|
150
213
|
)
|
|
@@ -154,7 +217,7 @@ export function SheetTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
|
|
|
154
217
|
const [local, rest] = splitProps(props, ['class'])
|
|
155
218
|
return (
|
|
156
219
|
<KobalteDialog.Title
|
|
157
|
-
class={cn(headingVariants({ size: '
|
|
220
|
+
class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
|
|
158
221
|
{...rest}
|
|
159
222
|
/>
|
|
160
223
|
)
|
|
@@ -81,3 +81,17 @@ export const menuSeparator = 'bg-border -mx-1 my-1 h-px'
|
|
|
81
81
|
|
|
82
82
|
/** Padding shared by every menu-list surface. */
|
|
83
83
|
export const menuContentPadding = 'p-1'
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Marks a floating layer — a menu, a listbox, a popover — as part of the top
|
|
87
|
+
* layer.
|
|
88
|
+
*
|
|
89
|
+
* A modal dialog hides everything outside itself from assistive technology
|
|
90
|
+
* (`aria-hidden` on its siblings) and keeps watching `<body>` for new nodes.
|
|
91
|
+
* A menu or select opened from inside the dialog portals to `<body>`, so the
|
|
92
|
+
* dialog hid it too: the list was on screen, focused, and invisible to a
|
|
93
|
+
* screen reader, and an outside-click check could treat a click on it as a
|
|
94
|
+
* click away from the dialog. Kobalte exempts nodes carrying this attribute
|
|
95
|
+
* from both, so every floating layer carries it.
|
|
96
|
+
*/
|
|
97
|
+
export const topLayerProps = { 'data-kb-top-layer': '' } as const
|
|
@@ -277,6 +277,19 @@
|
|
|
277
277
|
text-orientation: mixed;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* The docking box of an inset Sheet: below the top bar, with one gap (two spacing
|
|
282
|
+
* steps) from the top bar, the end edge and the bottom of the window, so the
|
|
283
|
+
* panel fills the height of the main view and its edges line up on every side.
|
|
284
|
+
* Narrow windows keep the same gap at the start edge too.
|
|
285
|
+
*/
|
|
286
|
+
@utility panel-dialog-inset {
|
|
287
|
+
top: calc(var(--topbar-height) + var(--spacing) * 2);
|
|
288
|
+
inset-inline-end: calc(var(--spacing) * 2);
|
|
289
|
+
bottom: calc(var(--spacing) * 2);
|
|
290
|
+
max-width: calc(100vw - var(--spacing) * 4);
|
|
291
|
+
}
|
|
292
|
+
|
|
280
293
|
@utility no-scrollbar {
|
|
281
294
|
-ms-overflow-style: none;
|
|
282
295
|
scrollbar-width: none;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adea-ai/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.108.0",
|
|
4
4
|
"description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adea",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"src",
|
|
25
25
|
"!src/**/*.stories.tsx",
|
|
26
26
|
"!src/**/*.stories.ts",
|
|
27
|
+
"!src/**/*.stories.css",
|
|
27
28
|
"!src/**/*.mdx",
|
|
28
29
|
"!src/**/*.test.ts",
|
|
29
30
|
"!src/**/*.test.tsx",
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
"theme:check": "bun scripts/build-default-theme.ts --check",
|
|
113
114
|
"design-system:build": "bun scripts/design-system/build.ts",
|
|
114
115
|
"check:packed-layout": "node --test scripts/check-packed-layout-process.test.mjs && node scripts/check-packed-layout.mjs && node scripts/check-packed-layout-tree.mjs",
|
|
116
|
+
"check:packed-lint": "node scripts/check-packed-lint.mjs",
|
|
115
117
|
"check:packed-consumer": "bun scripts/check-packed-consumer.ts",
|
|
116
118
|
"check:packed-list-row": "bun scripts/check-packed-list-row.ts",
|
|
117
119
|
"check:packed-scene-controls": "bun scripts/check-packed-scene-controls.ts",
|
package/registry.json
CHANGED
|
@@ -1555,6 +1555,31 @@
|
|
|
1555
1555
|
}
|
|
1556
1556
|
]
|
|
1557
1557
|
},
|
|
1558
|
+
{
|
|
1559
|
+
"name": "contextual-sidebar",
|
|
1560
|
+
"type": "registry:ui",
|
|
1561
|
+
"title": "Contextual Sidebar",
|
|
1562
|
+
"description": "",
|
|
1563
|
+
"dependencies": [],
|
|
1564
|
+
"registryDependencies": [
|
|
1565
|
+
"@adea-ai/ui/pixel-resize-handle",
|
|
1566
|
+
"@adea-ai/ui/sheet",
|
|
1567
|
+
"@adea-ai/ui/sidebar-nav",
|
|
1568
|
+
"lib"
|
|
1569
|
+
],
|
|
1570
|
+
"files": [
|
|
1571
|
+
{
|
|
1572
|
+
"path": "src/components/layout/contextual-sidebar/contextual-sidebar.tsx",
|
|
1573
|
+
"type": "registry:ui",
|
|
1574
|
+
"target": "components/layout/contextual-sidebar/contextual-sidebar.tsx"
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
"path": "src/components/layout/contextual-sidebar/index.ts",
|
|
1578
|
+
"type": "registry:ui",
|
|
1579
|
+
"target": "components/layout/contextual-sidebar/index.ts"
|
|
1580
|
+
}
|
|
1581
|
+
]
|
|
1582
|
+
},
|
|
1558
1583
|
{
|
|
1559
1584
|
"name": "floating-preview",
|
|
1560
1585
|
"type": "registry:ui",
|
|
@@ -1652,6 +1677,29 @@
|
|
|
1652
1677
|
}
|
|
1653
1678
|
]
|
|
1654
1679
|
},
|
|
1680
|
+
{
|
|
1681
|
+
"name": "pixel-resize-handle",
|
|
1682
|
+
"type": "registry:ui",
|
|
1683
|
+
"title": "Pixel Resize Handle",
|
|
1684
|
+
"description": "",
|
|
1685
|
+
"dependencies": [],
|
|
1686
|
+
"registryDependencies": [
|
|
1687
|
+
"@adea-ai/ui/resizable",
|
|
1688
|
+
"lib"
|
|
1689
|
+
],
|
|
1690
|
+
"files": [
|
|
1691
|
+
{
|
|
1692
|
+
"path": "src/components/layout/pixel-resize-handle/index.ts",
|
|
1693
|
+
"type": "registry:ui",
|
|
1694
|
+
"target": "components/layout/pixel-resize-handle/index.ts"
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
"path": "src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx",
|
|
1698
|
+
"type": "registry:ui",
|
|
1699
|
+
"target": "components/layout/pixel-resize-handle/pixel-resize-handle.tsx"
|
|
1700
|
+
}
|
|
1701
|
+
]
|
|
1702
|
+
},
|
|
1655
1703
|
{
|
|
1656
1704
|
"name": "side-rail",
|
|
1657
1705
|
"type": "registry:ui",
|
|
@@ -1687,11 +1735,11 @@
|
|
|
1687
1735
|
"title": "Sidebar Nav",
|
|
1688
1736
|
"description": "",
|
|
1689
1737
|
"dependencies": [
|
|
1690
|
-
"@corvu/resizable@^0.2.5",
|
|
1691
1738
|
"@kobalte/core@^0.13.14",
|
|
1692
1739
|
"lucide-solid@^1.51.0"
|
|
1693
1740
|
],
|
|
1694
1741
|
"registryDependencies": [
|
|
1742
|
+
"@adea-ai/ui/pixel-resize-handle",
|
|
1695
1743
|
"lib"
|
|
1696
1744
|
],
|
|
1697
1745
|
"files": [
|
|
@@ -1918,7 +1966,7 @@
|
|
|
1918
1966
|
"@adea-ai/ui/button",
|
|
1919
1967
|
"@adea-ai/ui/dropdown-menu",
|
|
1920
1968
|
"@adea-ai/ui/input",
|
|
1921
|
-
"@adea-ai/ui/
|
|
1969
|
+
"@adea-ai/ui/sheet",
|
|
1922
1970
|
"@adea-ai/ui/switch",
|
|
1923
1971
|
"@adea-ai/ui/theme",
|
|
1924
1972
|
"lib"
|
|
@@ -41,12 +41,15 @@ import { DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS } from '#lib/appearance-font-se
|
|
|
41
41
|
|
|
42
42
|
import { Switch } from '../../ui/switch/switch'
|
|
43
43
|
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
Sheet,
|
|
45
|
+
SheetBody,
|
|
46
|
+
SheetContent,
|
|
47
|
+
SheetDescription,
|
|
48
|
+
SheetFooter,
|
|
49
|
+
SheetHeader,
|
|
50
|
+
SheetTitle,
|
|
51
|
+
SheetTrigger,
|
|
52
|
+
} from '../../ui/sheet/sheet'
|
|
50
53
|
import type { AppearanceEditorProps, AppearancePopoverProps } from './appearance-types'
|
|
51
54
|
import { ModeChoices, AccentChoices, GlassChoices, isCustomAccent } from './appearance-choices'
|
|
52
55
|
import { SettingsRow, TerminalRow, ThemeRow } from './appearance-rows'
|
|
@@ -61,7 +64,6 @@ import { SettingsRow, TerminalRow, ThemeRow } from './appearance-rows'
|
|
|
61
64
|
*/
|
|
62
65
|
export function AppearanceEditor(props: AppearanceEditorProps) {
|
|
63
66
|
const errorId = createUniqueId()
|
|
64
|
-
const saveReasonId = createUniqueId()
|
|
65
67
|
const custom = () => isCustomAccent(props)
|
|
66
68
|
const accentDescription = () => {
|
|
67
69
|
if (props.draft.accent === 'theme') return "Theme default · Uses the palette's intended color."
|
|
@@ -188,15 +190,35 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
|
|
|
188
190
|
</Button>
|
|
189
191
|
</SettingsRow>
|
|
190
192
|
</div>
|
|
193
|
+
<Show when={!props.hideActions}>
|
|
194
|
+
{/* Sticky so Save/Cancel stay reachable in a tall scroll container: the
|
|
195
|
+
editor scrolls with its host panel, and actions parked at the bottom
|
|
196
|
+
of a long form were unreachable without scrolling past every row. */}
|
|
197
|
+
<div class="sticky bottom-0 z-10 mt-2 border-t bg-background px-4 py-3">
|
|
198
|
+
<AppearanceEditorActions {...props} />
|
|
199
|
+
</div>
|
|
200
|
+
</Show>
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Reset, Cancel and Save, with the reason Save is unavailable. Rendered by the
|
|
207
|
+
* editor itself, or — with `hideActions` — by a host that pins them in its own
|
|
208
|
+
* footer, as `AppearancePopover` does.
|
|
209
|
+
*/
|
|
210
|
+
export function AppearanceEditorActions(props: AppearanceEditorProps) {
|
|
211
|
+
const saveReasonId = createUniqueId()
|
|
212
|
+
return (
|
|
213
|
+
<div class="flex w-full flex-col gap-2">
|
|
191
214
|
<Show when={props.saveDisabledReason}>
|
|
192
|
-
<p id={saveReasonId} role="status" class="
|
|
215
|
+
<p id={saveReasonId} role="status" class="text-xs text-muted-foreground">
|
|
193
216
|
{props.saveDisabledReason}
|
|
194
217
|
</p>
|
|
195
218
|
</Show>
|
|
196
|
-
{/*
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<div class="global-appearance-editor-actions sticky bottom-0 z-10 mt-2 flex flex-wrap items-center gap-2 border-t bg-background px-4 py-3">
|
|
219
|
+
{/* `global-appearance-editor-actions` lets these buttons grow with the UI
|
|
220
|
+
font preference, like the rest of Appearance's own controls. */}
|
|
221
|
+
<div class="global-appearance-editor-actions flex flex-wrap items-center gap-2">
|
|
200
222
|
<Button
|
|
201
223
|
type="button"
|
|
202
224
|
size="sm"
|
|
@@ -232,19 +254,15 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
|
|
|
232
254
|
)
|
|
233
255
|
}
|
|
234
256
|
|
|
235
|
-
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
257
|
+
/** The editor in an inset Sheet docked beside the main view: the body scrolls and
|
|
258
|
+
* Reset/Cancel/Save sit in the panel's full-width footer, always reachable.
|
|
259
|
+
* Dismissal (Escape, the close button, an outside click) is `onDismiss`. */
|
|
238
260
|
export function AppearancePopover(props: AppearancePopoverProps) {
|
|
239
261
|
const [menuPortalMount, setMenuPortalMount] = createSignal<HTMLElement>()
|
|
240
262
|
|
|
241
263
|
return (
|
|
242
|
-
<
|
|
243
|
-
|
|
244
|
-
modal
|
|
245
|
-
onOpenChange={(open) => (open ? props.onOpen() : props.onDismiss())}
|
|
246
|
-
>
|
|
247
|
-
<PopoverTrigger
|
|
264
|
+
<Sheet open={props.open} onOpenChange={(open) => (open ? props.onOpen() : props.onDismiss())}>
|
|
265
|
+
<SheetTrigger
|
|
248
266
|
as={Button}
|
|
249
267
|
type="button"
|
|
250
268
|
variant="ghost"
|
|
@@ -252,17 +270,21 @@ export function AppearancePopover(props: AppearancePopoverProps) {
|
|
|
252
270
|
aria-label="Appearance settings"
|
|
253
271
|
>
|
|
254
272
|
<Palette />
|
|
255
|
-
</
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
</SheetTrigger>
|
|
274
|
+
<SheetContent side="end" closeLabel="Close appearance settings" ref={setMenuPortalMount}>
|
|
275
|
+
<SheetHeader>
|
|
276
|
+
<SheetTitle>Appearance</SheetTitle>
|
|
277
|
+
<SheetDescription>
|
|
278
|
+
Changes preview immediately. Save keeps them; Cancel restores the previous appearance.
|
|
279
|
+
</SheetDescription>
|
|
280
|
+
</SheetHeader>
|
|
281
|
+
<SheetBody>
|
|
282
|
+
<AppearanceEditor {...props} menuPortalMount={menuPortalMount()} hideActions />
|
|
283
|
+
</SheetBody>
|
|
284
|
+
<SheetFooter>
|
|
285
|
+
<AppearanceEditorActions {...props} />
|
|
286
|
+
</SheetFooter>
|
|
287
|
+
</SheetContent>
|
|
288
|
+
</Sheet>
|
|
267
289
|
)
|
|
268
290
|
}
|
|
@@ -55,6 +55,8 @@ export type AppearanceEditorProps = {
|
|
|
55
55
|
/** Mount nested font menus inside the containing overlay when one exists. */
|
|
56
56
|
menuPortalMount?: HTMLElement
|
|
57
57
|
class?: string
|
|
58
|
+
/** Leave Reset/Cancel/Save to the host, which renders `AppearanceEditorActions`. */
|
|
59
|
+
hideActions?: boolean
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
export type AppearancePopoverProps = AppearanceEditorProps & {
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { Accessor, JSX } from 'solid-js'
|
|
2
|
+
import { Show, createSignal, createUniqueId, onCleanup, onMount } from 'solid-js'
|
|
3
|
+
import { cn } from '#lib/utils'
|
|
4
|
+
import { Sheet, SheetContent, SheetTitle } from '../../ui/sheet'
|
|
5
|
+
import {
|
|
6
|
+
SidebarNav,
|
|
7
|
+
SidebarNavContent,
|
|
8
|
+
SidebarNavFooter,
|
|
9
|
+
SidebarNavHeader,
|
|
10
|
+
SidebarNavTitle,
|
|
11
|
+
} from '../sidebar-nav/sidebar-nav'
|
|
12
|
+
import { PixelResizeHandle } from '../pixel-resize-handle/pixel-resize-handle'
|
|
13
|
+
|
|
14
|
+
export type ContextualSidebarRenderContext = {
|
|
15
|
+
/** Whether the slot is being rendered inside the modal mobile sheet. */
|
|
16
|
+
mobile: boolean
|
|
17
|
+
/** Mount nested popovers and menus inside the mobile dialog's accessibility tree. */
|
|
18
|
+
portalMount(): HTMLElement | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ContextualSidebarProps = {
|
|
22
|
+
/** Stable host id prefix; generated when omitted. */
|
|
23
|
+
id?: string
|
|
24
|
+
/** Landmark name shared by the desktop and mobile navigation. */
|
|
25
|
+
label: string
|
|
26
|
+
/** Visible navigation title and mobile dialog title. */
|
|
27
|
+
title: string
|
|
28
|
+
/** Host-controlled expanded state for the desktop panel and mobile dialog. */
|
|
29
|
+
open: boolean
|
|
30
|
+
onOpenChange(open: boolean): void
|
|
31
|
+
/** Current host-owned pixel width, reflected by the resize handle. */
|
|
32
|
+
width: number
|
|
33
|
+
minimum: number
|
|
34
|
+
maximum: number
|
|
35
|
+
step?: number
|
|
36
|
+
/** Pass the host's initial viewport seed to distinguish stale desktop state. */
|
|
37
|
+
wideViewportAtLoad?: boolean
|
|
38
|
+
resizeLabel?: string
|
|
39
|
+
/** Stable external opener used when the modal mobile sheet closes. */
|
|
40
|
+
restoreFocusRef?: Accessor<HTMLElement | undefined>
|
|
41
|
+
/** Called with the nav node so a host can project width onto its layout root. */
|
|
42
|
+
onSidebarElement?: (element: HTMLElement | undefined, mobile: boolean) => void
|
|
43
|
+
/** Host-owned layout classes applied to both nav instances. */
|
|
44
|
+
sidebarClass?: string
|
|
45
|
+
/** Host-owned responsive constraints for the modal sheet. */
|
|
46
|
+
sheetClass?: string
|
|
47
|
+
headerClass?: string
|
|
48
|
+
contentClass?: string
|
|
49
|
+
footerClass?: string
|
|
50
|
+
headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
51
|
+
/** Optional action/content slot alongside the shared heading. */
|
|
52
|
+
header?(context: ContextualSidebarRenderContext): JSX.Element
|
|
53
|
+
/** View-specific navigation rows, projects, rooms, files, or sessions. */
|
|
54
|
+
content(context: ContextualSidebarRenderContext): JSX.Element
|
|
55
|
+
/** Optional view-specific footer, such as an archive affordance. */
|
|
56
|
+
footer?(context: ContextualSidebarRenderContext): JSX.Element
|
|
57
|
+
onWidthChange(width: number): void
|
|
58
|
+
onWidthCommit?(width: number): void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ContextualSidebar.
|
|
63
|
+
*
|
|
64
|
+
* Shared navigation geometry and interaction policy for an application's
|
|
65
|
+
* contextual sidebar. The host keeps ownership of the actual grid width,
|
|
66
|
+
* persisted preference, open state, and view-specific navigation content.
|
|
67
|
+
*/
|
|
68
|
+
export function ContextualSidebar(props: ContextualSidebarProps) {
|
|
69
|
+
const [isNarrowViewport, setIsNarrowViewport] = createSignal(false)
|
|
70
|
+
const [mobileMount, setMobileMount] = createSignal<
|
|
71
|
+
{ element: HTMLElement; connected: boolean } | undefined
|
|
72
|
+
>()
|
|
73
|
+
const id = props.id ?? `contextual-sidebar-${createUniqueId()}`
|
|
74
|
+
const resizeLabel = () => props.resizeLabel ?? `Resize ${props.label}`
|
|
75
|
+
const portalMount = () => {
|
|
76
|
+
const mount = mobileMount()
|
|
77
|
+
return mount?.connected && mount.element.isConnected ? mount.element : undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
onMount(() => {
|
|
81
|
+
const media = window.matchMedia('(max-width: 48rem)')
|
|
82
|
+
const updateViewport = () => {
|
|
83
|
+
const wasNarrow = isNarrowViewport()
|
|
84
|
+
// Close before mounting a modal. A desktop-expanded host state is not
|
|
85
|
+
// mobile intent, and opening the Sheet for one frame can steal focus.
|
|
86
|
+
if (media.matches && !wasNarrow) props.onOpenChange(false)
|
|
87
|
+
setIsNarrowViewport(media.matches)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// A route can mount after a wide-loaded host crosses into mobile. Clear
|
|
91
|
+
// that stale seed, but preserve a sidebar intentionally opened at a
|
|
92
|
+
// narrow boot viewport.
|
|
93
|
+
if (media.matches && props.wideViewportAtLoad && props.open) props.onOpenChange(false)
|
|
94
|
+
setIsNarrowViewport(media.matches)
|
|
95
|
+
media.addEventListener('change', updateViewport)
|
|
96
|
+
onCleanup(() => media.removeEventListener('change', updateViewport))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const renderSidebar = (mobile: boolean) => {
|
|
100
|
+
let element: HTMLElement | undefined
|
|
101
|
+
const context: ContextualSidebarRenderContext = {
|
|
102
|
+
mobile,
|
|
103
|
+
portalMount: () => (mobile ? portalMount() : undefined),
|
|
104
|
+
}
|
|
105
|
+
const navigationId = `${id}-${mobile ? 'mobile' : 'desktop'}`
|
|
106
|
+
|
|
107
|
+
onCleanup(() => {
|
|
108
|
+
if (mobile && mobileMount()?.element === element) setMobileMount(undefined)
|
|
109
|
+
element = undefined
|
|
110
|
+
props.onSidebarElement?.(undefined, mobile)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<SidebarNav
|
|
115
|
+
as="aside"
|
|
116
|
+
id={navigationId}
|
|
117
|
+
aria-label={props.label}
|
|
118
|
+
aria-hidden={!mobile && !props.open ? 'true' : undefined}
|
|
119
|
+
inert={!mobile && !props.open}
|
|
120
|
+
data-contextual-sidebar={mobile ? 'mobile' : 'desktop'}
|
|
121
|
+
data-open={mobile ? undefined : String(props.open)}
|
|
122
|
+
ref={(node) => {
|
|
123
|
+
element = node
|
|
124
|
+
if (mobile) {
|
|
125
|
+
setMobileMount({ element: node, connected: node.isConnected })
|
|
126
|
+
queueMicrotask(() => {
|
|
127
|
+
if (node.isConnected && mobileMount()?.element === node)
|
|
128
|
+
setMobileMount({ element: node, connected: true })
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
props.onSidebarElement?.(node, mobile)
|
|
132
|
+
}}
|
|
133
|
+
class={cn('relative h-full w-full max-w-full', props.sidebarClass)}
|
|
134
|
+
>
|
|
135
|
+
<Show when={!mobile && props.open}>
|
|
136
|
+
<PixelResizeHandle
|
|
137
|
+
side="left"
|
|
138
|
+
value={props.width}
|
|
139
|
+
minimum={props.minimum}
|
|
140
|
+
maximum={props.maximum}
|
|
141
|
+
step={props.step}
|
|
142
|
+
label={resizeLabel()}
|
|
143
|
+
controls={navigationId}
|
|
144
|
+
onChange={props.onWidthChange}
|
|
145
|
+
onCommit={props.onWidthCommit}
|
|
146
|
+
/>
|
|
147
|
+
</Show>
|
|
148
|
+
<Show when={!mobile || mobileMount()?.connected}>
|
|
149
|
+
<SidebarNavHeader class={cn(mobile && 'pe-12', props.headerClass)}>
|
|
150
|
+
<SidebarNavTitle as={props.headingAs ?? 'h2'}>{props.title}</SidebarNavTitle>
|
|
151
|
+
{props.header?.(context)}
|
|
152
|
+
</SidebarNavHeader>
|
|
153
|
+
<SidebarNavContent class={props.contentClass}>{props.content(context)}</SidebarNavContent>
|
|
154
|
+
{props.footer ? (
|
|
155
|
+
<SidebarNavFooter class={props.footerClass}>{props.footer(context)}</SidebarNavFooter>
|
|
156
|
+
) : null}
|
|
157
|
+
</Show>
|
|
158
|
+
</SidebarNav>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Sheet
|
|
164
|
+
open={isNarrowViewport() && props.open}
|
|
165
|
+
onOpenChange={(open) => props.onOpenChange(open)}
|
|
166
|
+
>
|
|
167
|
+
<Show when={!isNarrowViewport()}>{(_wide) => renderSidebar(false)}</Show>
|
|
168
|
+
<Show when={isNarrowViewport()}>
|
|
169
|
+
<SheetContent
|
|
170
|
+
side="start"
|
|
171
|
+
class={cn('gap-0 p-0', props.sheetClass)}
|
|
172
|
+
restoreFocusRef={props.restoreFocusRef}
|
|
173
|
+
>
|
|
174
|
+
<SheetTitle class="sr-only">{props.title} navigation</SheetTitle>
|
|
175
|
+
{renderSidebar(true)}
|
|
176
|
+
</SheetContent>
|
|
177
|
+
</Show>
|
|
178
|
+
</Sheet>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PixelResizeHandle, type PixelResizeHandleProps } from './pixel-resize-handle'
|