@exegia/corpora-ui 0.10.0 → 0.11.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 (65) hide show
  1. package/dist-lib/components/agents/ai-sidebar.d.ts +2 -0
  2. package/dist-lib/components/agents/index.d.ts +12 -0
  3. package/dist-lib/components/agents/marquee-label.d.ts +4 -0
  4. package/dist-lib/components/agents/sidebar-icon.d.ts +2 -0
  5. package/dist-lib/components/agents/sidebar-row.d.ts +2 -0
  6. package/dist-lib/components/agents/type.d.ts +77 -0
  7. package/dist-lib/components/agents/use-ai-sidebar.d.ts +31 -0
  8. package/dist-lib/components/agents/utils.d.ts +16 -0
  9. package/dist-lib/components/beste/piece/browser-frame.d.ts +2 -0
  10. package/dist-lib/components/beste/piece/type.d.ts +9 -0
  11. package/dist-lib/components/beste/piece/utils.d.ts +3 -0
  12. package/dist-lib/components/blocks/auth/signup-block.d.ts +5 -5
  13. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +3 -56
  14. package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +3 -0
  15. package/dist-lib/components/blocks/nav/types.d.ts +60 -0
  16. package/dist-lib/components/blocks/nav/utils.d.ts +1 -0
  17. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +6 -1
  18. package/dist-lib/components/blocks/shell/index.d.ts +1 -0
  19. package/dist-lib/components/blocks/shell/shell-layout.d.ts +3 -0
  20. package/dist-lib/components/blocks/shell/type.d.ts +26 -0
  21. package/dist-lib/components/blocks/shell/utils.d.ts +1 -0
  22. package/dist-lib/components/composed/social-providers.d.ts +9 -1
  23. package/dist-lib/components/docs/demo-controls.d.ts +4 -4
  24. package/dist-lib/components/motion/animated-sidebar.d.ts +12 -1
  25. package/dist-lib/components/motion/popover-morph.d.ts +35 -0
  26. package/dist-lib/components/motion/popover-position.d.ts +15 -0
  27. package/dist-lib/components/ui/badge.d.ts +7 -0
  28. package/dist-lib/components/ui/select.d.ts +30 -0
  29. package/dist-lib/index.d.ts +1 -0
  30. package/dist-lib/index.js +644 -524
  31. package/dist-lib/index.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/agents/ai-sidebar.tsx +116 -0
  34. package/src/components/agents/index.ts +14 -0
  35. package/src/components/agents/marquee-label.tsx +59 -0
  36. package/src/components/agents/sidebar-icon.tsx +14 -0
  37. package/src/components/agents/sidebar-row.tsx +271 -0
  38. package/src/components/agents/type.ts +89 -0
  39. package/src/components/agents/use-ai-sidebar.ts +386 -0
  40. package/src/components/agents/utils.ts +142 -0
  41. package/src/components/beste/piece/browser-frame.tsx +55 -0
  42. package/src/components/beste/piece/type.ts +11 -0
  43. package/src/components/beste/piece/utils.ts +21 -0
  44. package/src/components/blocks/auth/linked-accounts-block.tsx +55 -57
  45. package/src/components/blocks/auth/passkey-manager-block.tsx +88 -81
  46. package/src/components/blocks/auth/signup-block.tsx +6 -6
  47. package/src/components/blocks/nav/sidebar-block.tsx +25 -155
  48. package/src/components/blocks/nav/sidebar-nav-row.tsx +79 -0
  49. package/src/components/blocks/nav/types.ts +65 -0
  50. package/src/components/blocks/nav/utils.ts +4 -0
  51. package/src/components/blocks/profile/profile-card-block.tsx +17 -6
  52. package/src/components/blocks/shell/index.ts +1 -0
  53. package/src/components/blocks/shell/shell-layout.tsx +115 -0
  54. package/src/components/blocks/shell/type.ts +50 -0
  55. package/src/components/blocks/shell/utils.ts +2 -0
  56. package/src/components/composed/social-providers.tsx +83 -27
  57. package/src/components/docs/component-preview.tsx +13 -12
  58. package/src/components/docs/demo-controls.tsx +37 -21
  59. package/src/components/motion/animated-sidebar.tsx +392 -364
  60. package/src/components/motion/popover-morph.tsx +292 -0
  61. package/src/components/motion/popover-position.ts +86 -0
  62. package/src/components/ui/badge.tsx +52 -0
  63. package/src/components/ui/select.tsx +257 -0
  64. package/src/index.css +18 -0
  65. package/src/index.ts +1 -0
