@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.
Files changed (112) hide show
  1. package/dist/components/composites/appearance-editor/appearance-editor.d.ts +9 -3
  2. package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
  3. package/dist/components/composites/appearance-editor/appearance-editor.js +61 -29
  4. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  5. package/dist/components/composites/appearance-editor/appearance-types.d.ts +2 -0
  6. package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
  7. package/dist/components/composites/appearance-editor/index.d.ts +1 -1
  8. package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
  9. package/dist/components/composites/appearance-editor/index.js +2 -2
  10. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts +55 -0
  11. package/dist/components/layout/contextual-sidebar/contextual-sidebar.d.ts.map +1 -0
  12. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js +199 -0
  13. package/dist/components/layout/contextual-sidebar/contextual-sidebar.js.map +1 -0
  14. package/dist/components/layout/contextual-sidebar/index.d.ts +3 -0
  15. package/dist/components/layout/contextual-sidebar/index.d.ts.map +1 -0
  16. package/dist/components/layout/contextual-sidebar/index.js +3 -0
  17. package/dist/components/layout/pixel-resize-handle/index.d.ts +2 -0
  18. package/dist/components/layout/pixel-resize-handle/index.d.ts.map +1 -0
  19. package/dist/components/layout/pixel-resize-handle/index.js +2 -0
  20. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +29 -0
  21. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -0
  22. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +183 -0
  23. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -0
  24. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts +3 -19
  25. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts.map +1 -1
  26. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js +8 -76
  27. package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js.map +1 -1
  28. package/dist/components/ui/badge/badge.d.ts.map +1 -1
  29. package/dist/components/ui/badge/badge.js +9 -5
  30. package/dist/components/ui/badge/badge.js.map +1 -1
  31. package/dist/components/ui/combobox/combobox.d.ts.map +1 -1
  32. package/dist/components/ui/combobox/combobox.js +2 -2
  33. package/dist/components/ui/combobox/combobox.js.map +1 -1
  34. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  35. package/dist/components/ui/context-menu/context-menu.js +3 -3
  36. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  37. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  38. package/dist/components/ui/dropdown-menu/dropdown-menu.js +7 -4
  39. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  40. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  41. package/dist/components/ui/menubar/menubar.js +3 -3
  42. package/dist/components/ui/menubar/menubar.js.map +1 -1
  43. package/dist/components/ui/popover/popover.d.ts.map +1 -1
  44. package/dist/components/ui/popover/popover.js +2 -2
  45. package/dist/components/ui/popover/popover.js.map +1 -1
  46. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  47. package/dist/components/ui/resizable/resizable.js +2 -2
  48. package/dist/components/ui/resizable/resizable.js.map +1 -1
  49. package/dist/components/ui/select/select.d.ts.map +1 -1
  50. package/dist/components/ui/select/select.js +2 -2
  51. package/dist/components/ui/select/select.js.map +1 -1
  52. package/dist/components/ui/sheet/sheet.d.ts +16 -0
  53. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  54. package/dist/components/ui/sheet/sheet.js +82 -16
  55. package/dist/components/ui/sheet/sheet.js.map +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +4 -2
  59. package/dist/lib/overlay.d.ts +15 -0
  60. package/dist/lib/overlay.d.ts.map +1 -1
  61. package/dist/lib/overlay.js +14 -1
  62. package/dist/lib/overlay.js.map +1 -1
  63. package/dist/lint/index.d.ts.map +1 -1
  64. package/dist/lint.js +154 -20
  65. package/dist/lint.js.map +1 -1
  66. package/dist/r/appearance-editor.json +1 -1
  67. package/dist/r/contextual-sidebar.json +25 -0
  68. package/dist/r/pixel-resize-handle.json +23 -0
  69. package/dist/r/registry.json +50 -2
  70. package/dist/r/sidebar-nav.json +1 -1
  71. package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  72. package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  73. package/dist/r/src/components/composites/appearance-editor/index.ts +1 -1
  74. package/dist/r/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
  75. package/dist/r/src/components/layout/contextual-sidebar/index.ts +6 -0
  76. package/dist/r/src/components/layout/pixel-resize-handle/index.ts +1 -0
  77. package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
  78. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
  79. package/dist/r/src/components/ui/badge/badge.tsx +9 -5
  80. package/dist/r/src/components/ui/combobox/combobox.tsx +2 -1
  81. package/dist/r/src/components/ui/context-menu/context-menu.tsx +3 -0
  82. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  83. package/dist/r/src/components/ui/menubar/menubar.tsx +3 -0
  84. package/dist/r/src/components/ui/popover/popover.tsx +8 -1
  85. package/dist/r/src/components/ui/resizable/resizable.tsx +4 -2
  86. package/dist/r/src/components/ui/select/select.tsx +2 -1
  87. package/dist/r/src/components/ui/sheet/sheet.tsx +78 -15
  88. package/dist/r/src/lib/overlay.ts +14 -0
  89. package/dist/r/src/styles/base.css +13 -0
  90. package/package.json +3 -1
  91. package/registry.json +50 -2
  92. package/src/components/composites/appearance-editor/appearance-editor.tsx +55 -33
  93. package/src/components/composites/appearance-editor/appearance-types.ts +2 -0
  94. package/src/components/composites/appearance-editor/index.ts +1 -1
  95. package/src/components/layout/contextual-sidebar/contextual-sidebar.tsx +180 -0
  96. package/src/components/layout/contextual-sidebar/index.ts +6 -0
  97. package/src/components/layout/pixel-resize-handle/index.ts +1 -0
  98. package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +177 -0
  99. package/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +7 -55
  100. package/src/components/ui/badge/badge.tsx +9 -5
  101. package/src/components/ui/combobox/combobox.tsx +2 -1
  102. package/src/components/ui/context-menu/context-menu.tsx +3 -0
  103. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +9 -0
  104. package/src/components/ui/menubar/menubar.tsx +3 -0
  105. package/src/components/ui/popover/popover.tsx +8 -1
  106. package/src/components/ui/resizable/resizable.tsx +4 -2
  107. package/src/components/ui/select/select.tsx +2 -1
  108. package/src/components/ui/sheet/sheet.tsx +78 -15
  109. package/src/index.ts +1 -0
  110. package/src/lib/overlay.ts +14 -0
  111. package/src/lint/index.ts +234 -22
  112. package/src/styles/base.css +13 -0
@@ -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,6 @@
1
+ export {
2
+ ContextualSidebar,
3
+ type ContextualSidebarProps,
4
+ type ContextualSidebarRenderContext,
5
+ } from './contextual-sidebar'
6
+ export { PixelResizeHandle, type PixelResizeHandleProps } from '../pixel-resize-handle'
@@ -0,0 +1 @@
1
+ export { PixelResizeHandle, type PixelResizeHandleProps } from './pixel-resize-handle'
@@ -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 Resizable from '@corvu/resizable'
2
- import { cn } from '../../../lib/utils'
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
- const maximum = () => Math.max(1, props.maximum)
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
- /** Tinted status fills, for counts and states rather than actions. */
23
- success: 'bg-success-subtle text-foreground',
24
- warning: 'bg-warning-subtle text-foreground',
25
- destructive: 'bg-destructive-subtle text-foreground',
26
- info: 'bg-info-subtle text-foreground',
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 { menuContentPadding, popoverArrow, popoverMotion, overlaySurface } from '../../../lib/overlay'
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 value={() => rest.initialSizes !== undefined}>
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',