tembro 4.0.1 → 4.1.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/CHANGELOG.md +15 -0
- package/dist/components/display/chat.cjs +1 -0
- package/dist/components/display/chat.d.ts +94 -0
- package/dist/components/display/chat.js +1 -0
- package/dist/components/display/index.d.ts +1 -0
- package/dist/components/display/kanban.d.ts +33 -1
- package/dist/components/layout/sidebar.d.ts +24 -2
- package/dist/components/modern/rich-text-editor.d.ts +13 -1
- package/dist/components/theme-provider.d.ts +19 -6
- package/dist/components/ui/badge/index.d.ts +8 -2
- package/dist/src/components/display/activity-feed.cjs +1 -1
- package/dist/src/components/display/activity-feed.js +4 -1
- package/dist/src/components/display/chat.cjs +1 -0
- package/dist/src/components/display/chat.js +372 -0
- package/dist/src/components/display/index.cjs +1 -1
- package/dist/src/components/display/index.js +17 -16
- package/dist/src/components/display/kanban.cjs +1 -1
- package/dist/src/components/display/kanban.js +321 -176
- package/dist/src/components/display/timeline.cjs +1 -1
- package/dist/src/components/display/timeline.js +5 -5
- package/dist/src/components/inputs/tag-input.cjs +1 -1
- package/dist/src/components/inputs/tag-input.js +6 -2
- package/dist/src/components/layout/index.cjs +1 -1
- package/dist/src/components/layout/index.js +4 -4
- package/dist/src/components/layout/public.cjs +1 -1
- package/dist/src/components/layout/public.js +4 -4
- package/dist/src/components/layout/sidebar.cjs +1 -1
- package/dist/src/components/layout/sidebar.js +326 -228
- package/dist/src/components/modern/rich-text-editor.cjs +1 -1
- package/dist/src/components/modern/rich-text-editor.js +216 -89
- package/dist/src/components/theme-provider.cjs +1 -1
- package/dist/src/components/theme-provider.js +58 -36
- package/dist/src/components/ui/badge/index.cjs +1 -1
- package/dist/src/components/ui/badge/index.js +59 -47
- package/dist/src/components/ui/input/clearable.cjs +1 -1
- package/dist/src/components/ui/input/clearable.js +5 -2
- package/dist/src/components/ui/input/primitive.cjs +1 -1
- package/dist/src/components/ui/input/primitive.js +1 -1
- package/dist/src/index.cjs +1 -1
- package/dist/src/index.js +87 -86
- package/dist/src/public-component-surface.cjs +1 -1
- package/dist/src/public-component-surface.js +5 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +3 -3
- package/packages/cli/vendor/src/components/display/activity-feed.tsx +1 -1
- package/packages/cli/vendor/src/components/display/chat.tsx +237 -0
- package/packages/cli/vendor/src/components/display/index.ts +1 -0
- package/packages/cli/vendor/src/components/display/kanban.tsx +219 -72
- package/packages/cli/vendor/src/components/display/timeline.tsx +10 -10
- package/packages/cli/vendor/src/components/inputs/tag-input.tsx +3 -2
- package/packages/cli/vendor/src/components/layout/sidebar.tsx +212 -51
- package/packages/cli/vendor/src/components/modern/rich-text-editor.tsx +115 -32
- package/packages/cli/vendor/src/components/theme-provider.tsx +82 -36
- package/packages/cli/vendor/src/components/ui/badge/index.tsx +29 -8
- package/packages/cli/vendor/src/components/ui/input/clearable.tsx +2 -2
- package/packages/cli/vendor/src/components/ui/input/primitive.tsx +1 -1
- package/packages/cli/vendor/src/public-component-surface.ts +1 -0
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
- package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
- package/packages/cli/vendor/templates/styles/globals.css +91 -10
- package/registry.json +3 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import { ChevronRightIcon, MenuIcon, XIcon } from "lucide-react"
|
|
4
|
+
import { ChevronRightIcon, MenuIcon, SearchIcon, XIcon } from "lucide-react"
|
|
5
5
|
|
|
6
6
|
import { useIsMobile } from "@/hooks/use-is-mobile"
|
|
7
|
+
import { Input } from "@/components/ui/input"
|
|
7
8
|
import { Tooltip } from "@/components/ui/tooltip"
|
|
8
9
|
import { cn } from "@/lib/utils"
|
|
9
10
|
import { ControllableDetails } from "./controllable-details"
|
|
@@ -24,9 +25,22 @@ export type SidebarItem = {
|
|
|
24
25
|
expanded?: boolean
|
|
25
26
|
onExpandedChange?: (expanded: boolean) => void
|
|
26
27
|
current?: React.AriaAttributes["aria-current"]
|
|
27
|
-
tooltip?: React.ReactNode
|
|
28
|
-
onSelect?: () => void
|
|
29
|
-
|
|
28
|
+
tooltip?: React.ReactNode
|
|
29
|
+
onSelect?: () => void
|
|
30
|
+
keywords?: string[]
|
|
31
|
+
action?: React.ReactNode
|
|
32
|
+
actionLabel?: string
|
|
33
|
+
onAction?: () => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type SidebarSearch = {
|
|
37
|
+
value?: string
|
|
38
|
+
defaultValue?: string
|
|
39
|
+
placeholder?: string
|
|
40
|
+
label?: string
|
|
41
|
+
onValueChange?: (value: string) => void
|
|
42
|
+
empty?: React.ReactNode
|
|
43
|
+
}
|
|
30
44
|
|
|
31
45
|
export type SidebarFooterAccount = {
|
|
32
46
|
label: React.ReactNode
|
|
@@ -41,7 +55,12 @@ export type SidebarProps = React.ComponentProps<"aside"> & {
|
|
|
41
55
|
header?: React.ReactNode
|
|
42
56
|
footer?: React.ReactNode
|
|
43
57
|
items?: SidebarItem[]
|
|
44
|
-
collapsed?: boolean
|
|
58
|
+
collapsed?: boolean
|
|
59
|
+
defaultCollapsed?: boolean
|
|
60
|
+
onCollapsedChange?: (collapsed: boolean) => void
|
|
61
|
+
variant?: "sidebar" | "floating" | "inset"
|
|
62
|
+
side?: "left" | "right"
|
|
63
|
+
collapsible?: "icon" | "offcanvas" | "none"
|
|
45
64
|
width?: React.CSSProperties["width"]
|
|
46
65
|
collapsedWidth?: React.CSSProperties["width"]
|
|
47
66
|
mobileWidth?: React.CSSProperties["width"]
|
|
@@ -55,6 +74,10 @@ export type SidebarProps = React.ComponentProps<"aside"> & {
|
|
|
55
74
|
itemSize?: "sm" | "md" | "lg"
|
|
56
75
|
activeIndicator?: "none" | "bar" | "pill"
|
|
57
76
|
navigationLabel?: string
|
|
77
|
+
search?: SidebarSearch | React.ReactNode
|
|
78
|
+
hideScrollbar?: boolean
|
|
79
|
+
keyboardShortcut?: string | false
|
|
80
|
+
persistKey?: string
|
|
58
81
|
responsive?: boolean
|
|
59
82
|
mobileBreakpoint?: number
|
|
60
83
|
mobileOpen?: boolean
|
|
@@ -154,7 +177,8 @@ function SidebarLeafItem({
|
|
|
154
177
|
renderLink?: SidebarProps["renderLink"]
|
|
155
178
|
}) {
|
|
156
179
|
const currentValue: React.AriaAttributes["aria-current"] = item.current ?? (item.active ? "page" : undefined)
|
|
157
|
-
const commonProps = {
|
|
180
|
+
const commonProps = {
|
|
181
|
+
"aria-label": collapsed && typeof item.label === "string" ? item.label : undefined,
|
|
158
182
|
"aria-current": currentValue,
|
|
159
183
|
"aria-disabled": item.disabled || undefined,
|
|
160
184
|
"data-slot": "sidebar-item" as const,
|
|
@@ -164,7 +188,7 @@ function SidebarLeafItem({
|
|
|
164
188
|
"data-size": itemSize,
|
|
165
189
|
"data-active-indicator": activeIndicator,
|
|
166
190
|
className: cn(
|
|
167
|
-
"flex items-center gap-2 border border-transparent font-medium outline-none transition-[background-color,border-color,color,box-shadow] data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
|
|
191
|
+
"flex min-w-0 flex-1 items-center gap-2 border border-transparent text-left font-medium outline-none transition-[background-color,border-color,color,box-shadow] data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
|
|
168
192
|
getSidebarInteractiveClassName({ active: item.active, disabled: item.disabled }),
|
|
169
193
|
getSidebarItemLayoutClassName({ collapsed, depth, itemSize, activeIndicator })
|
|
170
194
|
),
|
|
@@ -178,19 +202,41 @@ function SidebarLeafItem({
|
|
|
178
202
|
</>
|
|
179
203
|
)
|
|
180
204
|
|
|
181
|
-
const wrapCollapsedContent = (node: React.ReactNode) =>
|
|
205
|
+
const wrapCollapsedContent = (node: React.ReactNode) =>
|
|
182
206
|
collapsed ? (
|
|
183
207
|
<Tooltip content={item.tooltip ?? item.label} side="right">
|
|
184
208
|
<span className="block">{node}</span>
|
|
185
209
|
</Tooltip>
|
|
186
210
|
) : (
|
|
187
|
-
node
|
|
188
|
-
)
|
|
211
|
+
node
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
const wrapItemAction = (node: React.ReactNode) => {
|
|
215
|
+
if (collapsed || !item.action) return node
|
|
216
|
+
return (
|
|
217
|
+
<div data-slot="sidebar-item-row" className="group/sidebar-item-row flex min-w-0 items-center gap-1">
|
|
218
|
+
{node}
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
data-slot="sidebar-item-action"
|
|
222
|
+
aria-label={item.actionLabel ?? `Actions for ${String(item.label)}`}
|
|
223
|
+
className="grid size-8 shrink-0 place-items-center rounded-md p-0 leading-none text-muted-foreground opacity-0 outline-none transition hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-sidebar-ring group-hover/sidebar-item-row:opacity-100"
|
|
224
|
+
onClick={(event) => {
|
|
225
|
+
event.preventDefault()
|
|
226
|
+
event.stopPropagation()
|
|
227
|
+
item.onAction?.()
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
{item.action}
|
|
231
|
+
</button>
|
|
232
|
+
</div>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
189
235
|
|
|
190
236
|
if (item.href?.startsWith("/")) {
|
|
191
237
|
if (renderLink) {
|
|
192
|
-
return wrapCollapsedContent(
|
|
193
|
-
<>
|
|
238
|
+
return wrapCollapsedContent(wrapItemAction(
|
|
239
|
+
<>
|
|
194
240
|
{renderLink({
|
|
195
241
|
item,
|
|
196
242
|
href: item.href,
|
|
@@ -204,12 +250,12 @@ function SidebarLeafItem({
|
|
|
204
250
|
},
|
|
205
251
|
children: content,
|
|
206
252
|
})}
|
|
207
|
-
</>
|
|
208
|
-
)
|
|
253
|
+
</>
|
|
254
|
+
))
|
|
209
255
|
}
|
|
210
256
|
|
|
211
|
-
return wrapCollapsedContent(
|
|
212
|
-
<a
|
|
257
|
+
return wrapCollapsedContent(wrapItemAction(
|
|
258
|
+
<a
|
|
213
259
|
href={item.href}
|
|
214
260
|
{...commonProps}
|
|
215
261
|
onClick={(event) => {
|
|
@@ -221,13 +267,13 @@ function SidebarLeafItem({
|
|
|
221
267
|
}}
|
|
222
268
|
>
|
|
223
269
|
{content}
|
|
224
|
-
</a>
|
|
225
|
-
)
|
|
270
|
+
</a>
|
|
271
|
+
))
|
|
226
272
|
}
|
|
227
273
|
|
|
228
274
|
if (item.href) {
|
|
229
|
-
return wrapCollapsedContent(
|
|
230
|
-
<button
|
|
275
|
+
return wrapCollapsedContent(wrapItemAction(
|
|
276
|
+
<button
|
|
231
277
|
type="button"
|
|
232
278
|
{...commonProps}
|
|
233
279
|
className={cn(commonProps.className, "w-full")}
|
|
@@ -244,12 +290,12 @@ function SidebarLeafItem({
|
|
|
244
290
|
}}
|
|
245
291
|
>
|
|
246
292
|
{content}
|
|
247
|
-
</button>
|
|
248
|
-
)
|
|
293
|
+
</button>
|
|
294
|
+
))
|
|
249
295
|
}
|
|
250
296
|
|
|
251
|
-
return wrapCollapsedContent(
|
|
252
|
-
<button
|
|
297
|
+
return wrapCollapsedContent(wrapItemAction(
|
|
298
|
+
<button
|
|
253
299
|
type="button"
|
|
254
300
|
disabled={item.disabled}
|
|
255
301
|
{...commonProps}
|
|
@@ -259,8 +305,8 @@ function SidebarLeafItem({
|
|
|
259
305
|
}}
|
|
260
306
|
>
|
|
261
307
|
{content}
|
|
262
|
-
</button>
|
|
263
|
-
)
|
|
308
|
+
</button>
|
|
309
|
+
))
|
|
264
310
|
}
|
|
265
311
|
|
|
266
312
|
function SidebarTree({
|
|
@@ -333,12 +379,13 @@ function SidebarTree({
|
|
|
333
379
|
onOpenChange={item.onExpandedChange}
|
|
334
380
|
>
|
|
335
381
|
<summary
|
|
382
|
+
aria-label={collapsed && typeof item.label === "string" ? item.label : undefined}
|
|
336
383
|
data-slot="sidebar-group-trigger"
|
|
337
384
|
data-size={itemSize}
|
|
338
385
|
data-active={active || undefined}
|
|
339
386
|
data-active-indicator={activeIndicator}
|
|
340
387
|
className={cn(
|
|
341
|
-
"flex list-none items-center gap-2 border border-transparent font-medium outline-none transition-[background-color,border-color,color,box-shadow]",
|
|
388
|
+
"flex list-none items-center gap-2 border border-transparent text-left font-medium outline-none transition-[background-color,border-color,color,box-shadow]",
|
|
342
389
|
getSidebarInteractiveClassName({ active }),
|
|
343
390
|
getSidebarItemLayoutClassName({ collapsed, depth, itemSize, activeIndicator })
|
|
344
391
|
)}
|
|
@@ -379,6 +426,22 @@ function SidebarTree({
|
|
|
379
426
|
})
|
|
380
427
|
}
|
|
381
428
|
|
|
429
|
+
function filterSidebarItems(items: SidebarItem[], query: string): SidebarItem[] {
|
|
430
|
+
const normalized = query.trim().toLocaleLowerCase()
|
|
431
|
+
if (!normalized) return items
|
|
432
|
+
|
|
433
|
+
return items.flatMap((item) => {
|
|
434
|
+
const children = item.items ? filterSidebarItems(item.items, normalized) : []
|
|
435
|
+
const label = typeof item.label === "string" ? item.label : ""
|
|
436
|
+
const haystack = [label, item.sectionLabel, ...(item.keywords ?? [])]
|
|
437
|
+
.filter((value): value is string => typeof value === "string")
|
|
438
|
+
.join(" ")
|
|
439
|
+
.toLocaleLowerCase()
|
|
440
|
+
if (!haystack.includes(normalized) && children.length === 0) return []
|
|
441
|
+
return [{ ...item, items: children.length ? children : item.items, defaultExpanded: children.length ? true : item.defaultExpanded }]
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
|
|
382
445
|
function SidebarActionButton({
|
|
383
446
|
item,
|
|
384
447
|
collapsed,
|
|
@@ -481,7 +544,7 @@ function SidebarFooterAccount({
|
|
|
481
544
|
)
|
|
482
545
|
}
|
|
483
546
|
|
|
484
|
-
function SidebarSurface({
|
|
547
|
+
function SidebarSurface({
|
|
485
548
|
className,
|
|
486
549
|
style,
|
|
487
550
|
header,
|
|
@@ -501,6 +564,11 @@ function SidebarSurface({
|
|
|
501
564
|
itemSize = "md",
|
|
502
565
|
activeIndicator = "bar",
|
|
503
566
|
navigationLabel = "Primary navigation",
|
|
567
|
+
search,
|
|
568
|
+
hideScrollbar = true,
|
|
569
|
+
variant = "sidebar",
|
|
570
|
+
side = "left",
|
|
571
|
+
collapsible = "icon",
|
|
504
572
|
onItemSelect,
|
|
505
573
|
renderItem,
|
|
506
574
|
renderLink,
|
|
@@ -515,8 +583,11 @@ function SidebarSurface({
|
|
|
515
583
|
}: Omit<SidebarProps, "responsive" | "mobileBreakpoint" | "mobileOpen" | "defaultMobileOpen" | "onMobileOpenChange" | "mobileToggleLabel" | "mobileToggleIcon" | "showMobileToggle" | "mobileToggleClassName" | "mobilePanelClassName" | "mobileOverlayClassName" | "renderMobileToggle"> & {
|
|
516
584
|
mobile?: boolean
|
|
517
585
|
onRequestClose?: () => void
|
|
518
|
-
}) {
|
|
519
|
-
const
|
|
586
|
+
}) {
|
|
587
|
+
const searchConfig = search && !React.isValidElement(search) && typeof search === "object" ? search as SidebarSearch : undefined
|
|
588
|
+
const [internalSearch, setInternalSearch] = React.useState(searchConfig?.defaultValue ?? "")
|
|
589
|
+
const searchValue = searchConfig?.value ?? internalSearch
|
|
590
|
+
const visibleItems = filterSidebarItems(items.filter((item) => !item.hidden), searchValue)
|
|
520
591
|
const visibleRailItems = railItems.filter((item) => !item.hidden)
|
|
521
592
|
const visibleSecondaryActions = secondaryActions.filter((item) => !item.hidden)
|
|
522
593
|
|
|
@@ -528,14 +599,25 @@ function SidebarSurface({
|
|
|
528
599
|
const showMobileHeader = mobile && (mobileTitle || mobileDescription || onRequestClose)
|
|
529
600
|
|
|
530
601
|
return (
|
|
531
|
-
<aside
|
|
602
|
+
<aside
|
|
532
603
|
data-slot="sidebar"
|
|
533
|
-
data-collapsed={collapsed || undefined}
|
|
534
|
-
data-mobile={mobile || undefined}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
604
|
+
data-collapsed={collapsed || undefined}
|
|
605
|
+
data-mobile={mobile || undefined}
|
|
606
|
+
data-variant={variant}
|
|
607
|
+
data-side={side}
|
|
608
|
+
data-collapsible={collapsible}
|
|
609
|
+
className={cn(
|
|
610
|
+
"peer group/sidebar flex h-full min-h-0 flex-col overflow-hidden bg-sidebar text-sidebar-foreground transition-[width,min-width,transform,border-radius,box-shadow] duration-200",
|
|
611
|
+
side === "right" && "border-l border-r-0",
|
|
612
|
+
!mobile && variant === "floating" && "m-2 h-[calc(100%-1rem)] rounded-xl border shadow-lg",
|
|
613
|
+
!mobile && variant === "inset" && "m-2 h-[calc(100%-1rem)] rounded-lg border bg-sidebar/92 shadow-sm",
|
|
614
|
+
mobile && "m-0 h-full rounded-none border-y-0",
|
|
615
|
+
collapsible === "offcanvas" && collapsed && (side === "left" ? "-translate-x-full" : "translate-x-full"),
|
|
616
|
+
className
|
|
617
|
+
)}
|
|
618
|
+
style={{
|
|
619
|
+
width: collapsed ? (collapsible === "icon" ? collapsedWidth : collapsible === "offcanvas" ? 0 : width) : width,
|
|
620
|
+
minWidth: collapsed ? (collapsible === "icon" ? collapsedWidth : collapsible === "offcanvas" ? 0 : width) : width,
|
|
539
621
|
...style,
|
|
540
622
|
}}
|
|
541
623
|
{...props}
|
|
@@ -569,12 +651,34 @@ function SidebarSurface({
|
|
|
569
651
|
) : null}
|
|
570
652
|
</div>
|
|
571
653
|
</div>
|
|
572
|
-
)}
|
|
573
|
-
|
|
654
|
+
)}
|
|
655
|
+
|
|
656
|
+
{search && !collapsed ? (
|
|
657
|
+
<div data-slot="sidebar-search" className="shrink-0 px-2 pt-2">
|
|
658
|
+
{React.isValidElement(search) ? search : (
|
|
659
|
+
<Input
|
|
660
|
+
kind="search"
|
|
661
|
+
value={searchValue}
|
|
662
|
+
onValueChange={(nextValue) => {
|
|
663
|
+
if (searchConfig?.value === undefined) setInternalSearch(nextValue)
|
|
664
|
+
searchConfig?.onValueChange?.(nextValue)
|
|
665
|
+
}}
|
|
666
|
+
placeholder={searchConfig?.placeholder ?? "Search navigation..."}
|
|
667
|
+
searchIcon={<SearchIcon />}
|
|
668
|
+
aria-label={searchConfig?.label ?? "Search navigation"}
|
|
669
|
+
className="h-9 bg-sidebar-accent/45"
|
|
670
|
+
/>
|
|
671
|
+
)}
|
|
672
|
+
</div>
|
|
673
|
+
) : null}
|
|
674
|
+
|
|
574
675
|
<nav
|
|
575
676
|
data-slot="sidebar-nav"
|
|
576
677
|
aria-label={navigationLabel}
|
|
577
|
-
className=
|
|
678
|
+
className={cn(
|
|
679
|
+
"flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overscroll-contain p-2 [scrollbar-gutter:stable]",
|
|
680
|
+
hideScrollbar && "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
681
|
+
)}
|
|
578
682
|
>
|
|
579
683
|
{children ??
|
|
580
684
|
visibleItems.map((item) => {
|
|
@@ -591,7 +695,7 @@ function SidebarSurface({
|
|
|
591
695
|
</Tooltip>
|
|
592
696
|
)
|
|
593
697
|
})}
|
|
594
|
-
{!children && !renderItem && (
|
|
698
|
+
{!children && !renderItem && (
|
|
595
699
|
<SidebarTree
|
|
596
700
|
items={visibleItems}
|
|
597
701
|
collapsed={collapsed}
|
|
@@ -602,7 +706,12 @@ function SidebarSurface({
|
|
|
602
706
|
onItemSelect={handleSelect}
|
|
603
707
|
renderLink={renderLink}
|
|
604
708
|
/>
|
|
605
|
-
)}
|
|
709
|
+
)}
|
|
710
|
+
{!children && visibleItems.length === 0 ? (
|
|
711
|
+
<div data-slot="sidebar-search-empty" className="grid min-h-24 place-items-center px-4 text-center text-xs text-muted-foreground">
|
|
712
|
+
{searchConfig?.empty ?? "No navigation items found."}
|
|
713
|
+
</div>
|
|
714
|
+
) : null}
|
|
606
715
|
</nav>
|
|
607
716
|
|
|
608
717
|
{(footerAccount || footerSecondary || footer || visibleSecondaryActions.length > 0 || (collapsed && (collapsedRail || visibleRailItems.length > 0))) && (
|
|
@@ -670,8 +779,13 @@ function Sidebar({
|
|
|
670
779
|
className,
|
|
671
780
|
header,
|
|
672
781
|
footer,
|
|
673
|
-
items = [],
|
|
782
|
+
items = [],
|
|
674
783
|
collapsed: collapsedProp,
|
|
784
|
+
defaultCollapsed = false,
|
|
785
|
+
onCollapsedChange,
|
|
786
|
+
variant = "sidebar",
|
|
787
|
+
side = "left",
|
|
788
|
+
collapsible = "icon",
|
|
675
789
|
width = DEFAULT_SIDEBAR_WIDTH,
|
|
676
790
|
collapsedWidth = DEFAULT_COLLAPSED_SIDEBAR_WIDTH,
|
|
677
791
|
mobileWidth = DEFAULT_MOBILE_SIDEBAR_WIDTH,
|
|
@@ -685,6 +799,10 @@ function Sidebar({
|
|
|
685
799
|
itemSize = "md",
|
|
686
800
|
activeIndicator = "bar",
|
|
687
801
|
navigationLabel = "Primary navigation",
|
|
802
|
+
search,
|
|
803
|
+
hideScrollbar = true,
|
|
804
|
+
keyboardShortcut = "b",
|
|
805
|
+
persistKey,
|
|
688
806
|
responsive = true,
|
|
689
807
|
mobileBreakpoint = DEFAULT_SIDEBAR_BREAKPOINT,
|
|
690
808
|
mobileOpen: mobileOpenProp,
|
|
@@ -708,11 +826,38 @@ function Sidebar({
|
|
|
708
826
|
...props
|
|
709
827
|
}: SidebarProps) {
|
|
710
828
|
const sidebarContext = useSidebar(true)
|
|
711
|
-
const
|
|
829
|
+
const [internalCollapsed, setInternalCollapsed] = React.useState(defaultCollapsed)
|
|
830
|
+
const collapsed = collapsible === "none" ? false : collapsedProp ?? sidebarContext?.collapsed ?? internalCollapsed
|
|
712
831
|
const matchesMobileBreakpoint = useIsMobile(mobileBreakpoint)
|
|
713
832
|
const isMobile = responsive && matchesMobileBreakpoint
|
|
714
833
|
const [uncontrolledMobileOpen, setUncontrolledMobileOpen] = React.useState(defaultMobileOpen)
|
|
715
834
|
const mobileOpen = mobileOpenProp ?? sidebarContext?.mobileOpen ?? uncontrolledMobileOpen
|
|
835
|
+
|
|
836
|
+
const setCollapsed = React.useCallback((nextCollapsed: boolean) => {
|
|
837
|
+
if (collapsible === "none") return
|
|
838
|
+
if (collapsedProp === undefined && sidebarContext) sidebarContext.setCollapsed(nextCollapsed)
|
|
839
|
+
else if (collapsedProp === undefined) setInternalCollapsed(nextCollapsed)
|
|
840
|
+
onCollapsedChange?.(nextCollapsed)
|
|
841
|
+
if (persistKey) window.localStorage.setItem(persistKey, String(nextCollapsed))
|
|
842
|
+
}, [collapsedProp, collapsible, onCollapsedChange, persistKey, sidebarContext])
|
|
843
|
+
|
|
844
|
+
React.useEffect(() => {
|
|
845
|
+
if (!persistKey || collapsedProp !== undefined) return
|
|
846
|
+
const stored = window.localStorage.getItem(persistKey)
|
|
847
|
+
if (stored === "true" || stored === "false") setCollapsed(stored === "true")
|
|
848
|
+
}, [collapsedProp, persistKey, setCollapsed])
|
|
849
|
+
|
|
850
|
+
React.useEffect(() => {
|
|
851
|
+
if (!keyboardShortcut || collapsible === "none") return
|
|
852
|
+
const handleShortcut = (event: KeyboardEvent) => {
|
|
853
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLocaleLowerCase() === keyboardShortcut.toLocaleLowerCase()) {
|
|
854
|
+
event.preventDefault()
|
|
855
|
+
setCollapsed(!collapsed)
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
window.addEventListener("keydown", handleShortcut)
|
|
859
|
+
return () => window.removeEventListener("keydown", handleShortcut)
|
|
860
|
+
}, [collapsed, collapsible, keyboardShortcut, setCollapsed])
|
|
716
861
|
|
|
717
862
|
const setMobileOpen = React.useCallback((nextOpen: boolean) => {
|
|
718
863
|
if (mobileOpenProp == null && sidebarContext) {
|
|
@@ -766,6 +911,11 @@ function Sidebar({
|
|
|
766
911
|
itemSize,
|
|
767
912
|
activeIndicator,
|
|
768
913
|
navigationLabel,
|
|
914
|
+
search,
|
|
915
|
+
hideScrollbar,
|
|
916
|
+
variant,
|
|
917
|
+
side,
|
|
918
|
+
collapsible,
|
|
769
919
|
mobileCloseLabel,
|
|
770
920
|
closeOnSelect,
|
|
771
921
|
onItemSelect,
|
|
@@ -833,21 +983,32 @@ function Sidebar({
|
|
|
833
983
|
role="dialog"
|
|
834
984
|
aria-modal="true"
|
|
835
985
|
aria-label={typeof mobileTitle === "string" ? mobileTitle : "Navigation"}
|
|
836
|
-
className={cn(
|
|
837
|
-
"fixed inset-y-0
|
|
838
|
-
|
|
986
|
+
className={cn(
|
|
987
|
+
"fixed inset-y-0 z-50 max-w-[22rem] border-[color:var(--aui-divider,var(--border))] bg-[color:var(--aui-page-bg,var(--background))] shadow-2xl transition-transform duration-200 ease-out",
|
|
988
|
+
side === "left" ? "left-0 border-r" : "right-0 border-l",
|
|
989
|
+
mobileOpen ? "translate-x-0" : side === "left" ? "-translate-x-full" : "translate-x-full",
|
|
839
990
|
mobilePanelClassName,
|
|
840
991
|
className
|
|
841
992
|
)}
|
|
842
993
|
style={{
|
|
843
994
|
width: mobileWidth,
|
|
844
995
|
minWidth: mobileWidth,
|
|
845
|
-
translate: mobileOpen ? "0 0" : "-100% 0",
|
|
996
|
+
translate: mobileOpen ? "0 0" : side === "left" ? "-100% 0" : "100% 0",
|
|
846
997
|
}}
|
|
847
998
|
/>
|
|
848
999
|
</div>
|
|
849
1000
|
</>
|
|
850
1001
|
)
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|
1005
|
+
return (
|
|
1006
|
+
<main
|
|
1007
|
+
data-slot="sidebar-inset"
|
|
1008
|
+
className={cn("relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:border md:peer-data-[variant=inset]:shadow-sm", className)}
|
|
1009
|
+
{...props}
|
|
1010
|
+
/>
|
|
1011
|
+
)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export { Sidebar, SidebarInset }
|