@@ -12,149 +12,20 @@ import {
12
12
  AnimatedSidebarGroupLabel,
13
13
  AnimatedSidebarHeader,
14
14
  AnimatedSidebarMenu,
15
- AnimatedSidebarMenuButton,
16
- AnimatedSidebarMenuItem,
17
- AnimatedSidebarMenuSub,
18
- AnimatedSidebarMenuSubButton,
19
- AnimatedSidebarMenuSubItem,
20
15
  AnimatedSidebarProvider,
21
16
  AnimatedSidebarTrigger,
22
17
  } from "@/components/motion/animated-sidebar"
23
18
  import { cn } from "@/lib/utils"
19
+ import { SidebarNavRow } from "@/components/blocks/nav/sidebar-nav-row"
24
20
 
25
- /** A nested link, one level under a top-level entry. */
26
- export interface SidebarNavSubItem {
27
- id: string
28
- label: string
29
- icon?: React.ReactNode
30
- /** Renders an anchor. Without one the row is a button. */
31
- href?: string
32
- disabled?: boolean
33
- target?: "_blank" | "_self" | "_parent" | "_top"
34
- onSelect?: () => void
35
- }
36
-
37
- /** A top-level entry. With `items` it expands instead of navigating. */
38
- export interface SidebarNavItem extends SidebarNavSubItem {
39
- /** Trailing hint — a count, a "New" pill. Hidden while collapsed. */
40
- badge?: React.ReactNode
41
- items?: SidebarNavSubItem[]
42
- /** Start expanded. Defaults to expanded when one of its children is active. */
43
- defaultOpen?: boolean
44
- }
45
-
46
- /** A titled run of entries. The title hides while the rail is collapsed. */
47
- export interface SidebarNavSection {
48
- id: string
49
- label?: string
50
- items: SidebarNavItem[]
51
- }
52
-
53
- export interface SidebarBlockProps {
54
- sections: SidebarNavSection[]
55
- /** `id` of the current entry — matches top-level and nested ids alike. */
56
- activeId?: string
57
- /** Fires for every selection, after the entry's own `onSelect`. */
58
- onNavigate?: (item: SidebarNavItem | SidebarNavSubItem) => void
59
- /** Brand row above the navigation. */
60
- header?: React.ReactNode
61
- /** Pinned below the navigation — an account card, a version note. */
62
- footer?: React.ReactNode
63
- /** Collapse toggle in the header row. */
64
- showTrigger?: boolean
65
- side?: "left" | "right"
66
- variant?: "sidebar" | "floating" | "inset"
67
- /** How the rail collapses: to icons, off-canvas, or not at all. */
68
- collapsible?: "offcanvas" | "icon" | "none"
69
- open?: boolean
70
- defaultOpen?: boolean
71
- onOpenChange?: (open: boolean) => void
72
- /** Under 768px the panel is a drawer — drive it from your own header. */
73
- openMobile?: boolean
74
- defaultOpenMobile?: boolean
75
- onOpenMobileChange?: (open: boolean) => void
76
- /** Rail widths. CSS lengths, e.g. "16rem". */
77
- width?: string
78
- iconWidth?: string
79
- mobileWidth?: string
80
- ariaLabel?: string
81
- className?: string
82
- }
83
-
84
- function isActive(id: string, activeId?: string): boolean {
85
- return activeId !== undefined && id === activeId
86
- }
87
-
88
- interface RowProps {
89
- item: SidebarNavItem
90
- activeId?: string
91
- onNavigate?: (item: SidebarNavItem | SidebarNavSubItem) => void
92
- }
21
+ import type { ISidebarBlockProps } from "@/components/blocks/nav/types"
93
22
 
