@squadbase/vantage 0.6.1 → 0.6.2-dev1.31accb1

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 (183) hide show
  1. package/dist/add-DY4AXANK.js +6 -0
  2. package/dist/add-DY4AXANK.js.map +1 -0
  3. package/dist/{build-O5TVNH4E.js → build-TVKFIVFQ.js} +8 -8
  4. package/dist/{build-O5TVNH4E.js.map → build-TVKFIVFQ.js.map} +1 -1
  5. package/dist/{check-WFBEDHDR.js → check-3KHZXZMQ.js} +6 -6
  6. package/dist/{check-WFBEDHDR.js.map → check-3KHZXZMQ.js.map} +1 -1
  7. package/dist/{chunk-PLST5Q7T.js → chunk-AG6RZ2RR.js} +1010 -23
  8. package/dist/chunk-AG6RZ2RR.js.map +1 -0
  9. package/dist/{chunk-KLEIVDUV.js → chunk-CEXAQQ5W.js} +150 -11
  10. package/dist/chunk-CEXAQQ5W.js.map +1 -0
  11. package/dist/{chunk-JGRMRXXZ.js → chunk-JKLLFVEU.js} +3 -3
  12. package/dist/{chunk-JGRMRXXZ.js.map → chunk-JKLLFVEU.js.map} +1 -1
  13. package/dist/{chunk-EP6JHXSR.js → chunk-LMUACBMA.js} +3 -3
  14. package/dist/{chunk-EP6JHXSR.js.map → chunk-LMUACBMA.js.map} +1 -1
  15. package/dist/{chunk-MXI3PZFT.js → chunk-OB6UBJXF.js} +156 -200
  16. package/dist/chunk-OB6UBJXF.js.map +1 -0
  17. package/dist/{chunk-BCE22JSM.js → chunk-P7W4LR3Z.js} +32 -13
  18. package/dist/chunk-P7W4LR3Z.js.map +1 -0
  19. package/dist/chunk-PXRX5PEC.js +54 -0
  20. package/dist/chunk-PXRX5PEC.js.map +1 -0
  21. package/dist/{chunk-3G43C6OL.js → chunk-QI3XQKHS.js} +39 -3
  22. package/dist/chunk-QI3XQKHS.js.map +1 -0
  23. package/dist/{chunk-QDZA5JSL.js → chunk-U2M6HGKQ.js} +3 -3
  24. package/dist/{chunk-QDZA5JSL.js.map → chunk-U2M6HGKQ.js.map} +1 -1
  25. package/dist/{chunk-T3FLAGI6.js → chunk-VA776YE5.js} +3 -3
  26. package/dist/{chunk-T3FLAGI6.js.map → chunk-VA776YE5.js.map} +1 -1
  27. package/dist/cli.js +14 -11
  28. package/dist/cli.js.map +1 -1
  29. package/dist/components/index.d.ts +223 -1
  30. package/dist/components/index.js +3 -2
  31. package/dist/{dev-NZRLOQCL.js → dev-GDKATCC7.js} +22 -11
  32. package/dist/dev-GDKATCC7.js.map +1 -0
  33. package/dist/doc/index.d.ts +4 -0
  34. package/dist/doc/index.js +107 -11
  35. package/dist/doc/index.js.map +1 -1
  36. package/dist/{docs-ZGDJUHJI.js → docs-EJJ3BECT.js} +3 -3
  37. package/dist/{docs-ZGDJUHJI.js.map → docs-EJJ3BECT.js.map} +1 -1
  38. package/dist/{doctor-VFKEMR54.js → doctor-WQXPXP6K.js} +7 -7
  39. package/dist/doctor-WQXPXP6K.js.map +1 -0
  40. package/dist/{preview-QDCIVVSR.js → preview-OGMCNQUO.js} +12 -4
  41. package/dist/preview-OGMCNQUO.js.map +1 -0
  42. package/dist/{routes-RHP5FVGD.js → routes-MWBWWOJ2.js} +5 -5
  43. package/dist/{routes-RHP5FVGD.js.map → routes-MWBWWOJ2.js.map} +1 -1
  44. package/dist/{search-RFBELGY7.js → search-6Y422SV6.js} +3 -3
  45. package/dist/{search-RFBELGY7.js.map → search-6Y422SV6.js.map} +1 -1
  46. package/dist/slide/index.js +61 -15
  47. package/dist/slide/index.js.map +1 -1
  48. package/dist/table/index.js +7 -5
  49. package/dist/table/index.js.map +1 -1
  50. package/dist/ui/index.d.ts +140 -10
  51. package/dist/ui/index.js +1406 -63
  52. package/dist/ui/index.js.map +1 -1
  53. package/dist/{upgrade-GF2P67M6.js → upgrade-HCANX4GQ.js} +6 -6
  54. package/dist/{upgrade-GF2P67M6.js.map → upgrade-HCANX4GQ.js.map} +1 -1
  55. package/dist/vite/index.js +3 -3
  56. package/docs/en/changelog.md +24 -0
  57. package/docs/en/cli-reference.md +30 -0
  58. package/docs/en/parts/page-shell.md +1 -1
  59. package/docs/en/routing.md +4 -2
  60. package/docs/index.json +291 -61
  61. package/docs/ja/changelog.md +50 -0
  62. package/docs/ja/cli-reference.md +29 -0
  63. package/docs/ja/components.md +33 -89
  64. package/docs/ja/page-types/csv.md +83 -0
  65. package/docs/ja/page-types/doc.md +83 -0
  66. package/docs/ja/page-types/excel.md +82 -0
  67. package/docs/ja/page-types/slide.md +132 -0
  68. package/docs/ja/page-types.md +47 -0
  69. package/docs/ja/parts/area-chart.md +61 -0
  70. package/docs/ja/parts/bar-chart.md +69 -0
  71. package/docs/ja/parts/bar-list.md +35 -0
  72. package/docs/ja/parts/category-bar.md +36 -0
  73. package/docs/ja/parts/donut-chart.md +47 -0
  74. package/docs/ja/parts/echart.md +3 -0
  75. package/docs/ja/parts/line-chart.md +58 -0
  76. package/docs/ja/parts/page-shell.md +1 -1
  77. package/docs/ja/parts/progress-circle.md +34 -0
  78. package/docs/ja/parts/sparkline.md +1 -1
  79. package/docs/ja/parts/tracker.md +32 -0
  80. package/docs/ja/routing.md +8 -84
  81. package/docs/ja/ui/alert-dialog.md +41 -0
  82. package/docs/ja/ui/avatar.md +31 -0
  83. package/docs/ja/ui/combobox.md +74 -0
  84. package/docs/ja/ui/drawer.md +41 -0
  85. package/docs/ja/ui/field.md +42 -0
  86. package/docs/ja/ui/hover-card.md +21 -0
  87. package/docs/ja/ui/kbd.md +16 -0
  88. package/docs/ja/ui/pagination.md +36 -0
  89. package/docs/ja/ui/radio-group.md +21 -0
  90. package/docs/ja/ui/toast.md +46 -0
  91. package/docs/ja/ui-and-theming.md +14 -7
  92. package/package.json +18 -12
  93. package/registry/ui/accordion.tsx +74 -0
  94. package/registry/ui/alert-dialog.tsx +189 -0
  95. package/registry/ui/alert.tsx +78 -0
  96. package/registry/ui/app-shell.tsx +281 -0
  97. package/registry/ui/area-chart.tsx +1015 -0
  98. package/registry/ui/avatar.tsx +110 -0
  99. package/registry/ui/badge.tsx +54 -0
  100. package/registry/ui/bar-chart.tsx +1013 -0
  101. package/registry/ui/bar-list.tsx +302 -0
  102. package/registry/ui/breadcrumb.tsx +126 -0
  103. package/registry/ui/button.tsx +60 -0
  104. package/registry/ui/calendar.tsx +221 -0
  105. package/registry/ui/card.tsx +105 -0
  106. package/registry/ui/category-bar.tsx +281 -0
  107. package/registry/ui/checkbox.tsx +28 -0
  108. package/registry/ui/collapsible.tsx +21 -0
  109. package/registry/ui/combobox.tsx +310 -0
  110. package/registry/ui/command.tsx +196 -0
  111. package/registry/ui/dashboard-card.tsx +174 -0
  112. package/registry/ui/data-table.tsx +16 -91
  113. package/registry/ui/date-range-picker.tsx +209 -0
  114. package/registry/ui/dialog.tsx +159 -0
  115. package/registry/ui/donut-chart.tsx +1004 -0
  116. package/registry/ui/drawer.tsx +229 -0
  117. package/registry/ui/dropdown-menu.tsx +272 -0
  118. package/registry/ui/echart.tsx +486 -0
  119. package/registry/ui/field.tsx +240 -0
  120. package/registry/ui/filter-bar.tsx +365 -0
  121. package/registry/ui/funnel-steps.tsx +274 -0
  122. package/registry/ui/hover-card.tsx +52 -0
  123. package/registry/ui/input-group.tsx +158 -0
  124. package/registry/ui/input.tsx +22 -0
  125. package/registry/ui/kbd.tsx +28 -0
  126. package/registry/ui/label.tsx +20 -0
  127. package/registry/ui/line-chart.tsx +1007 -0
  128. package/registry/ui/metric-value.tsx +54 -0
  129. package/registry/ui/multi-select.tsx +226 -0
  130. package/registry/ui/page-shell.tsx +227 -0
  131. package/registry/ui/pagination.tsx +138 -0
  132. package/registry/ui/placeholder.tsx +30 -0
  133. package/registry/ui/popover.tsx +90 -0
  134. package/registry/ui/progress-circle.tsx +272 -0
  135. package/registry/ui/progress.tsx +83 -0
  136. package/registry/ui/radio-group.tsx +39 -0
  137. package/registry/ui/scroll-area.tsx +55 -0
  138. package/registry/ui/searchable-select.tsx +209 -0
  139. package/registry/ui/section-header.tsx +41 -0
  140. package/registry/ui/segmented-control.tsx +71 -0
  141. package/registry/ui/select.tsx +253 -0
  142. package/registry/ui/separator.tsx +25 -0
  143. package/registry/ui/sheet.tsx +137 -0
  144. package/registry/ui/sidebar.tsx +723 -0
  145. package/registry/ui/skeleton.tsx +15 -0
  146. package/registry/ui/slider.tsx +54 -0
  147. package/registry/ui/sparkline.tsx +155 -0
  148. package/registry/ui/spinner.tsx +12 -0
  149. package/registry/ui/states.tsx +61 -0
  150. package/registry/ui/status-badge.tsx +79 -0
  151. package/registry/ui/switch.tsx +32 -0
  152. package/registry/ui/table.tsx +116 -0
  153. package/registry/ui/tabs.tsx +82 -0
  154. package/registry/ui/textarea.tsx +20 -0
  155. package/registry/ui/toast.tsx +250 -0
  156. package/registry/ui/toggle-group.tsx +89 -0
  157. package/registry/ui/toggle.tsx +45 -0
  158. package/registry/ui/tooltip.tsx +66 -0
  159. package/registry/ui/tracker.tsx +236 -0
  160. package/registry/ui/trend-indicator.tsx +62 -0
  161. package/skills/vantage-add-feature/SKILL.md +9 -7
  162. package/skills/vantage-design/SKILL.md +3 -1
  163. package/skills/vantage-design/references/format.md +1 -1
  164. package/skills/vantage-pitfalls/SKILL.md +5 -1
  165. package/skills/vantage-slide/SKILL.md +17 -0
  166. package/templates/AGENTS.md +10 -1
  167. package/templates/agents/api.md +29 -0
  168. package/templates/agents/csv.md +2 -1
  169. package/templates/agents/doc.md +6 -3
  170. package/templates/agents/page.md +22 -8
  171. package/templates/agents/slide.md +13 -1
  172. package/templates/agents/xlsx.md +2 -1
  173. package/theme.css +51 -1
  174. package/dist/add-EISGSZAH.js +0 -6
  175. package/dist/add-EISGSZAH.js.map +0 -1
  176. package/dist/chunk-3G43C6OL.js.map +0 -1
  177. package/dist/chunk-BCE22JSM.js.map +0 -1
  178. package/dist/chunk-KLEIVDUV.js.map +0 -1
  179. package/dist/chunk-MXI3PZFT.js.map +0 -1
  180. package/dist/chunk-PLST5Q7T.js.map +0 -1
  181. package/dist/dev-NZRLOQCL.js.map +0 -1
  182. package/dist/doctor-VFKEMR54.js.map +0 -1
  183. package/dist/preview-QDCIVVSR.js.map +0 -1
