@adea-ai/ui 0.83.1 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/NOTICE +26 -0
- package/dist/components/composites/catalog-browser/catalog-browser-group.d.ts +20 -0
- package/dist/components/composites/catalog-browser/catalog-browser-group.d.ts.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-browser-group.js +195 -0
- package/dist/components/composites/catalog-browser/catalog-browser-group.js.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-browser.d.ts +3 -0
- package/dist/components/composites/catalog-browser/catalog-browser.d.ts.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-browser.js +378 -0
- package/dist/components/composites/catalog-browser/catalog-browser.js.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-detail.d.ts +21 -0
- package/dist/components/composites/catalog-browser/catalog-detail.d.ts.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-detail.js +92 -0
- package/dist/components/composites/catalog-browser/catalog-detail.js.map +1 -0
- package/dist/components/composites/catalog-browser/catalog-types.d.ts +78 -0
- package/dist/components/composites/catalog-browser/catalog-types.d.ts.map +1 -0
- package/dist/components/composites/catalog-browser/index.d.ts +4 -0
- package/dist/components/composites/catalog-browser/index.d.ts.map +1 -0
- package/dist/components/composites/catalog-browser/index.js +3 -0
- package/dist/components/layout/sidebar-nav/index.d.ts +1 -0
- package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/index.js +2 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts +21 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.d.ts.map +1 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js +84 -0
- package/dist/components/layout/sidebar-nav/sidebar-nav-resize-handle.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/lint/index.d.ts +1 -0
- package/dist/lint/index.d.ts.map +1 -1
- package/dist/lint.js +171 -59
- package/dist/lint.js.map +1 -1
- package/dist/r/catalog-browser.json +47 -0
- package/dist/r/registry.json +53 -0
- package/dist/r/sidebar-nav.json +6 -0
- package/dist/r/src/components/composites/catalog-browser/catalog-browser-group.tsx +148 -0
- package/dist/r/src/components/composites/catalog-browser/catalog-browser.tsx +258 -0
- package/dist/r/src/components/composites/catalog-browser/catalog-detail.tsx +64 -0
- package/dist/r/src/components/composites/catalog-browser/catalog-types.ts +83 -0
- package/dist/r/src/components/composites/catalog-browser/index.ts +10 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +4 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +59 -0
- package/package.json +1 -1
- package/registry.json +53 -0
- package/src/components/composites/catalog-browser/catalog-browser-group.tsx +148 -0
- package/src/components/composites/catalog-browser/catalog-browser.tsx +258 -0
- package/src/components/composites/catalog-browser/catalog-detail.tsx +64 -0
- package/src/components/composites/catalog-browser/catalog-types.ts +83 -0
- package/src/components/composites/catalog-browser/index.ts +10 -0
- package/src/components/layout/sidebar-nav/index.ts +4 -0
- package/src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx +59 -0
- package/src/index.ts +1 -0
- package/src/lint/index.ts +168 -19
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from 'solid-js'
|
|
2
|
+
import { ArrowLeft, Blocks, Search } from 'lucide-solid'
|
|
3
|
+
import { keyedRows } from '../../../lib/keyed-rows'
|
|
4
|
+
import { Button } from '../../ui/button'
|
|
5
|
+
import {
|
|
6
|
+
Empty,
|
|
7
|
+
EmptyContent,
|
|
8
|
+
EmptyDescription,
|
|
9
|
+
EmptyHeader,
|
|
10
|
+
EmptyMedia,
|
|
11
|
+
EmptyTitle,
|
|
12
|
+
} from '../../ui/empty'
|
|
13
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from '../../ui/input-group'
|
|
14
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../ui/tabs'
|
|
15
|
+
import { ScrollArea } from '../../ui/scroll-area'
|
|
16
|
+
import { cn } from '../../../lib/utils'
|
|
17
|
+
import { CatalogBrowserGroup, CatalogLoadingState } from './catalog-browser-group'
|
|
18
|
+
import type { CatalogBrowserProps } from './catalog-types'
|
|
19
|
+
|
|
20
|
+
export function CatalogBrowser<Value>(props: CatalogBrowserProps<Value>) {
|
|
21
|
+
const [expandedGroups, setExpandedGroups] = createSignal<ReadonlySet<string>>(new Set())
|
|
22
|
+
const [returnKey, setReturnKey] = createSignal<string>()
|
|
23
|
+
const [lastSelectedId, setLastSelectedId] = createSignal<string>()
|
|
24
|
+
const rowRefs = new Map<string, HTMLButtonElement>()
|
|
25
|
+
let searchInput: HTMLInputElement | undefined
|
|
26
|
+
let backButton: HTMLButtonElement | undefined
|
|
27
|
+
|
|
28
|
+
const selectedTab = createMemo(() => props.tabs.find((tab) => tab.id === props.tab))
|
|
29
|
+
const supplemental = () => selectedTab()?.kind === 'supplemental'
|
|
30
|
+
const selected = createMemo(() => {
|
|
31
|
+
const id = props.selectedId
|
|
32
|
+
return id && !supplemental() ? props.entries.find((entry) => entry.id === id) : undefined
|
|
33
|
+
})
|
|
34
|
+
const groupRows = keyedRows(
|
|
35
|
+
() => props.groups,
|
|
36
|
+
(group) => group.id
|
|
37
|
+
)
|
|
38
|
+
const [wide, setWide] = createSignal(
|
|
39
|
+
typeof window === 'undefined' || window.matchMedia('(min-width: 48rem)').matches
|
|
40
|
+
)
|
|
41
|
+
const updateWide = (event: MediaQueryListEvent) => setWide(event.matches)
|
|
42
|
+
|
|
43
|
+
createEffect(() => {
|
|
44
|
+
if (typeof window === 'undefined') return
|
|
45
|
+
const query = window.matchMedia('(min-width: 48rem)')
|
|
46
|
+
query.addEventListener('change', updateWide)
|
|
47
|
+
onCleanup(() => query.removeEventListener('change', updateWide))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
createEffect(() => {
|
|
51
|
+
const open = props.open
|
|
52
|
+
const selectedId = props.selectedId
|
|
53
|
+
const selectedEntry = selected()
|
|
54
|
+
if (!open) {
|
|
55
|
+
setExpandedGroups(new Set<string>())
|
|
56
|
+
setLastSelectedId(undefined)
|
|
57
|
+
setReturnKey(undefined)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (selectedId && selectedEntry) {
|
|
61
|
+
const previous = lastSelectedId()
|
|
62
|
+
setLastSelectedId(selectedId)
|
|
63
|
+
if (selectedId !== previous) {
|
|
64
|
+
window.requestAnimationFrame(() => {
|
|
65
|
+
if (props.open && props.selectedId === selectedId) backButton?.focus()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const previous = lastSelectedId()
|
|
71
|
+
if (!previous) return
|
|
72
|
+
const key = returnKey()
|
|
73
|
+
setLastSelectedId(undefined)
|
|
74
|
+
setReturnKey(undefined)
|
|
75
|
+
window.requestAnimationFrame(() => {
|
|
76
|
+
if (!props.open || props.selectedId) return
|
|
77
|
+
const row = key ? rowRefs.get(key) : undefined
|
|
78
|
+
if (row?.isConnected) row.focus()
|
|
79
|
+
else searchInput?.focus()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const registerRow = (key: string, element: HTMLButtonElement | undefined) => {
|
|
84
|
+
if (element) rowRefs.set(key, element)
|
|
85
|
+
else rowRefs.delete(key)
|
|
86
|
+
}
|
|
87
|
+
const selectEntry = (key: string, value: Value) => {
|
|
88
|
+
setReturnKey(key)
|
|
89
|
+
props.onSelect(value)
|
|
90
|
+
}
|
|
91
|
+
const toggleGroup = (id: string) => {
|
|
92
|
+
setExpandedGroups((current) => {
|
|
93
|
+
const next = new Set(current)
|
|
94
|
+
if (next.has(id)) next.delete(id)
|
|
95
|
+
else next.add(id)
|
|
96
|
+
return next
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
const previewCount = () => (wide() ? 6 : 3)
|
|
100
|
+
const countLabel = () =>
|
|
101
|
+
props.status === 'loading' ? props.loadingLabel : props.resultLabel(props.resultCount)
|
|
102
|
+
const supplementalContent = () => props.renderSupplementalView?.(props.tab)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div class={cn('flex min-h-0 min-w-0 flex-1 flex-col', props.class)}>
|
|
106
|
+
<Show
|
|
107
|
+
when={selected()}
|
|
108
|
+
fallback={
|
|
109
|
+
<Tabs
|
|
110
|
+
class="flex min-h-0 min-w-0 flex-1 flex-col gap-0"
|
|
111
|
+
value={props.tab}
|
|
112
|
+
onChange={(value) => value && props.onTabChange(value)}
|
|
113
|
+
>
|
|
114
|
+
<TabsList aria-label={props.tabsLabel} class="ms-auto max-w-full flex-wrap px-4 pt-2">
|
|
115
|
+
<For each={props.tabs}>
|
|
116
|
+
{(tab) => <TabsTrigger value={tab.id}>{tab.label}</TabsTrigger>}
|
|
117
|
+
</For>
|
|
118
|
+
</TabsList>
|
|
119
|
+
<TabsContent value={props.tab} class="flex min-h-0 min-w-0 flex-1 flex-col gap-3">
|
|
120
|
+
<Show
|
|
121
|
+
when={supplemental()}
|
|
122
|
+
fallback={
|
|
123
|
+
<>
|
|
124
|
+
<Show when={props.installError}>
|
|
125
|
+
{(message) => (
|
|
126
|
+
<p class="text-destructive px-4 pt-2 text-sm" role="alert">
|
|
127
|
+
{message()}
|
|
128
|
+
</p>
|
|
129
|
+
)}
|
|
130
|
+
</Show>
|
|
131
|
+
<div class="flex min-w-0 shrink-0 flex-wrap items-center gap-2 px-4 pt-2">
|
|
132
|
+
{props.filterControl}
|
|
133
|
+
<InputGroup class="min-w-0 flex-1 sm:max-w-md">
|
|
134
|
+
<InputGroupAddon>
|
|
135
|
+
<Search aria-hidden="true" />
|
|
136
|
+
</InputGroupAddon>
|
|
137
|
+
<InputGroupInput
|
|
138
|
+
ref={(element) => (searchInput = element)}
|
|
139
|
+
type="search"
|
|
140
|
+
aria-label={props.searchLabel}
|
|
141
|
+
placeholder={props.searchPlaceholder}
|
|
142
|
+
value={props.query}
|
|
143
|
+
onInput={(event) => props.onQueryChange(event.currentTarget.value)}
|
|
144
|
+
/>
|
|
145
|
+
</InputGroup>
|
|
146
|
+
<span class="text-muted-foreground ms-auto text-sm" role="status">
|
|
147
|
+
{countLabel()}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<ScrollArea
|
|
151
|
+
role="region"
|
|
152
|
+
aria-label={props.resultsRegionLabel}
|
|
153
|
+
orientation="vertical"
|
|
154
|
+
class="flex min-h-0 min-w-0 flex-1 flex-col gap-5 px-4 pb-4"
|
|
155
|
+
>
|
|
156
|
+
<Show when={props.status === 'error'}>
|
|
157
|
+
<Empty
|
|
158
|
+
class="min-h-0 min-w-0 flex-1"
|
|
159
|
+
role={props.catalogError.role ?? 'alert'}
|
|
160
|
+
>
|
|
161
|
+
<EmptyHeader>
|
|
162
|
+
<EmptyMedia variant="icon">
|
|
163
|
+
<Blocks aria-hidden="true" />
|
|
164
|
+
</EmptyMedia>
|
|
165
|
+
<EmptyTitle>{props.catalogError.title}</EmptyTitle>
|
|
166
|
+
<EmptyDescription>{props.catalogError.description}</EmptyDescription>
|
|
167
|
+
</EmptyHeader>
|
|
168
|
+
<Show when={props.catalogErrorAction}>
|
|
169
|
+
<EmptyContent>{props.catalogErrorAction}</EmptyContent>
|
|
170
|
+
</Show>
|
|
171
|
+
</Empty>
|
|
172
|
+
</Show>
|
|
173
|
+
<Show when={props.status === 'loading'}>
|
|
174
|
+
<CatalogLoadingState label={props.loadingLabel} />
|
|
175
|
+
</Show>
|
|
176
|
+
<Show when={props.status === 'ready'}>
|
|
177
|
+
<Show
|
|
178
|
+
when={props.resultCount > 0}
|
|
179
|
+
fallback={
|
|
180
|
+
<Empty class="min-h-0 min-w-0 flex-1" role="status">
|
|
181
|
+
<EmptyHeader>
|
|
182
|
+
<EmptyMedia variant="icon">
|
|
183
|
+
<Blocks aria-hidden="true" />
|
|
184
|
+
</EmptyMedia>
|
|
185
|
+
<EmptyTitle>{props.emptyState.title}</EmptyTitle>
|
|
186
|
+
<EmptyDescription>{props.emptyState.description}</EmptyDescription>
|
|
187
|
+
</EmptyHeader>
|
|
188
|
+
<Show when={props.emptyStateAction}>
|
|
189
|
+
<EmptyContent>{props.emptyStateAction}</EmptyContent>
|
|
190
|
+
</Show>
|
|
191
|
+
</Empty>
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
<div class="grid min-w-0 gap-5">
|
|
195
|
+
<For each={groupRows()}>
|
|
196
|
+
{(row) => (
|
|
197
|
+
<CatalogBrowserGroup
|
|
198
|
+
group={row.item()}
|
|
199
|
+
expanded={expandedGroups().has(row.item().id)}
|
|
200
|
+
previewCount={previewCount()}
|
|
201
|
+
disabled={props.interactionDisabled ?? false}
|
|
202
|
+
installedLabel={props.installedLabel}
|
|
203
|
+
publishedByLabel={props.publishedByLabel}
|
|
204
|
+
showMoreLabel={props.showMoreLabel}
|
|
205
|
+
showLessLabel={props.showLessLabel}
|
|
206
|
+
renderIcon={props.renderIcon}
|
|
207
|
+
onSelect={(entry, key) => selectEntry(key, entry.value)}
|
|
208
|
+
onRef={registerRow}
|
|
209
|
+
onToggle={toggleGroup}
|
|
210
|
+
/>
|
|
211
|
+
)}
|
|
212
|
+
</For>
|
|
213
|
+
</div>
|
|
214
|
+
</Show>
|
|
215
|
+
</Show>
|
|
216
|
+
<For each={props.notices ?? []}>
|
|
217
|
+
{(notice) => (
|
|
218
|
+
<p class="text-muted-foreground text-sm" role={notice.role}>
|
|
219
|
+
{notice.message}
|
|
220
|
+
</p>
|
|
221
|
+
)}
|
|
222
|
+
</For>
|
|
223
|
+
</ScrollArea>
|
|
224
|
+
</>
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
{supplementalContent()}
|
|
228
|
+
</Show>
|
|
229
|
+
</TabsContent>
|
|
230
|
+
</Tabs>
|
|
231
|
+
}
|
|
232
|
+
>
|
|
233
|
+
{(entry) => (
|
|
234
|
+
<div class="flex min-h-0 min-w-0 flex-1 flex-col gap-3" data-catalog-browser-detail>
|
|
235
|
+
<Button
|
|
236
|
+
ref={(element) => (backButton = element)}
|
|
237
|
+
variant="ghost"
|
|
238
|
+
size="sm"
|
|
239
|
+
onClick={() => props.onBack()}
|
|
240
|
+
class="mx-4 mt-2 self-start"
|
|
241
|
+
>
|
|
242
|
+
<ArrowLeft data-icon="inline-start" aria-hidden="true" />
|
|
243
|
+
{props.backLabel}
|
|
244
|
+
</Button>
|
|
245
|
+
<ScrollArea
|
|
246
|
+
role="region"
|
|
247
|
+
aria-label={props.detailRegionLabel}
|
|
248
|
+
orientation="vertical"
|
|
249
|
+
class="min-h-0 min-w-0 flex-1 px-4 pb-4"
|
|
250
|
+
>
|
|
251
|
+
{props.renderDetail(entry().value)}
|
|
252
|
+
</ScrollArea>
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</Show>
|
|
256
|
+
</div>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createUniqueId, Show, type JSX } from 'solid-js'
|
|
2
|
+
import { Badge } from '../../ui/badge'
|
|
3
|
+
import { Card, CardContent, CardFooter, CardHeader } from '../../ui/card'
|
|
4
|
+
|
|
5
|
+
/** Shared item summary and action frame for catalog-owned detail content. */
|
|
6
|
+
export function CatalogDetail(props: {
|
|
7
|
+
title: string
|
|
8
|
+
description: string
|
|
9
|
+
category: string
|
|
10
|
+
publisher: string
|
|
11
|
+
publishedByLabel: (publisher: string) => string
|
|
12
|
+
leading?: JSX.Element
|
|
13
|
+
eyebrow?: string
|
|
14
|
+
badges?: JSX.Element
|
|
15
|
+
action?: JSX.Element
|
|
16
|
+
status?: JSX.Element
|
|
17
|
+
children?: JSX.Element
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<article class="grid min-w-0 gap-4" data-catalog-detail>
|
|
21
|
+
<Card>
|
|
22
|
+
<CardHeader class="flex-row items-start gap-3">
|
|
23
|
+
<Show when={props.leading}>
|
|
24
|
+
<span class="shrink-0">{props.leading}</span>
|
|
25
|
+
</Show>
|
|
26
|
+
<div class="grid min-w-0 gap-1.5">
|
|
27
|
+
<div class="flex min-w-0 flex-wrap items-center gap-1.5">
|
|
28
|
+
<Show when={props.eyebrow}>
|
|
29
|
+
<Badge variant="outline">{props.eyebrow}</Badge>
|
|
30
|
+
</Show>
|
|
31
|
+
<Badge variant="secondary">{props.category}</Badge>
|
|
32
|
+
{props.badges}
|
|
33
|
+
</div>
|
|
34
|
+
<h2 class="text-base leading-tight font-semibold tracking-tight">{props.title}</h2>
|
|
35
|
+
<p class="text-muted-foreground text-sm">{props.description}</p>
|
|
36
|
+
<p class="text-muted-foreground text-xs">{props.publishedByLabel(props.publisher)}</p>
|
|
37
|
+
</div>
|
|
38
|
+
</CardHeader>
|
|
39
|
+
<CardFooter class="flex-wrap justify-between gap-3">
|
|
40
|
+
{props.action}
|
|
41
|
+
{props.status}
|
|
42
|
+
</CardFooter>
|
|
43
|
+
</Card>
|
|
44
|
+
<div class="grid min-w-0 gap-3">{props.children}</div>
|
|
45
|
+
</article>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A shared surface and heading for host-specific fields, permissions, and copy. */
|
|
50
|
+
export function CatalogDetailSection(props: { title: string; children?: JSX.Element }) {
|
|
51
|
+
const headingId = `catalog-detail-section-${createUniqueId()}`
|
|
52
|
+
return (
|
|
53
|
+
<section class="min-w-0" aria-labelledby={headingId}>
|
|
54
|
+
<Card>
|
|
55
|
+
<CardHeader>
|
|
56
|
+
<h3 id={headingId} class="text-sm font-semibold tracking-tight">
|
|
57
|
+
{props.title}
|
|
58
|
+
</h3>
|
|
59
|
+
</CardHeader>
|
|
60
|
+
<CardContent class="grid min-w-0 gap-3">{props.children}</CardContent>
|
|
61
|
+
</Card>
|
|
62
|
+
</section>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
/** A host-owned catalog record reduced to the fields needed by the browser. */
|
|
4
|
+
export type CatalogBrowserEntry<Value> = Readonly<{
|
|
5
|
+
id: string
|
|
6
|
+
value: Value
|
|
7
|
+
name: string
|
|
8
|
+
description: string
|
|
9
|
+
category: string
|
|
10
|
+
publisher: string
|
|
11
|
+
installed: boolean
|
|
12
|
+
}>
|
|
13
|
+
|
|
14
|
+
/** One stable, host-ordered category in the browser. */
|
|
15
|
+
export type CatalogBrowserGroup<Value> = Readonly<{
|
|
16
|
+
id: string
|
|
17
|
+
label: string
|
|
18
|
+
entries: readonly CatalogBrowserEntry<Value>[]
|
|
19
|
+
}>
|
|
20
|
+
|
|
21
|
+
export type CatalogBrowserTab = Readonly<{
|
|
22
|
+
id: string
|
|
23
|
+
label: string
|
|
24
|
+
/** A host-rendered view, such as navigation settings, outside the catalog. */
|
|
25
|
+
kind?: 'catalog' | 'supplemental'
|
|
26
|
+
}>
|
|
27
|
+
|
|
28
|
+
export type CatalogBrowserNotice = Readonly<{
|
|
29
|
+
id: string
|
|
30
|
+
role: 'alert' | 'status'
|
|
31
|
+
message: string
|
|
32
|
+
}>
|
|
33
|
+
|
|
34
|
+
export type CatalogBrowserMessage = Readonly<{
|
|
35
|
+
title: string
|
|
36
|
+
description: string
|
|
37
|
+
role?: 'alert' | 'status'
|
|
38
|
+
}>
|
|
39
|
+
|
|
40
|
+
export type CatalogBrowserProps<Value> = Readonly<{
|
|
41
|
+
/** Optional layout hook for the host's containing surface. */
|
|
42
|
+
class?: string
|
|
43
|
+
/** Whether the containing host surface is open; closing it clears view-only state. */
|
|
44
|
+
open: boolean
|
|
45
|
+
tabs: readonly CatalogBrowserTab[]
|
|
46
|
+
tab: string
|
|
47
|
+
tabsLabel: string
|
|
48
|
+
onTabChange: (tab: string) => void
|
|
49
|
+
/** A host-owned view is rendered in the same tab frame without catalog assumptions. */
|
|
50
|
+
renderSupplementalView?: (tab: string) => JSX.Element | undefined
|
|
51
|
+
query: string
|
|
52
|
+
onQueryChange: (query: string) => void
|
|
53
|
+
searchLabel: string
|
|
54
|
+
searchPlaceholder: string
|
|
55
|
+
resultsRegionLabel: string
|
|
56
|
+
filterControl?: JSX.Element
|
|
57
|
+
resultCount: number
|
|
58
|
+
resultLabel: (count: number) => string
|
|
59
|
+
loadingLabel: string
|
|
60
|
+
status: 'loading' | 'ready' | 'error'
|
|
61
|
+
catalogError: CatalogBrowserMessage
|
|
62
|
+
catalogErrorAction?: JSX.Element
|
|
63
|
+
installError?: string
|
|
64
|
+
notices?: readonly CatalogBrowserNotice[]
|
|
65
|
+
emptyState: CatalogBrowserMessage
|
|
66
|
+
emptyStateAction?: JSX.Element
|
|
67
|
+
groups: readonly CatalogBrowserGroup<Value>[]
|
|
68
|
+
/** All selectable records, including records omitted by the current filter. */
|
|
69
|
+
entries: readonly CatalogBrowserEntry<Value>[]
|
|
70
|
+
selectedId: string | null
|
|
71
|
+
onSelect: (value: Value) => void
|
|
72
|
+
onBack: () => void
|
|
73
|
+
backLabel: string
|
|
74
|
+
detailRegionLabel: string
|
|
75
|
+
installedLabel: string
|
|
76
|
+
publishedByLabel: (publisher: string) => string
|
|
77
|
+
showMoreLabel: (entries: readonly CatalogBrowserEntry<Value>[], remainingCount: number) => string
|
|
78
|
+
showLessLabel: string
|
|
79
|
+
/** Temporarily disables catalog controls while a host menu owns interaction. */
|
|
80
|
+
interactionDisabled?: boolean
|
|
81
|
+
renderIcon: (value: Value) => JSX.Element
|
|
82
|
+
renderDetail: (value: Value) => JSX.Element
|
|
83
|
+
}>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CatalogBrowser } from './catalog-browser'
|
|
2
|
+
export { CatalogDetail, CatalogDetailSection } from './catalog-detail'
|
|
3
|
+
export type {
|
|
4
|
+
CatalogBrowserEntry,
|
|
5
|
+
CatalogBrowserGroup,
|
|
6
|
+
CatalogBrowserMessage,
|
|
7
|
+
CatalogBrowserNotice,
|
|
8
|
+
CatalogBrowserProps,
|
|
9
|
+
CatalogBrowserTab,
|
|
10
|
+
} from './catalog-types'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Resizable from '@corvu/resizable'
|
|
2
|
+
import { cn } from '../../../lib/utils'
|
|
3
|
+
|
|
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
|
+
}
|
|
15
|
+
|
|
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
|
+
*/
|
|
24
|
+
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
|
+
)
|
|
59
|
+
}
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1558,6 +1558,7 @@
|
|
|
1558
1558
|
"title": "Sidebar Nav",
|
|
1559
1559
|
"description": "",
|
|
1560
1560
|
"dependencies": [
|
|
1561
|
+
"@corvu/resizable@^0.2.5",
|
|
1561
1562
|
"@kobalte/core@^0.13.14",
|
|
1562
1563
|
"lucide-solid@^1.48.0"
|
|
1563
1564
|
],
|
|
@@ -1570,6 +1571,11 @@
|
|
|
1570
1571
|
"type": "registry:ui",
|
|
1571
1572
|
"target": "components/layout/sidebar-nav/index.ts"
|
|
1572
1573
|
},
|
|
1574
|
+
{
|
|
1575
|
+
"path": "src/components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx",
|
|
1576
|
+
"type": "registry:ui",
|
|
1577
|
+
"target": "components/layout/sidebar-nav/sidebar-nav-resize-handle.tsx"
|
|
1578
|
+
},
|
|
1573
1579
|
{
|
|
1574
1580
|
"path": "src/components/layout/sidebar-nav/sidebar-nav.tsx",
|
|
1575
1581
|
"type": "registry:ui",
|
|
@@ -1786,6 +1792,53 @@
|
|
|
1786
1792
|
}
|
|
1787
1793
|
]
|
|
1788
1794
|
},
|
|
1795
|
+
{
|
|
1796
|
+
"name": "catalog-browser",
|
|
1797
|
+
"type": "registry:ui",
|
|
1798
|
+
"title": "Catalog Browser",
|
|
1799
|
+
"description": "",
|
|
1800
|
+
"dependencies": [
|
|
1801
|
+
"lucide-solid@^1.48.0"
|
|
1802
|
+
],
|
|
1803
|
+
"registryDependencies": [
|
|
1804
|
+
"@adea-ai/ui/badge",
|
|
1805
|
+
"@adea-ai/ui/button",
|
|
1806
|
+
"@adea-ai/ui/card",
|
|
1807
|
+
"@adea-ai/ui/empty",
|
|
1808
|
+
"@adea-ai/ui/input-group",
|
|
1809
|
+
"@adea-ai/ui/scroll-area",
|
|
1810
|
+
"@adea-ai/ui/skeleton",
|
|
1811
|
+
"@adea-ai/ui/tabs",
|
|
1812
|
+
"lib"
|
|
1813
|
+
],
|
|
1814
|
+
"files": [
|
|
1815
|
+
{
|
|
1816
|
+
"path": "src/components/composites/catalog-browser/catalog-browser-group.tsx",
|
|
1817
|
+
"type": "registry:ui",
|
|
1818
|
+
"target": "components/composites/catalog-browser/catalog-browser-group.tsx"
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
"path": "src/components/composites/catalog-browser/catalog-browser.tsx",
|
|
1822
|
+
"type": "registry:ui",
|
|
1823
|
+
"target": "components/composites/catalog-browser/catalog-browser.tsx"
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
"path": "src/components/composites/catalog-browser/catalog-detail.tsx",
|
|
1827
|
+
"type": "registry:ui",
|
|
1828
|
+
"target": "components/composites/catalog-browser/catalog-detail.tsx"
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
"path": "src/components/composites/catalog-browser/catalog-types.ts",
|
|
1832
|
+
"type": "registry:ui",
|
|
1833
|
+
"target": "components/composites/catalog-browser/catalog-types.ts"
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
"path": "src/components/composites/catalog-browser/index.ts",
|
|
1837
|
+
"type": "registry:ui",
|
|
1838
|
+
"target": "components/composites/catalog-browser/index.ts"
|
|
1839
|
+
}
|
|
1840
|
+
]
|
|
1841
|
+
},
|
|
1789
1842
|
{
|
|
1790
1843
|
"name": "list-row",
|
|
1791
1844
|
"type": "registry:ui",
|