94
- function SidebarNavRow({ item, activeId, onNavigate }: RowProps) {
95
- const children = item.items ?? []
96
- const hasChildren = children.length > 0
97
- const childActive = children.some((child) => isActive(child.id, activeId))
98
- // null = follow the active child; a boolean = the reader has said otherwise.
99
- const [toggled, setToggled] = React.useState<boolean | null>(null)
100
- const [wasChildActive, setWasChildActive] = React.useState(childActive)
101
-
102
- // Navigating into a nested route from elsewhere reveals its parent, even if
103
- // the reader had collapsed it. Adjusted during render rather than in an
104
- // effect, so the group never paints closed for a frame first.
105
- if (childActive !== wasChildActive) {
106
- setWasChildActive(childActive)
107
- if (childActive) setToggled(null)
108
- }
109
-
110
- const open = toggled ?? item.defaultOpen ?? childActive
111
-
112
- function select(entry: SidebarNavItem | SidebarNavSubItem) {
113
- entry.onSelect?.()
114
- onNavigate?.(entry)
115
- }
116
-
117
- return (
118
- <AnimatedSidebarMenuItem>
119
- <AnimatedSidebarMenuButton
120
- ariaExpanded={hasChildren ? open : undefined}
121
- badge={item.badge}
122
- disabled={item.disabled}
123
- href={hasChildren ? undefined : item.href}
124
- icon={item.icon}
125
- isActive={isActive(item.id, activeId)}
126
- onSelect={() => {
127
- if (hasChildren) {
128
- setToggled(!open)
129
- return
130
- }
131
- select(item)
132
- }}
133
- target={item.target}
134
- >
135
- {item.label}
136
- </AnimatedSidebarMenuButton>
137
- {hasChildren ? (
138
- <AnimatedSidebarMenuSub open={open}>
139
- {children.map((child) => (
140
- <AnimatedSidebarMenuSubItem key={child.id}>
141
- <AnimatedSidebarMenuSubButton
142
- disabled={child.disabled}
143
- href={child.href}
144
- icon={child.icon}
145
- isActive={isActive(child.id, activeId)}
146
- onSelect={() => select(child)}
147
- target={child.target}
148
- >
149
- {child.label}
150
- </AnimatedSidebarMenuSubButton>
151
- </AnimatedSidebarMenuSubItem>
152
- ))}
153
- </AnimatedSidebarMenuSub>
154
- ) : null}
155
- </AnimatedSidebarMenuItem>
156
- )
157
- }
23
+ export type {
24
+ ISidebarBlockProps as SidebarBlockProps,
25
+ ISidebarNavItem as SidebarNavItem,
26
+ ISidebarNavSection as SidebarNavSection,
27
+ ISidebarNavSubItem as SidebarNavSubItem,
28
+ } from "@/components/blocks/nav/types"
158
29
 
