@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
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../ui/resizable'
|
|
2
|
+
import { createSignal, onCleanup } from 'solid-js'
|
|
3
|
+
import { cn } from '#lib/utils'
|
|
4
|
+
|
|
5
|
+
export type PixelResizeHandleProps = {
|
|
6
|
+
/** Which physical edge of the ruler the resizable surface is attached to. */
|
|
7
|
+
side: 'left' | 'right'
|
|
8
|
+
/** Current width in CSS pixels. */
|
|
9
|
+
value: number
|
|
10
|
+
minimum: number
|
|
11
|
+
maximum: number
|
|
12
|
+
step?: number
|
|
13
|
+
label?: string
|
|
14
|
+
controls?: string
|
|
15
|
+
class?: string
|
|
16
|
+
onChange(value: number): void
|
|
17
|
+
onCommit?(value: number): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PixelResizeHandle.
|
|
22
|
+
*
|
|
23
|
+
* A keyboard- and pointer-operable edge ruler for a host-owned pane. Keep the
|
|
24
|
+
* pane's actual width and any persistence in its layout owner; this component
|
|
25
|
+
* reports a bounded pixel value and places the shared resizable grip at the
|
|
26
|
+
* matching left or right edge.
|
|
27
|
+
*
|
|
28
|
+
* Corvu 0.2.5 supplies geometry, keyboard resizing, ARIA and the shared grip,
|
|
29
|
+
* but its global drag handler does not end on pointer cancellation. Prevent
|
|
30
|
+
* that handler through its public start hook and use local pointer capture
|
|
31
|
+
* for bounded pixel gestures, including cancellation and lost capture.
|
|
32
|
+
*/
|
|
33
|
+
export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
34
|
+
const maximum = () => Math.max(1, props.maximum)
|
|
35
|
+
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))
|
|
36
|
+
const value = () => Math.min(maximum(), Math.max(minimum(), Math.round(props.value)))
|
|
37
|
+
const ratio = () => value() / maximum()
|
|
38
|
+
const isRight = () => props.side === 'right'
|
|
39
|
+
const rulerSizes = () => (isRight() ? [1 - ratio(), ratio()] : [ratio(), 1 - ratio()])
|
|
40
|
+
const commit = () => props.onCommit?.(value())
|
|
41
|
+
let interacting = false
|
|
42
|
+
const [drag, setDrag] = createSignal<{
|
|
43
|
+
pointerId: number
|
|
44
|
+
startX: number
|
|
45
|
+
startValue: number
|
|
46
|
+
handle: HTMLElement
|
|
47
|
+
}>()
|
|
48
|
+
const endPointer = (shouldCommit: boolean) => {
|
|
49
|
+
const current = drag()
|
|
50
|
+
if (!current) return
|
|
51
|
+
setDrag(undefined)
|
|
52
|
+
if (current.handle.hasPointerCapture(current.pointerId))
|
|
53
|
+
current.handle.releasePointerCapture(current.pointerId)
|
|
54
|
+
if (shouldCommit) commit()
|
|
55
|
+
}
|
|
56
|
+
const endKeyboard = () => {
|
|
57
|
+
const wasInteracting = interacting
|
|
58
|
+
interacting = false
|
|
59
|
+
if (wasInteracting) commit()
|
|
60
|
+
}
|
|
61
|
+
onCleanup(() => {
|
|
62
|
+
endKeyboard()
|
|
63
|
+
endPointer(false)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ResizablePanelGroup
|
|
68
|
+
orientation="horizontal"
|
|
69
|
+
dir="ltr"
|
|
70
|
+
sizes={rulerSizes()}
|
|
71
|
+
keyboardDelta={`${props.step ?? 16}px`}
|
|
72
|
+
style={{ width: `${maximum()}px` }}
|
|
73
|
+
class={cn(
|
|
74
|
+
'pointer-events-none absolute inset-y-0 flex',
|
|
75
|
+
{
|
|
76
|
+
'left-0': props.side === 'left',
|
|
77
|
+
'right-0': props.side === 'right',
|
|
78
|
+
},
|
|
79
|
+
props.class
|
|
80
|
+
)}
|
|
81
|
+
onSizesChange={(sizes) => {
|
|
82
|
+
// Panel registration and teardown also emit sizes. Only user resizing
|
|
83
|
+
// may update the host preference; collapsing must preserve its width.
|
|
84
|
+
if (!interacting || sizes.length !== 2) return
|
|
85
|
+
const next = Math.round((sizes[isRight() ? 1 : 0] ?? ratio()) * maximum())
|
|
86
|
+
const bounded = Math.min(maximum(), Math.max(minimum(), next))
|
|
87
|
+
if (bounded !== value()) props.onChange(bounded)
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<ResizablePanel
|
|
91
|
+
minSize={isRight() ? 0 : minimum() / maximum()}
|
|
92
|
+
maxSize={1}
|
|
93
|
+
aria-hidden="true"
|
|
94
|
+
/>
|
|
95
|
+
<ResizableHandle
|
|
96
|
+
withHandle
|
|
97
|
+
label={props.label ?? `Resize ${props.side} pane`}
|
|
98
|
+
aria-controls={props.controls}
|
|
99
|
+
aria-valuemin={minimum()}
|
|
100
|
+
aria-valuemax={maximum()}
|
|
101
|
+
aria-valuenow={value()}
|
|
102
|
+
aria-valuetext={`${value()} pixels`}
|
|
103
|
+
data-dragging={drag() ? '' : undefined}
|
|
104
|
+
onHandleDragStart={(event) => {
|
|
105
|
+
// Corvu checks defaultPrevented before starting its global handler.
|
|
106
|
+
event.preventDefault()
|
|
107
|
+
if (event.button !== 0 || !event.isPrimary) return
|
|
108
|
+
const handle = event.currentTarget
|
|
109
|
+
if (!(handle instanceof HTMLElement)) return
|
|
110
|
+
endPointer(false)
|
|
111
|
+
interacting = false
|
|
112
|
+
handle.focus()
|
|
113
|
+
handle.setPointerCapture(event.pointerId)
|
|
114
|
+
setDrag({
|
|
115
|
+
pointerId: event.pointerId,
|
|
116
|
+
startX: event.clientX,
|
|
117
|
+
startValue: value(),
|
|
118
|
+
handle,
|
|
119
|
+
})
|
|
120
|
+
}}
|
|
121
|
+
onPointerMove={(event: PointerEvent) => {
|
|
122
|
+
const current = drag()
|
|
123
|
+
if (!current || event.pointerId !== current.pointerId) return
|
|
124
|
+
if (!current.handle.hasPointerCapture(current.pointerId)) {
|
|
125
|
+
endPointer(false)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
const delta = (event.clientX - current.startX) * (isRight() ? -1 : 1)
|
|
129
|
+
const next = Math.min(
|
|
130
|
+
maximum(),
|
|
131
|
+
Math.max(minimum(), Math.round(current.startValue + delta))
|
|
132
|
+
)
|
|
133
|
+
if (next !== value()) props.onChange(next)
|
|
134
|
+
}}
|
|
135
|
+
onPointerUp={(event: PointerEvent) => {
|
|
136
|
+
if (event.pointerId === drag()?.pointerId) endPointer(true)
|
|
137
|
+
}}
|
|
138
|
+
onPointerCancel={(event: PointerEvent) => {
|
|
139
|
+
if (event.pointerId === drag()?.pointerId) endPointer(false)
|
|
140
|
+
}}
|
|
141
|
+
onLostPointerCapture={(event: PointerEvent) => {
|
|
142
|
+
if (event.pointerId === drag()?.pointerId) endPointer(false)
|
|
143
|
+
}}
|
|
144
|
+
onBlur={() => {
|
|
145
|
+
endKeyboard()
|
|
146
|
+
endPointer(false)
|
|
147
|
+
}}
|
|
148
|
+
onKeyDown={(event) => {
|
|
149
|
+
const current = drag()
|
|
150
|
+
if (current && !current.handle.hasPointerCapture(current.pointerId)) endPointer(false)
|
|
151
|
+
if (drag()) {
|
|
152
|
+
event.preventDefault()
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) interacting = true
|
|
156
|
+
// Corvu operates on physical panel order. Home/End here describe
|
|
157
|
+
// the controlled pane width, including a pane after the separator.
|
|
158
|
+
if (event.key === 'Home' || event.key === 'End') {
|
|
159
|
+
event.preventDefault()
|
|
160
|
+
props.onChange(event.key === 'Home' ? minimum() : maximum())
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
onKeyUp={(event) => {
|
|
164
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
|
|
165
|
+
endKeyboard()
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
class="pointer-events-auto"
|
|
169
|
+
/>
|
|
170
|
+
<ResizablePanel
|
|
171
|
+
minSize={isRight() ? minimum() / maximum() : 0}
|
|
172
|
+
maxSize={1}
|
|
173
|
+
aria-hidden="true"
|
|
174
|
+
/>
|
|
175
|
+
</ResizablePanelGroup>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -1,59 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
PixelResizeHandle,
|
|
3
|
+
type PixelResizeHandleProps,
|
|
4
|
+
} from '../pixel-resize-handle/pixel-resize-handle'
|
|
3
5
|
|
|
4
|
-
export type SidebarNavResizeHandleProps =
|
|
5
|
-
value: number
|
|
6
|
-
minimum: number
|
|
7
|
-
maximum: number
|
|
8
|
-
step?: number
|
|
9
|
-
label?: string
|
|
10
|
-
controls?: string
|
|
11
|
-
class?: string
|
|
12
|
-
onChange(value: number): void
|
|
13
|
-
onCommit?(value: number): void
|
|
14
|
-
}
|
|
6
|
+
export type SidebarNavResizeHandleProps = Omit<PixelResizeHandleProps, 'side'>
|
|
15
7
|
|
|
16
|
-
/**
|
|
17
|
-
* Resize a contextual sidebar without making its host recreate a separator
|
|
18
|
-
* widget. Corvu owns pointer capture, cancellation, constraints, keyboard
|
|
19
|
-
* movement and cleanup. Its two invisible panels provide a bounded pixel
|
|
20
|
-
* ruler; the host retains the real pane layout and preference persistence.
|
|
21
|
-
* Place this inside the positioned sidebar. The ruler can extend over the
|
|
22
|
-
* adjacent content, but only its handle participates in pointer hit testing.
|
|
23
|
-
*/
|
|
8
|
+
/** Keep the established left-sidebar API as a thin adapter over the shared edge ruler. */
|
|
24
9
|
export function SidebarNavResizeHandle(props: SidebarNavResizeHandleProps) {
|
|
25
|
-
|
|
26
|
-
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))
|
|
27
|
-
const value = () => Math.min(maximum(), Math.max(minimum(), props.value))
|
|
28
|
-
const commit = () => props.onCommit?.(value())
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Resizable
|
|
32
|
-
orientation="horizontal"
|
|
33
|
-
sizes={[value() / maximum(), 1 - value() / maximum()]}
|
|
34
|
-
keyboardDelta={`${props.step ?? 16}px`}
|
|
35
|
-
style={{ width: `${maximum()}px` }}
|
|
36
|
-
class={cn('pointer-events-none absolute inset-y-0 start-0 flex', props.class)}
|
|
37
|
-
onSizesChange={(sizes) => {
|
|
38
|
-
const next = Math.round((sizes[0] ?? 0) * maximum())
|
|
39
|
-
if (next !== value()) props.onChange(Math.min(maximum(), Math.max(minimum(), next)))
|
|
40
|
-
}}
|
|
41
|
-
>
|
|
42
|
-
<Resizable.Panel minSize={minimum() / maximum()} maxSize={1} aria-hidden="true" />
|
|
43
|
-
<Resizable.Handle
|
|
44
|
-
aria-label={props.label ?? 'Resize navigation'}
|
|
45
|
-
aria-controls={props.controls}
|
|
46
|
-
aria-orientation="vertical"
|
|
47
|
-
aria-valuemin={minimum()}
|
|
48
|
-
aria-valuemax={maximum()}
|
|
49
|
-
aria-valuenow={value()}
|
|
50
|
-
onHandleDragEnd={commit}
|
|
51
|
-
onKeyUp={(event) => {
|
|
52
|
-
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) commit()
|
|
53
|
-
}}
|
|
54
|
-
class="pointer-events-auto relative z-(--z-docked) w-px -translate-x-full shrink-0 bg-transparent after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 hover:bg-primary focus-visible:bg-primary focus-visible:ring-3 focus-visible:ring-primary-subtle focus-visible:outline-none"
|
|
55
|
-
/>
|
|
56
|
-
<Resizable.Panel minSize={0} aria-hidden="true" />
|
|
57
|
-
</Resizable>
|
|
58
|
-
)
|
|
10
|
+
return <PixelResizeHandle {...props} side="left" label={props.label ?? 'Resize navigation'} />
|
|
59
11
|
}
|
|
@@ -19,11 +19,15 @@ export const badgeVariants = cva(
|
|
|
19
19
|
default: 'bg-primary text-primary-foreground',
|
|
20
20
|
secondary: 'bg-secondary text-secondary-foreground',
|
|
21
21
|
outline: 'border-border text-foreground',
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Tinted status fills, for counts and states rather than actions. The
|
|
24
|
+
* word stays in `foreground` for contrast; the glyph takes the status
|
|
25
|
+
* hue, so tags of different tones read apart at a glance.
|
|
26
|
+
*/
|
|
27
|
+
success: 'bg-success-subtle text-foreground [&_svg]:text-success',
|
|
28
|
+
warning: 'bg-warning-subtle text-foreground [&_svg]:text-warning',
|
|
29
|
+
destructive: 'bg-destructive-subtle text-foreground [&_svg]:text-destructive',
|
|
30
|
+
info: 'bg-info-subtle text-foreground [&_svg]:text-info',
|
|
27
31
|
subtle: 'bg-primary-subtle text-primary',
|
|
28
32
|
/**
|
|
29
33
|
* A count that asks for attention, pinned to a corner of another control
|
|
@@ -2,7 +2,7 @@ import { Combobox as KobalteCombobox } from '@kobalte/core/combobox'
|
|
|
2
2
|
import { Check, ChevronDown, X } from 'lucide-solid'
|
|
3
3
|
import type { ComponentProps } from 'solid-js'
|
|
4
4
|
import { Show, splitProps } from 'solid-js'
|
|
5
|
-
import { menuContentPadding, menuItem, popoverArrow } from '#lib/overlay'
|
|
5
|
+
import { menuContentPadding, menuItem, popoverArrow, topLayerProps } from '#lib/overlay'
|
|
6
6
|
import { useFormFieldControl } from '#lib/form-field'
|
|
7
7
|
import { cn } from '#lib/utils'
|
|
8
8
|
|
|
@@ -108,6 +108,7 @@ export function ComboboxContent(props: ComponentProps<typeof KobalteCombobox.Con
|
|
|
108
108
|
return (
|
|
109
109
|
<KobalteCombobox.Portal>
|
|
110
110
|
<KobalteCombobox.Content
|
|
111
|
+
{...topLayerProps}
|
|
111
112
|
class={cn(
|
|
112
113
|
'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg',
|
|
113
114
|
'z-(--z-menu) max-h-72 min-w-(--kb-popper-anchor-width) origin-(--kb-combobox-content-transform-origin) overflow-hidden',
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
menuSeparator,
|
|
11
11
|
overlayMotion,
|
|
12
12
|
overlaySurface,
|
|
13
|
+
topLayerProps,
|
|
13
14
|
} from '#lib/overlay'
|
|
14
15
|
import { cn } from '#lib/utils'
|
|
15
16
|
|
|
@@ -40,6 +41,7 @@ export function ContextMenuContent(props: ComponentProps<typeof KobalteContextMe
|
|
|
40
41
|
return (
|
|
41
42
|
<KobalteContextMenu.Portal>
|
|
42
43
|
<KobalteContextMenu.Content
|
|
44
|
+
{...topLayerProps}
|
|
43
45
|
class={cn(
|
|
44
46
|
overlaySurface,
|
|
45
47
|
overlayMotion,
|
|
@@ -174,6 +176,7 @@ export function ContextMenuSubContent(props: ComponentProps<typeof KobalteContex
|
|
|
174
176
|
return (
|
|
175
177
|
<KobalteContextMenu.Portal>
|
|
176
178
|
<KobalteContextMenu.SubContent
|
|
179
|
+
{...topLayerProps}
|
|
177
180
|
class={cn(
|
|
178
181
|
overlaySurface,
|
|
179
182
|
overlayMotion,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
overlayMotion,
|
|
12
12
|
overlaySurface,
|
|
13
13
|
popoverArrow,
|
|
14
|
+
topLayerProps,
|
|
14
15
|
} from '#lib/overlay'
|
|
15
16
|
import { cn } from '#lib/utils'
|
|
16
17
|
|
|
@@ -148,6 +149,12 @@ export function DropdownMenuTrigger(props: ComponentProps<typeof KobalteDropdown
|
|
|
148
149
|
const isNativeLink = trigger instanceof HTMLAnchorElement && trigger.hasAttribute('href')
|
|
149
150
|
if (trigger instanceof HTMLElement && !isNativeLink) {
|
|
150
151
|
trigger.focus({ preventScroll: true })
|
|
152
|
+
// Focusing first is not enough in WebKit: the press's default mousedown
|
|
153
|
+
// action runs afterwards and moves focus off a button to the nearest
|
|
154
|
+
// focusable ancestor — inside a dialog, the dialog — so the menu that
|
|
155
|
+
// just opened still reads focus leaving it and closes. Cancelling the
|
|
156
|
+
// default keeps focus where it was put.
|
|
157
|
+
if (event.pointerType !== 'touch') event.preventDefault()
|
|
151
158
|
}
|
|
152
159
|
}
|
|
153
160
|
const onKeyDown = (event: DropdownMenuTriggerKeyEvent) => {
|
|
@@ -196,6 +203,7 @@ export function DropdownMenuContent(props: DropdownMenuContentProps) {
|
|
|
196
203
|
return (
|
|
197
204
|
<KobalteDropdownMenu.Portal mount={local.portalMount}>
|
|
198
205
|
<KobalteDropdownMenu.Content
|
|
206
|
+
{...topLayerProps}
|
|
199
207
|
class={cn(
|
|
200
208
|
overlaySurface,
|
|
201
209
|
overlayMotion,
|
|
@@ -372,6 +380,7 @@ export function DropdownMenuSubContent(
|
|
|
372
380
|
return (
|
|
373
381
|
<KobalteDropdownMenu.Portal>
|
|
374
382
|
<KobalteDropdownMenu.SubContent
|
|
383
|
+
{...topLayerProps}
|
|
375
384
|
class={cn(
|
|
376
385
|
overlaySurface,
|
|
377
386
|
overlayMotion,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
overlayMotion,
|
|
12
12
|
overlaySurface,
|
|
13
13
|
popoverArrow,
|
|
14
|
+
topLayerProps,
|
|
14
15
|
} from '#lib/overlay'
|
|
15
16
|
import { cn } from '#lib/utils'
|
|
16
17
|
|
|
@@ -71,6 +72,7 @@ export function MenubarContent(props: ComponentProps<typeof KobalteMenubar.Conte
|
|
|
71
72
|
return (
|
|
72
73
|
<KobalteMenubar.Portal>
|
|
73
74
|
<KobalteMenubar.Content
|
|
75
|
+
{...topLayerProps}
|
|
74
76
|
class={cn(
|
|
75
77
|
overlaySurface,
|
|
76
78
|
overlayMotion,
|
|
@@ -201,6 +203,7 @@ export function MenubarSubContent(props: ComponentProps<typeof KobalteMenubar.Su
|
|
|
201
203
|
return (
|
|
202
204
|
<KobalteMenubar.Portal>
|
|
203
205
|
<KobalteMenubar.SubContent
|
|
206
|
+
{...topLayerProps}
|
|
204
207
|
class={cn(
|
|
205
208
|
overlaySurface,
|
|
206
209
|
overlayMotion,
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Popover as KobaltePopover } from '@kobalte/core/popover'
|
|
2
2
|
import type { ComponentProps } from 'solid-js'
|
|
3
3
|
import { splitProps } from 'solid-js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
menuContentPadding,
|
|
6
|
+
popoverArrow,
|
|
7
|
+
popoverMotion,
|
|
8
|
+
overlaySurface,
|
|
9
|
+
topLayerProps,
|
|
10
|
+
} from '#lib/overlay'
|
|
5
11
|
import { cn } from '#lib/utils'
|
|
6
12
|
|
|
7
13
|
/**
|
|
@@ -39,6 +45,7 @@ export function PopoverContent(props: PopoverContentProps) {
|
|
|
39
45
|
return (
|
|
40
46
|
<KobaltePopover.Portal>
|
|
41
47
|
<KobaltePopover.Content
|
|
48
|
+
{...topLayerProps}
|
|
42
49
|
class={cn(
|
|
43
50
|
overlaySurface,
|
|
44
51
|
popoverMotion,
|
|
@@ -17,7 +17,7 @@ import { cn } from '#lib/utils'
|
|
|
17
17
|
* adjusted from the keyboard; a bare `cursor-col-resize` div cannot.
|
|
18
18
|
*/
|
|
19
19
|
/**
|
|
20
|
-
* Whether the group was given `initialSizes`. Those are corvu's to apply, and a
|
|
20
|
+
* Whether the group was given `initialSizes` or controlled `sizes`. Those are corvu's to apply, and a
|
|
21
21
|
* panel's own `initialSize` overrides them — so an unsized panel must not invent
|
|
22
22
|
* one when the group already has an answer.
|
|
23
23
|
*/
|
|
@@ -27,7 +27,9 @@ export function ResizablePanelGroup(props: ComponentProps<typeof Resizable> & {
|
|
|
27
27
|
const [local, rest] = splitProps(props, ['class'])
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<GroupSizesContext.Provider
|
|
30
|
+
<GroupSizesContext.Provider
|
|
31
|
+
value={() => rest.initialSizes !== undefined || rest.sizes !== undefined}
|
|
32
|
+
>
|
|
31
33
|
<Resizable
|
|
32
34
|
class={cn('flex size-full data-[orientation=vertical]:flex-col', local.class)}
|
|
33
35
|
{...rest}
|
|
@@ -2,7 +2,7 @@ import { Select as KobalteSelect } from '@kobalte/core/select'
|
|
|
2
2
|
import { Check, ChevronDown, ChevronsUpDown } from 'lucide-solid'
|
|
3
3
|
import type { ComponentProps } from 'solid-js'
|
|
4
4
|
import { Show, splitProps } from 'solid-js'
|
|
5
|
-
import { menuContentPadding, menuItem, popoverArrow } from '#lib/overlay'
|
|
5
|
+
import { menuContentPadding, menuItem, popoverArrow, topLayerProps } from '#lib/overlay'
|
|
6
6
|
import { cn } from '#lib/utils'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -140,6 +140,7 @@ export function SelectContent(props: SelectContentProps) {
|
|
|
140
140
|
return (
|
|
141
141
|
<KobalteSelect.Portal mount={local.portalMount}>
|
|
142
142
|
<KobalteSelect.Content
|
|
143
|
+
{...topLayerProps}
|
|
143
144
|
class={cn(
|
|
144
145
|
'bg-popover text-popover-foreground rounded-xl border border-border shadow-lg',
|
|
145
146
|
'z-(--z-menu) max-h-(--kb-popper-content-available-height) min-w-[8rem] origin-(--kb-select-content-transform-origin) overflow-y-auto overflow-x-hidden',
|
|
@@ -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
|
)
|
package/src/index.ts
CHANGED
|
@@ -89,6 +89,7 @@ export * from './components/layout/page'
|
|
|
89
89
|
export * from './components/layout/panel'
|
|
90
90
|
export * from './components/layout/side-rail'
|
|
91
91
|
export * from './components/layout/sidebar-nav'
|
|
92
|
+
export * from './components/layout/contextual-sidebar'
|
|
92
93
|
export * from './components/layout/status-bar'
|
|
93
94
|
export * from './components/layout/top-bar'
|
|
94
95
|
|
package/src/lib/overlay.ts
CHANGED
|
@@ -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
|