@@ -0,0 +1,189 @@
1
+ // Copied by `vantage add ui alert-dialog`. This file is yours to edit.
2
+ // Generated from @squadbase/vantage src/ui/alert-dialog.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
3
+ "use client"
4
+
5
+ import * as React from "react"
6
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
7
+ import { cn } from "@squadbase/vantage/ui"
8
+
9
+ import { Button } from "@squadbase/vantage/ui"
10
+
11
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
12
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
13
+ }
14
+
15
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
16
+ return (
17
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
18
+ )
19
+ }
20
+
21
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
22
+ return (
23
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
24
+ )
25
+ }
26
+
27
+ function AlertDialogOverlay({
28
+ className,
29
+ ...props
30
+ }: AlertDialogPrimitive.Backdrop.Props) {
31
+ return (
32
+ <AlertDialogPrimitive.Backdrop
33
+ data-slot="alert-dialog-overlay"
34
+ className={cn(
35
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ )
41
+ }
42
+
43
+ function AlertDialogContent({
44
+ className,
45
+ size = "default",
46
+ ...props
47
+ }: AlertDialogPrimitive.Popup.Props & {
48
+ size?: "default" | "sm"
49
+ }) {
50
+ return (
51
+ <AlertDialogPortal>
52
+ <AlertDialogOverlay />
53
+ <AlertDialogPrimitive.Popup
54
+ data-slot="alert-dialog-content"
55
+ data-size={size}
56
+ className={cn(
57
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ </AlertDialogPortal>
63
+ )
64
+ }
65
+
66
+ function AlertDialogHeader({
67
+ className,
68
+ ...props
69
+ }: React.ComponentProps<"div">) {
70
+ return (
71
+ <div
72
+ data-slot="alert-dialog-header"
73
+ className={cn(
74
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function AlertDialogFooter({
83
+ className,
84
+ ...props
85
+ }: React.ComponentProps<"div">) {
86
+ return (
87
+ <div
88
+ data-slot="alert-dialog-footer"
89
+ className={cn(
90
+ "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ function AlertDialogMedia({
99
+ className,
100
+ ...props
101
+ }: React.ComponentProps<"div">) {
102
+ return (
103
+ <div
104
+ data-slot="alert-dialog-media"
105
+ className={cn(
106
+ "mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
107
+ className
108
+ )}
109
+ {...props}
110
+ />
111
+ )
112
+ }
113
+
114
+ function AlertDialogTitle({
115
+ className,
116
+ ...props
117
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
118
+ return (
119
+ <AlertDialogPrimitive.Title
120
+ data-slot="alert-dialog-title"
121
+ className={cn(
122
+ "cn-font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
123
+ className
124
+ )}
125
+ {...props}
126
+ />
127
+ )
128
+ }
129
+
130
+ function AlertDialogDescription({
131
+ className,
132
+ ...props
133
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
134
+ return (
135
+ <AlertDialogPrimitive.Description
136
+ data-slot="alert-dialog-description"
137
+ className={cn(
138
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function AlertDialogAction({
147
+ className,
148
+ ...props
149
+ }: React.ComponentProps<typeof Button>) {
150
+ return (
151
+ <Button
152
+ data-slot="alert-dialog-action"
153
+ className={cn(className)}
154
+ {...props}
155
+ />
156
+ )
157
+ }
158
+
159
+ function AlertDialogCancel({
160
+ className,
161
+ variant = "outline",
162
+ size = "default",
163
+ ...props
164
+ }: AlertDialogPrimitive.Close.Props &
165
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
166
+ return (
167
+ <AlertDialogPrimitive.Close
168
+ data-slot="alert-dialog-cancel"
169
+ className={cn(className)}
170
+ render={<Button variant={variant} size={size} />}
171
+ {...props}
172
+ />
173
+ )
174
+ }
175
+
176
+ export {
177
+ AlertDialog,
178
+ AlertDialogAction,
179
+ AlertDialogCancel,
180
+ AlertDialogContent,
181
+ AlertDialogDescription,
182
+ AlertDialogFooter,
183
+ AlertDialogHeader,
184
+ AlertDialogMedia,
185
+ AlertDialogOverlay,
186
+ AlertDialogPortal,
187
+ AlertDialogTitle,
188
+ AlertDialogTrigger,
189
+ }
@@ -0,0 +1,78 @@
1
+ // Copied by `vantage add ui alert`. This file is yours to edit.
2
+ // Generated from @squadbase/vantage src/ui/alert.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "@squadbase/vantage/ui"
7
+
8
+ const alertVariants = cva(
9
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-card text-card-foreground",
14
+ destructive:
15
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: "default",
20
+ },
21
+ }
22
+ )
23
+
24
+ function Alert({
25
+ className,
26
+ variant,
27
+ ...props
28
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
29
+ return (
30
+ <div
31
+ data-slot="alert"
32
+ role="alert"
33
+ className={cn(alertVariants({ variant }), className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
40
+ return (
41
+ <div
42
+ data-slot="alert-title"
43
+ className={cn(
44
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+
52
+ function AlertDescription({
53
+ className,
54
+ ...props
55
+ }: React.ComponentProps<"div">) {
56
+ return (
57
+ <div
58
+ data-slot="alert-description"
59
+ className={cn(
60
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
69
+ return (
70
+ <div
71
+ data-slot="alert-action"
72
+ className={cn("absolute top-2 right-2", className)}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ export { Alert, AlertTitle, AlertDescription, AlertAction }
@@ -0,0 +1,281 @@
1
+ // Copied by `vantage add ui app-shell`. This file is yours to edit.
2
+ // Generated from @squadbase/vantage src/components/app-shell.tsx by `npm run registry` (in Vantage, edit src/, not registry/).
3
+ /**
4
+ * AppShell — the outermost application frame. `variant="sidebar"` (default)
5
+ * renders a collapsible sidebar with nested nav groups; `variant="header"`
6
+ * collapses the same nav into a single select in a top bar.
7
+ *
8
+ * Navigation is router-agnostic: pass `linkComponent` to render links with your
9
+ * router's Link, or leave it and `onNavigate` handles the header variant.
10
+ */
11
+ import { forwardRef, type ElementType, type ReactNode } from "react"
12
+ import { ChevronRight } from "lucide-react"
13
+
14
+ import { cn } from "@squadbase/vantage/ui"
15
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@squadbase/vantage/ui"
16
+ import { Separator } from "@squadbase/vantage/ui"
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectGroup,
21
+ SelectItem,
22
+ SelectLabel,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from "@squadbase/vantage/ui"
26
+ import {
27
+ Sidebar,
28
+ SidebarContent,
29
+ SidebarFooter,
30
+ SidebarGroup,
31
+ SidebarGroupLabel,
32
+ SidebarInset,
33
+ SidebarMenu,
34
+ SidebarMenuBadge,
35
+ SidebarMenuButton,
36
+ SidebarMenuItem,
37
+ SidebarMenuSub,
38
+ SidebarMenuSubButton,
39
+ SidebarMenuSubItem,
40
+ SidebarProvider,
41
+ SidebarRail,
42
+ SidebarTrigger,
43
+ } from "@squadbase/vantage/ui"
44
+ import type { NavGroup, NavItem } from "@squadbase/vantage/components"
45
+
46
+ export interface AppShellSidebarProps {
47
+ variant?: "sidebar"
48
+ groups: NavGroup[]
49
+ actions?: ReactNode
50
+ header?: ReactNode
51
+ children: ReactNode
52
+ defaultOpen?: boolean
53
+ className?: string
54
+ linkComponent?: ElementType
55
+ onNavigate?: (href: string) => void
56
+ }
57
+
58
+ export interface AppShellHeaderProps {
59
+ variant: "header"
60
+ groups: NavGroup[]
61
+ actions?: ReactNode
62
+ header?: ReactNode
63
+ children: ReactNode
64
+ className?: string
65
+ linkComponent?: ElementType
66
+ onNavigate?: (href: string) => void
67
+ }
68
+
69
+ export type AppShellProps = AppShellSidebarProps | AppShellHeaderProps
70
+
71
+ /** Nested groups collapse to their leaves — the select is a flat list. */
72
+ function flattenItems(item: NavItem): NavItem[] {
73
+ if (item.children && item.children.length > 0) {
74
+ return item.children.flatMap(flattenItems)
75
+ }
76
+ return [item]
77
+ }
78
+
79
+ function NavSelect({
80
+ groups,
81
+ onNavigate,
82
+ }: {
83
+ groups: NavGroup[]
84
+ onNavigate: (href: string) => void
85
+ }) {
86
+ const allItems = groups.flatMap((g) => g.items.flatMap(flattenItems))
87
+ const currentValue = allItems.find((item) => item.isActive)?.href ?? ""
88
+
89
+ return (
90
+ <Select
91
+ value={currentValue}
92
+ onValueChange={(href) => {
93
+ // Base UI reports `null` when the selection is cleared; there is no
94
+ // page to navigate to in that case.
95
+ if (href !== null) onNavigate(href)
96
+ }}
97
+ >
98
+ <SelectTrigger size="sm" className="w-48">
99
+ <SelectValue placeholder="ページを選択" />
100
+ </SelectTrigger>
101
+ {/*
102
+ Page titles are author-written and often longer than the 12rem trigger,
103
+ so the list is allowed to grow past the anchor width (bounded by the
104
+ viewport) instead of clamping every title to the trigger's width.
105
+ */}
106
+ <SelectContent
107
+ align="start"
108
+ className="w-auto min-w-(--anchor-width) max-w-[min(24rem,var(--available-width))]"
109
+ >
110
+ {groups.map((group, index) => (
111
+ <SelectGroup key={group.label ?? index}>
112
+ {group.label && <SelectLabel>{group.label}</SelectLabel>}
113
+ {group.items.flatMap(flattenItems).map((item) => (
114
+ <SelectItem key={item.href} value={item.href} disabled={item.disabled}>
115
+ {item.label}
116
+ </SelectItem>
117
+ ))}
118
+ </SelectGroup>
119
+ ))}
120
+ </SelectContent>
121
+ </Select>
122
+ )
123
+ }
124
+
125
+ function NavItemWithChildren({
126
+ item,
127
+ linkComponent: LinkComponent = "a",
128
+ }: {
129
+ item: NavItem
130
+ linkComponent?: ElementType
131
+ }) {
132
+ return (
133
+ <Collapsible
134
+ defaultOpen={item.isActive}
135
+ className="group/collapsible"
136
+ render={<SidebarMenuItem />}
137
+ >
138
+ <CollapsibleTrigger
139
+ render={
140
+ <SidebarMenuButton
141
+ tooltip={item.label}
142
+ aria-disabled={item.disabled}
143
+ className={cn(item.disabled && "pointer-events-none")}
144
+ />
145
+ }
146
+ >
147
+ {item.icon && <item.icon />}
148
+ <span>{item.label}</span>
149
+ <ChevronRight className="ml-auto transition-transform duration-200 group-data-open/collapsible:rotate-90" />
150
+ </CollapsibleTrigger>
151
+ {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
152
+ <CollapsibleContent>
153
+ <SidebarMenuSub>
154
+ {item.children!.map((child) => (
155
+ <SidebarMenuSubItem key={child.href}>
156
+ <SidebarMenuSubButton
157
+ isActive={child.isActive}
158
+ render={
159
+ <LinkComponent
160
+ href={child.href}
161
+ aria-disabled={child.disabled}
162
+ tabIndex={child.disabled ? -1 : undefined}
163
+ className={cn(child.disabled && "pointer-events-none opacity-50")}
164
+ />
165
+ }
166
+ >
167
+ {child.icon && <child.icon />}
168
+ <span>{child.label}</span>
169
+ </SidebarMenuSubButton>
170
+ </SidebarMenuSubItem>
171
+ ))}
172
+ </SidebarMenuSub>
173
+ </CollapsibleContent>
174
+ </Collapsible>
175
+ )
176
+ }
177
+
178
+ function NavItemLink({
179
+ item,
180
+ linkComponent: LinkComponent = "a",
181
+ }: {
182
+ item: NavItem
183
+ linkComponent?: ElementType
184
+ }) {
185
+ return (
186
+ <SidebarMenuItem>
187
+ <SidebarMenuButton
188
+ tooltip={item.label}
189
+ isActive={item.isActive}
190
+ render={
191
+ <LinkComponent
192
+ href={item.href}
193
+ aria-disabled={item.disabled}
194
+ tabIndex={item.disabled ? -1 : undefined}
195
+ className={cn(item.disabled && "pointer-events-none")}
196
+ />
197
+ }
198
+ >
199
+ {item.icon && <item.icon />}
200
+ <span>{item.label}</span>
201
+ </SidebarMenuButton>
202
+ {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
203
+ </SidebarMenuItem>
204
+ )
205
+ }
206
+
207
+ const defaultNavigate = (href: string) => {
208
+ window.location.assign(href)
209
+ }
210
+
211
+ export const AppShell = forwardRef<HTMLDivElement, AppShellProps>((props, ref) => {
212
+ const {
213
+ variant = "sidebar",
214
+ groups,
215
+ actions,
216
+ header,
217
+ children,
218
+ className,
219
+ linkComponent,
220
+ onNavigate = defaultNavigate,
221
+ } = props
222
+
223
+ if (variant === "header") {
224
+ return (
225
+ <div ref={ref} data-slot="app-shell" className={cn("flex h-full flex-col", className)}>
226
+ {header !== undefined ? (
227
+ header
228
+ ) : (
229
+ <header className="flex h-14 shrink-0 items-center gap-3 border-b px-4">
230
+ {groups.length > 0 && <NavSelect groups={groups} onNavigate={onNavigate} />}
231
+ {actions && <div className="ml-auto flex items-center gap-2">{actions}</div>}
232
+ </header>
233
+ )}
234
+ <main className="flex-1 overflow-auto">{children}</main>
235
+ </div>
236
+ )
237
+ }
238
+
239
+ const { defaultOpen = true } = props as AppShellSidebarProps
240
+
241
+ return (
242
+ <SidebarProvider defaultOpen={defaultOpen}>
243
+ <Sidebar data-slot="app-sidebar" collapsible="offcanvas">
244
+ <SidebarContent>
245
+ {groups.map((group, index) => (
246
+ <SidebarGroup key={group.label ?? index}>
247
+ {group.label && <SidebarGroupLabel>{group.label}</SidebarGroupLabel>}
248
+ <SidebarMenu>
249
+ {group.items.map((item) =>
250
+ item.children && item.children.length > 0 ? (
251
+ <NavItemWithChildren
252
+ key={item.href}
253
+ item={item}
254
+ linkComponent={linkComponent}
255
+ />
256
+ ) : (
257
+ <NavItemLink key={item.href} item={item} linkComponent={linkComponent} />
258
+ ),
259
+ )}
260
+ </SidebarMenu>
261
+ </SidebarGroup>
262
+ ))}
263
+ </SidebarContent>
264
+ {actions && <SidebarFooter>{actions}</SidebarFooter>}
265
+ <SidebarRail />
266
+ </Sidebar>
267
+ <SidebarInset ref={ref} data-slot="app-shell" className={className}>
268
+ {header !== undefined ? (
269
+ header
270
+ ) : (
271
+ <header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
272
+ <SidebarTrigger />
273
+ <Separator orientation="vertical" className="h-4" />
274
+ </header>
275
+ )}
276
+ <main className="flex-1 overflow-auto">{children}</main>
277
+ </SidebarInset>
278
+ </SidebarProvider>
279
+ )
280
+ })
281
+ AppShell.displayName = "AppShell"