159
30
  /**
160
31
  * Application sidebar navigation, driven by data rather than composition.
@@ -187,7 +58,7 @@ export function SidebarBlock({
187
58
  mobileWidth,
188
59
  ariaLabel = "Main",
189
60
  className,
190
- }: SidebarBlockProps): React.ReactElement {
61
+ }: ISidebarBlockProps): React.ReactElement {
191
62
  return (
192
63
  <AnimatedSidebarProvider
193
64
  // The provider ships a full-page flex shell; this block is just the
@@ -220,26 +91,27 @@ export function SidebarBlock({
220
91
  side={side}
221
92
  variant={variant}
222
93
  >
223
- {header || showTrigger ? (
224
- <AnimatedSidebarHeader>
225
- <div className="flex min-w-0 items-center justify-between gap-2">
226
- {header ? <div className="min-w-0 flex-1">{header}</div> : null}
227
- {showTrigger ? (
228
- <AnimatedSidebarTrigger className="text-muted-foreground hover:text-foreground">
229
- <PanelLeftIcon className="size-4.5" />
230
- </AnimatedSidebarTrigger>
231
- ) : null}
232
- </div>
233
- </AnimatedSidebarHeader>
234
- ) : null}
94
+ {header ||
95
+ (showTrigger && (
96
+ <AnimatedSidebarHeader>
97
+ <div className="flex min-w-0 items-center justify-between gap-2">
98
+ {header && <div className="min-w-0 flex-1">{header}</div>}
99
+ {showTrigger && (
100
+ <AnimatedSidebarTrigger className="text-muted-foreground hover:text-foreground">
101
+ <PanelLeftIcon className="size-4.5" />
102
+ </AnimatedSidebarTrigger>
103
+ )}
104
+ </div>
105
+ </AnimatedSidebarHeader>
106
+ ))}
235
107
  <AnimatedSidebarContent>
236
108
  {sections.map((section) => (
237
109
  <AnimatedSidebarGroup key={section.id}>
238
- {section.label ? (
110
+ {section.label && (
239
111
  <AnimatedSidebarGroupLabel>
240
112
  {section.label}
241
113
  </AnimatedSidebarGroupLabel>
242
- ) : null}
114
+ )}
243
115
  <AnimatedSidebarGroupContent>
244
116
  <AnimatedSidebarMenu>
245
117
  {section.items.map((item) => (
@@ -255,9 +127,7 @@ export function SidebarBlock({
255
127
  </AnimatedSidebarGroup>
256
128
  ))}
257
129
  </AnimatedSidebarContent>
258
- {footer ? (
259
- <AnimatedSidebarFooter>{footer}</AnimatedSidebarFooter>
260
- ) : null}
130
+ {footer && <AnimatedSidebarFooter>{footer}</AnimatedSidebarFooter>}
261
131
  </AnimatedSidebar>
262
132
  </AnimatedSidebarProvider>
263
133
  )
@@ -0,0 +1,79 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import {
6
+ AnimatedSidebarMenuButton,
7
+ AnimatedSidebarMenuItem,
8
+ AnimatedSidebarMenuSub,
9
+ AnimatedSidebarMenuSubButton,
10
+ AnimatedSidebarMenuSubItem,
11
+ } from "@/components/motion/animated-sidebar"
12
+
13
+ import type { IRowProps, ISidebarNavItem, ISidebarNavSubItem } from "@/components/blocks/nav/types"
14
+ import { isActive } from "@/components/blocks/nav/utils"
15
+
16
+ export function SidebarNavRow({ item, activeId, onNavigate }: IRowProps) {
17
+ const children = item.items ?? []
18
+ const hasChildren = children.length > 0
19
+ const childActive = children.some((child) => isActive(child.id, activeId))
20
+ // null = follow the active child; a boolean = the reader has said otherwise.
21
+ const [toggled, setToggled] = React.useState<boolean | null>(null)
22
+ const [wasChildActive, setWasChildActive] = React.useState(childActive)
23
+
24
+ // Navigating into a nested route from elsewhere reveals its parent, even if
25
+ // the reader had collapsed it. Adjusted during render rather than in an
26
+ // effect, so the group never paints closed for a frame first.
27
+ if (childActive !== wasChildActive) {
28
+ setWasChildActive(childActive)
29
+ if (childActive) setToggled(null)
30
+ }
31
+
32
+ const open = toggled ?? item.defaultOpen ?? childActive
33
+
34
+ function select(entry: ISidebarNavItem | ISidebarNavSubItem) {
35
+ entry.onSelect?.()
36
+ onNavigate?.(entry)
37
+ }
38
+
39
+ return (
40
+ <AnimatedSidebarMenuItem>
41
+ <AnimatedSidebarMenuButton
42
+ ariaExpanded={hasChildren ? open : undefined}
43
+ badge={item.badge}
44
+ disabled={item.disabled}
45
+ href={hasChildren ? undefined : item.href}
46
+ icon={item.icon}
47
+ isActive={isActive(item.id, activeId)}
48
+ onSelect={() => {
49
+ if (hasChildren) {
50
+ setToggled(!open)
51
+ return
52
+ }
53
+ select(item)
54
+ }}
55
+ target={item.target}
56
+ >
57
+ {item.label}
58
+ </AnimatedSidebarMenuButton>
59
+ {hasChildren ? (
60
+ <AnimatedSidebarMenuSub open={open}>
61
+ {children.map((child) => (
62
+ <AnimatedSidebarMenuSubItem key={child.id}>
63
+ <AnimatedSidebarMenuSubButton
64
+ disabled={child.disabled}
65
+ href={child.href}
66
+ icon={child.icon}
67
+ isActive={isActive(child.id, activeId)}
68
+ onSelect={() => select(child)}
69
+ target={child.target}
70
+ >
71
+ {child.label}
72
+ </AnimatedSidebarMenuSubButton>
73
+ </AnimatedSidebarMenuSubItem>
74
+ ))}
75
+ </AnimatedSidebarMenuSub>
76
+ ) : null}
77
+ </AnimatedSidebarMenuItem>
78
+ )
79
+ }
@@ -0,0 +1,65 @@
1
+
2
+ /** A nested link, one level under a top-level entry. */
3
+ export interface ISidebarNavSubItem {
4
+ id: string
5
+ label: string
6
+ icon?: React.ReactNode
7
+ /** Renders an anchor. Without one the row is a button. */
8
+ href?: string
9
+ disabled?: boolean
10
+ target?: "_blank" | "_self" | "_parent" | "_top"
11
+ onSelect?: () => void
12
+ }
13
+
14
+ /** A top-level entry. With `items` it expands instead of navigating. */
15
+ export interface ISidebarNavItem extends ISidebarNavSubItem {
16
+ /** Trailing hint — a count, a "New" pill. Hidden while collapsed. */
17
+ badge?: React.ReactNode
18
+ items?: ISidebarNavSubItem[]
19
+ /** Start expanded. Defaults to expanded when one of its children is active. */
20
+ defaultOpen?: boolean
21
+ }
22
+
23
+ /** A titled run of entries. The title hides while the rail is collapsed. */
24
+ export interface ISidebarNavSection {
25
+ id: string
26
+ label?: string
27
+ items: ISidebarNavItem[]
28
+ }
29
+
30
+ export interface ISidebarBlockProps {
31
+ sections: ISidebarNavSection[]
32
+ /** `id` of the current entry — matches top-level and nested ids alike. */
33
+ activeId?: string
34
+ /** Fires for every selection, after the entry's own `onSelect`. */
35
+ onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
36
+ /** Brand row above the navigation. */
37
+ header?: React.ReactNode
38
+ /** Pinned below the navigation — an account card, a version note. */
39
+ footer?: React.ReactNode
40
+ /** Collapse toggle in the header row. */
41
+ showTrigger?: boolean
42
+ side?: "left" | "right"
43
+ variant?: "sidebar" | "floating" | "inset"
44
+ /** How the rail collapses: to icons, off-canvas, or not at all. */
45
+ collapsible?: "offcanvas" | "icon" | "none"
46
+ open?: boolean
47
+ defaultOpen?: boolean
48
+ onOpenChange?: (open: boolean) => void
49
+ /** Under 768px the panel is a drawer — drive it from your own header. */
50
+ openMobile?: boolean
51
+ defaultOpenMobile?: boolean
52
+ onOpenMobileChange?: (open: boolean) => void
53
+ /** Rail widths. CSS lengths, e.g. "16rem". */
54
+ width?: string
55
+ iconWidth?: string
56
+ mobileWidth?: string
57
+ ariaLabel?: string
58
+ className?: string
59
+ }
60
+
61
+ export interface IRowProps {
62
+ item: ISidebarNavItem
63
+ activeId?: string
64
+ onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
65
+ }
@@ -0,0 +1,4 @@
1
+
2
+ export function isActive(id: string, activeId?: string): boolean {
3
+ return activeId !== undefined && id === activeId
4
+ }
@@ -109,6 +109,11 @@ export interface ProfileCardBlockProps {
109
109
  open?: boolean
110
110
  defaultOpen?: boolean
111
111
  onOpenChange?: MenuPrimitive.Root.Props["onOpenChange"]
112
+ /**
113
+ * "content" sizes the menu to its items; "card" locks it to the trigger's
114
+ * width, so `align` stops mattering horizontally.
115
+ */
116
+ menuWidth?: "content" | "card"
112
117
  /**
113
118
  * Emit cuelume press/release on the card and play the open/close cues.
114
119
  * Inert unless the app opts into interaction sound via `bindSounds()`.
@@ -158,6 +163,7 @@ export function ProfileCardBlock({
158
163
  align = "center",
159
164
  side = "bottom",
160
165
  sideOffset = 8,
166
+ menuWidth = "content",
161
167
  open,
162
168
  defaultOpen,
163
169
  onOpenChange,
@@ -212,13 +218,15 @@ export function ProfileCardBlock({
212
218
  // avatar sits inside the button and the handle is a second line.
213
219
  aria-label={`${user.name}${user.username ? ` ${user.username}` : ""}, account menu`}
214
220
  className={cn(
215
- "h-auto w-full justify-between py-1.5 pl-1.5 sm:h-auto",
221
+ // Quiet at rest — the background only surfaces on hover or
222
+ // while the menu is open.
223
+ "h-auto w-full justify-between py-1.5 pl-1.5 data-popup-open:bg-accent sm:h-auto",
216
224
  className
217
225
  )}
218
226
  data-slot="profile-card"
219
227
  loading={busy}
220
228
  sound={sound}
221
- variant="outline"
229
+ variant="ghost"
222
230
  >
223
231
  <span className="flex w-full min-w-0 items-center justify-start gap-2">
224
232
  <UserAvatar
@@ -243,11 +251,14 @@ export function ProfileCardBlock({
243
251
  />
244
252
  <DropdownMenuContent
245
253
  align={align}
246
- // Content-width menu (not the card's width) so `align` reads: a popup
247
- // locked to `--anchor-width` lands in the same place for every value.
248
- // `!` because the popup's own `not-[class*='w-']:min-w-32` compiles to
254
+ // Content-width by default so `align` reads: a popup locked to
255
+ // `--anchor-width` lands in the same place for every value. `!`
256
+ // because the popup's own `not-[class*='w-']:min-w-32` compiles to
249
257
  // a higher-specificity `:not(:is())` that would otherwise win.
250
- className="min-w-44! origin-(--transform-origin) transition-[opacity,scale,translate] duration-150 ease-smooth-out data-ending-style:scale-98 data-ending-style:opacity-0 data-ending-style:duration-100 data-starting-style:scale-98 data-starting-style:opacity-0 data-[side=bottom]:data-ending-style:-translate-y-1 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-ending-style:translate-y-1 data-[side=top]:data-starting-style:translate-y-1 motion-reduce:transition-none"
258
+ className={cn(
259
+ menuWidth === "card" ? "w-(--anchor-width)" : "min-w-44!",
260
+ "origin-(--transform-origin) transition-[opacity,scale,translate] duration-150 ease-smooth-out data-ending-style:scale-98 data-ending-style:opacity-0 data-ending-style:duration-100 data-starting-style:scale-98 data-starting-style:opacity-0 data-[side=bottom]:data-ending-style:-translate-y-1 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-ending-style:translate-y-1 data-[side=top]:data-starting-style:translate-y-1 motion-reduce:transition-none"
261
+ )}
251
262
  side={side}
252
263
  sideOffset={sideOffset}
253
264
  >
@@ -0,0 +1 @@
1
+ export * from "./shell-layout"
@@ -0,0 +1,115 @@
1
+ "use client"
2
+
3
+ import { MenuIcon, PanelRightIcon } from "lucide-react"
4
+ import * as React from "react"
5
+
6
+ import {
7
+ AnimatedSidebar,
8
+ AnimatedSidebarInset,
9
+ AnimatedSidebarProvider,
10
+ AnimatedSidebarTrigger,
11
+ } from "@/components/motion/animated-sidebar"
12
+ import { cn } from "@/lib/utils"
13
+ import type { ShellLayoutProps } from "./type"
14
+ import { TITLE_BAR_HEIGHT } from "./utils"
15
+ import type { ClassNameValue } from "tailwind-merge"
16
+
17
+ export function ShellLayout({
18
+ children,
19
+ open,
20
+ defaultOpen,
21
+ onOpenChange,
22
+ rightDrawer,
23
+ rightOpen,
24
+ defaultRightOpen,
25
+ onRightOpenChange,
26
+ variant = "desktop",
27
+ className,
28
+ }: ShellLayoutProps): React.ReactElement {
29
+
30
+ const background: ClassNameValue = `bg-linear-to-tr/increasing from-neutral-200 via-neutral-100 to-stone-200 dark:from-neutral-900 dark:via-neutral-950 dark:to-stone-950`
31
+ return (
32
+ <AnimatedSidebarProvider
33
+ className={cn("block-full", className, background, 'relative pr-2')}
34
+ defaultOpen={defaultOpen}
35
+ defaultOpenRight={defaultRightOpen}
36
+ onOpenChange={onOpenChange}
37
+ onOpenRightChange={onRightOpenChange}
38
+ open={open}
39
+ openRight={rightOpen}
40
+ style={{
41
+ paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0
42
+ }}
43
+ >
44
+ <AnimatedSidebar
45
+ ariaLabel="Primary navigation"
46
+ className={cn("")}
47
+ collapsible="icon"
48
+ role="navigation"
49
+ variant="inset"
50
+ ></AnimatedSidebar>
51
+
52
+ <AnimatedSidebarInset
53
+ className={cn(
54
+ "min-w-24 overflow-hidden border-t border-neutral-200 dark:border-neutral-700 inset-ring-1 inset-ring-neutral-50 dark:inset-ring-black bg-white dark:bg-card rounded-lg!",
55
+ `mx-0!`
56
+ )}
57
+ >
58
+ <header className="flex h-16 items-center justify-between gap-3 px-4 sm:px-6">
59
+ <div className="flex min-w-0 items-center gap-3">
60
+ <AnimatedSidebarTrigger>
61
+ <MenuIcon className="size-5" />
62
+ </AnimatedSidebarTrigger>
63
+ </div>
64
+ <div className="flex shrink-0 items-center gap-3">
65
+ <AnimatedSidebarTrigger aria-label="Toggle panel" side="right">
66
+ <PanelRightIcon className="size-5" />
67
+ </AnimatedSidebarTrigger>
68
+ </div>
69
+ </header>
70
+ <div className="min-h-24 flex-1 overflow-auto">
71
+ {children ?? <ShellLayoutPlaceholder />}
72
+ </div>
73
+ </AnimatedSidebarInset>
74
+
75
+
76
+
77
+ <AnimatedSidebar
78
+ ariaLabel="Secondary panel"
79
+ // Below md the panel is portal led over the page, so it carries the
80
+ // surface itself; the desktop rail keeps it on the inner panel.
81
+ className={cn("max-md:border-l max-md:border-neutral-200 max-md:bg-white dark:max-md:border-neutral-700 dark:max-md:bg-card")}
82
+ collapsible="offcanvas"
83
+ panelClassName={cn(
84
+ // The off canvas panel keeps a fixed width so it can slide out of the
85
+ // zero-width rail, so the inset gutter has to come out of that width
86
+ // rather than from a left margin.
87
+ "top-2 ml-0 h-[calc(100%-1rem)] min-w-[calc(var(--sidebar-width))]",
88
+ "rounded-lg border-t border-neutral-200 bg-white shadow-md inset-ring-1 inset-ring-neutral-50",
89
+ "dark:border-neutral-700 dark:bg-card dark:inset-ring-black"
90
+ )}
91
+ role="complementary"
92
+ side="right"
93
+ variant="inset"
94
+ >
95
+ {rightDrawer}
96
+ </AnimatedSidebar>
97
+ </AnimatedSidebarProvider>
98
+ )
99
+ }
100
+
101
+ function ShellLayoutPlaceholder() {
102
+ return (
103
+ <div className="grid gap-5">
104
+ <div className="grid gap-5 lg:grid-cols-3">
105
+ {Array.from({ length: 3 }).map((_, index) => (
106
+ <div
107
+ className="h-32 border border-border bg-background/60"
108
+ key={index}
109
+ />
110
+ ))}
111
+ </div>
112
+ <div className="h-72 border border-border bg-background/60" />
113
+ </div>
114
+ )
115
+ }
@@ -0,0 +1,50 @@
1
+ // import type {
2
+ // ISidebarNavItem,
3
+ // ISidebarNavSection,
4
+ // ISidebarNavSubItem,
5
+ // } from "@/components/blocks/nav/types"
6
+
7
+
8
+ export interface ShellAction {
9
+ id: string
10
+ label: string
11
+ icon: React.ReactNode
12
+ badge?: React.ReactNode
13
+ onSelect?: () => void
14
+ }
15
+
16
+ export interface ShellWorkspace {
17
+ name: string
18
+ logo?: React.ReactNode
19
+ meta?: React.ReactNode
20
+ }
21
+
22
+ export interface ShellLayoutProps {
23
+ children?: React.ReactNode
24
+ // sections?: ISidebarNavSection[]
25
+ //activeId?: string
26
+ // onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
27
+ // workspace?: ShellWorkspace
28
+ //sidebarFooter?: React.ReactNode
29
+ // topActions?: ShellAction[]
30
+ //title?: React.ReactNode
31
+ //eyebrow?: React.ReactNode
32
+ //searchPlaceholder?: string
33
+ //searchShortcut?: React.ReactNode
34
+ open?: boolean
35
+ defaultOpen?: boolean
36
+ onOpenChange?: (open: boolean) => void
37
+ /** Content of the right drawer. It has no icon rail — it is either fully
38
+ * shown or fully hidden. */
39
+ rightDrawer?: React.ReactNode
40
+ rightOpen?: boolean
41
+ defaultRightOpen?: boolean
42
+ onRightOpenChange?: (open: boolean) => void
43
+ // openMobile?: boolean
44
+ //defaultOpenMobile?: boolean
45
+ //onOpenMobileChange?: (open: boolean) => void
46
+ className?: string
47
+ variant: 'web' | 'desktop'
48
+ //sidebarClassName?: string
49
+ //contentClassName?: string
50
+ }
@@ -0,0 +1,2 @@
1
+
2
+ export const TITLE_BAR_HEIGHT = 48