create-kuckit-app 0.2.1 → 0.3.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 (23) hide show
  1. package/package.json +1 -1
  2. package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +47 -10
  3. package/templates/base/apps/web/src/components/dashboard/app-sidebar.tsx +120 -0
  4. package/templates/base/apps/web/src/components/dashboard/dashboard-layout.tsx +46 -0
  5. package/templates/base/apps/web/src/components/dashboard/dashboard-overview.tsx +24 -0
  6. package/templates/base/apps/web/src/components/dashboard/index.ts +2 -0
  7. package/templates/base/apps/web/src/components/dashboard/nav-user.tsx +77 -0
  8. package/templates/base/apps/web/src/components/ui/avatar.tsx +39 -0
  9. package/templates/base/apps/web/src/components/ui/breadcrumb.tsx +102 -0
  10. package/templates/base/apps/web/src/components/ui/collapsible.tsx +21 -0
  11. package/templates/base/apps/web/src/components/ui/separator.tsx +26 -0
  12. package/templates/base/apps/web/src/components/ui/sheet.tsx +130 -0
  13. package/templates/base/apps/web/src/components/ui/sidebar.tsx +694 -0
  14. package/templates/base/apps/web/src/components/ui/skeleton.tsx +13 -0
  15. package/templates/base/apps/web/src/components/ui/tooltip.tsx +55 -0
  16. package/templates/base/apps/web/src/hooks/use-mobile.ts +19 -0
  17. package/templates/base/apps/web/src/lib/utils.ts +6 -0
  18. package/templates/base/apps/web/src/modules.client.ts +1 -1
  19. package/templates/base/apps/web/src/providers/KuckitProvider.tsx +1 -25
  20. package/templates/base/apps/web/src/routes/dashboard/$.tsx +9 -0
  21. package/templates/base/apps/web/src/routes/dashboard/index.tsx +6 -0
  22. package/templates/base/apps/web/src/routes/dashboard.tsx +25 -0
  23. package/templates/base/apps/web/src/lib/kuckit-router.ts +0 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kuckit-app",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Create a new Kuckit application",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,13 +3,25 @@ import { useKuckit } from '@/providers/KuckitProvider'
3
3
  import { useServices } from '@/providers/ServicesProvider'
4
4
  import { useEffect, useState } from 'react'
5
5
 
6
+ /**
7
+ * Helper to determine if a route path is a root-level route (outside dashboard).
8
+ * Root routes start with '/' but NOT '/dashboard'.
9
+ */
10
+ function isRootLevelRoute(path: string): boolean {
11
+ return path.startsWith('/') && !path.startsWith('/dashboard')
12
+ }
13
+
6
14
  /**
7
15
  * Dynamic route renderer for Kuckit module routes.
8
16
  *
9
17
  * This component looks up the current path in the RouteRegistry
10
18
  * and renders the corresponding module component if found.
11
19
  *
12
- * For routes with meta.requiresAuth: true, redirects to /login if not authenticated.
20
+ * Route resolution is context-aware:
21
+ * - When rendered under /dashboard/*: matches dashboard routes
22
+ * - When rendered at root level: matches root-level routes (paths starting with '/' but not '/dashboard/')
23
+ *
24
+ * For root routes with meta.requiresAuth: true, redirects to /login if not authenticated.
13
25
  */
14
26
  export function KuckitModuleRoute() {
15
27
  const { routeRegistry } = useKuckit()
@@ -21,10 +33,28 @@ export function KuckitModuleRoute() {
21
33
  const [authChecked, setAuthChecked] = useState(false)
22
34
  const [isAuthenticated, setIsAuthenticated] = useState(false)
23
35
 
24
- // Find matching route in registry
25
- const routeDef = routeRegistry.getAll().find((r) => r.path === pathname)
36
+ // Determine if we're in dashboard context
37
+ const isDashboardContext = pathname.startsWith('/dashboard')
38
+
39
+ // Filter routes based on context
40
+ const contextRoutes = routeRegistry.getAll().filter((r) => {
41
+ const isRoot = isRootLevelRoute(r.path)
42
+ // In dashboard context: show non-root routes
43
+ // In root context: show root routes
44
+ return isDashboardContext ? !isRoot : isRoot
45
+ })
46
+
47
+ // For dashboard context, also try matching with /dashboard prefix stripped
48
+ const modulePathname = isDashboardContext ? pathname.replace('/dashboard', '') || '/' : pathname
26
49
 
27
- // Check auth for routes with requiresAuth
50
+ // Find matching route
51
+ let routeDef = contextRoutes.find((r) => r.path === pathname)
52
+ if (!routeDef && isDashboardContext) {
53
+ // For dashboard routes, also try matching stripped path
54
+ routeDef = contextRoutes.find((r) => r.path === modulePathname)
55
+ }
56
+
57
+ // Check auth for root routes with requiresAuth
28
58
  useEffect(() => {
29
59
  async function checkAuth() {
30
60
  if (!routeDef) {
@@ -32,7 +62,14 @@ export function KuckitModuleRoute() {
32
62
  return
33
63
  }
34
64
 
35
- // Check if auth is required
65
+ // Dashboard routes are already protected by the dashboard layout
66
+ if (isDashboardContext) {
67
+ setAuthChecked(true)
68
+ setIsAuthenticated(true)
69
+ return
70
+ }
71
+
72
+ // For root routes, check if auth is required
36
73
  if (routeDef.meta?.requiresAuth) {
37
74
  const session = await authClient.getSession()
38
75
  if (!session.data) {
@@ -49,10 +86,10 @@ export function KuckitModuleRoute() {
49
86
  }
50
87
 
51
88
  checkAuth()
52
- }, [routeDef, pathname, navigate, authClient])
89
+ }, [routeDef, isDashboardContext, pathname, navigate, authClient])
53
90
 
54
- // Show loading while checking auth for protected routes
55
- if (routeDef?.meta?.requiresAuth && !authChecked) {
91
+ // Show loading while checking auth for protected root routes
92
+ if (!isDashboardContext && routeDef?.meta?.requiresAuth && !authChecked) {
56
93
  return (
57
94
  <div className="flex items-center justify-center min-h-screen">
58
95
  <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
@@ -60,8 +97,8 @@ export function KuckitModuleRoute() {
60
97
  )
61
98
  }
62
99
 
63
- // Protected route - waiting for redirect
64
- if (routeDef?.meta?.requiresAuth && !isAuthenticated) {
100
+ // Protected root route - waiting for redirect
101
+ if (!isDashboardContext && routeDef?.meta?.requiresAuth && !isAuthenticated) {
65
102
  return null
66
103
  }
67
104
 
@@ -0,0 +1,120 @@
1
+ import { Link, useRouterState } from '@tanstack/react-router'
2
+ import { LayoutDashboard, Settings, Blocks, type LucideIcon } from 'lucide-react'
3
+ import { useKuckitNav } from '@kuckit/sdk-react'
4
+ import {
5
+ Sidebar,
6
+ SidebarContent,
7
+ SidebarFooter,
8
+ SidebarGroup,
9
+ SidebarGroupContent,
10
+ SidebarGroupLabel,
11
+ SidebarHeader,
12
+ SidebarMenu,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ SidebarRail,
16
+ } from '@/components/ui/sidebar'
17
+ import { NavUser } from './nav-user'
18
+
19
+ const iconMap: Record<string, LucideIcon> = {
20
+ 'layout-dashboard': LayoutDashboard,
21
+ settings: Settings,
22
+ blocks: Blocks,
23
+ }
24
+
25
+ const coreNavItems = [
26
+ { label: 'Dashboard', path: '/dashboard', icon: LayoutDashboard },
27
+ { label: 'Settings', path: '/dashboard/settings', icon: Settings },
28
+ ]
29
+
30
+ export function AppSidebar() {
31
+ const navRegistry = useKuckitNav()
32
+ const pathname = useRouterState({ select: (s) => s.location.pathname })
33
+
34
+ const mainNavModuleItems = navRegistry.getMainNavItems()
35
+ const moduleItems = navRegistry.getModuleNavItems()
36
+
37
+ return (
38
+ <Sidebar collapsible="icon">
39
+ <SidebarHeader>
40
+ <div className="flex items-center gap-2 px-2 py-2">
41
+ <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-primary to-primary/80">
42
+ <span className="text-sm font-bold text-primary-foreground">K</span>
43
+ </div>
44
+ <span className="font-semibold group-data-[collapsible=icon]:hidden">__APP_TITLE__</span>
45
+ </div>
46
+ </SidebarHeader>
47
+
48
+ <SidebarContent>
49
+ <SidebarGroup>
50
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
51
+ <SidebarGroupContent>
52
+ <SidebarMenu>
53
+ {coreNavItems.map((item) => (
54
+ <SidebarMenuItem key={item.path}>
55
+ <SidebarMenuButton asChild isActive={pathname === item.path} tooltip={item.label}>
56
+ <Link to={item.path}>
57
+ <item.icon className="h-4 w-4" />
58
+ <span>{item.label}</span>
59
+ </Link>
60
+ </SidebarMenuButton>
61
+ </SidebarMenuItem>
62
+ ))}
63
+
64
+ {mainNavModuleItems.map((item) => {
65
+ const Icon = item.icon ? iconMap[item.icon] : Blocks
66
+ return (
67
+ <SidebarMenuItem key={item.id}>
68
+ <SidebarMenuButton
69
+ asChild
70
+ isActive={pathname === item.path || pathname.startsWith(item.path + '/')}
71
+ tooltip={item.label}
72
+ >
73
+ <Link to={item.path}>
74
+ {Icon && <Icon className="h-4 w-4" />}
75
+ <span>{item.label}</span>
76
+ </Link>
77
+ </SidebarMenuButton>
78
+ </SidebarMenuItem>
79
+ )
80
+ })}
81
+ </SidebarMenu>
82
+ </SidebarGroupContent>
83
+ </SidebarGroup>
84
+
85
+ {moduleItems.length > 0 && (
86
+ <SidebarGroup>
87
+ <SidebarGroupLabel>Modules</SidebarGroupLabel>
88
+ <SidebarGroupContent>
89
+ <SidebarMenu>
90
+ {moduleItems.map((item) => {
91
+ const Icon = item.icon ? iconMap[item.icon] : Blocks
92
+ return (
93
+ <SidebarMenuItem key={item.id}>
94
+ <SidebarMenuButton
95
+ asChild
96
+ isActive={pathname === item.path || pathname.startsWith(item.path + '/')}
97
+ tooltip={item.label}
98
+ >
99
+ <Link to={item.path}>
100
+ {Icon && <Icon className="h-4 w-4" />}
101
+ <span>{item.label}</span>
102
+ </Link>
103
+ </SidebarMenuButton>
104
+ </SidebarMenuItem>
105
+ )
106
+ })}
107
+ </SidebarMenu>
108
+ </SidebarGroupContent>
109
+ </SidebarGroup>
110
+ )}
111
+ </SidebarContent>
112
+
113
+ <SidebarFooter>
114
+ <NavUser />
115
+ </SidebarFooter>
116
+
117
+ <SidebarRail />
118
+ </Sidebar>
119
+ )
120
+ }
@@ -0,0 +1,46 @@
1
+ import type { ReactNode } from 'react'
2
+ import { SidebarProvider, SidebarInset, SidebarTrigger } from '@/components/ui/sidebar'
3
+ import { AppSidebar } from './app-sidebar'
4
+ import { Separator } from '@/components/ui/separator'
5
+ import {
6
+ Breadcrumb,
7
+ BreadcrumbItem,
8
+ BreadcrumbList,
9
+ BreadcrumbPage,
10
+ } from '@/components/ui/breadcrumb'
11
+ import { useRouterState } from '@tanstack/react-router'
12
+
13
+ interface DashboardLayoutProps {
14
+ children: ReactNode
15
+ }
16
+
17
+ export function DashboardLayout({ children }: DashboardLayoutProps) {
18
+ const pathname = useRouterState({ select: (s) => s.location.pathname })
19
+
20
+ const pathSegments = pathname.split('/').filter(Boolean)
21
+ const pageTitle =
22
+ pathSegments.length > 1
23
+ ? pathSegments[pathSegments.length - 1].charAt(0).toUpperCase() +
24
+ pathSegments[pathSegments.length - 1].slice(1)
25
+ : 'Dashboard'
26
+
27
+ return (
28
+ <SidebarProvider>
29
+ <AppSidebar />
30
+ <SidebarInset>
31
+ <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
32
+ <SidebarTrigger className="-ml-1" />
33
+ <Separator orientation="vertical" className="mr-2 h-4" />
34
+ <Breadcrumb>
35
+ <BreadcrumbList>
36
+ <BreadcrumbItem>
37
+ <BreadcrumbPage>{pageTitle}</BreadcrumbPage>
38
+ </BreadcrumbItem>
39
+ </BreadcrumbList>
40
+ </Breadcrumb>
41
+ </header>
42
+ <main className="flex-1 overflow-auto p-4">{children}</main>
43
+ </SidebarInset>
44
+ </SidebarProvider>
45
+ )
46
+ }
@@ -0,0 +1,24 @@
1
+ import { useServices } from '@/providers/ServicesProvider'
2
+
3
+ export function DashboardOverview() {
4
+ const { authClient } = useServices()
5
+ const { data: session } = authClient.useSession()
6
+
7
+ return (
8
+ <div className="space-y-6">
9
+ <div>
10
+ <h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
11
+ <p className="text-muted-foreground">Welcome back, {session?.user?.name || 'User'}</p>
12
+ </div>
13
+
14
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
15
+ <div className="rounded-lg border bg-card p-6">
16
+ <h3 className="font-semibold">Getting Started</h3>
17
+ <p className="text-sm text-muted-foreground">
18
+ Your Kuckit application is ready. Start building!
19
+ </p>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ )
24
+ }
@@ -0,0 +1,2 @@
1
+ export { DashboardLayout } from './dashboard-layout'
2
+ export { DashboardOverview } from './dashboard-overview'
@@ -0,0 +1,77 @@
1
+ import { ChevronsUpDown, LogOut, User } from 'lucide-react'
2
+ import { useNavigate } from '@tanstack/react-router'
3
+ import { useServices } from '@/providers/ServicesProvider'
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuSeparator,
9
+ DropdownMenuTrigger,
10
+ } from '@/components/ui/dropdown-menu'
11
+ import {
12
+ SidebarMenu,
13
+ SidebarMenuButton,
14
+ SidebarMenuItem,
15
+ useSidebar,
16
+ } from '@/components/ui/sidebar'
17
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
18
+
19
+ export function NavUser() {
20
+ const { isMobile } = useSidebar()
21
+ const navigate = useNavigate()
22
+ const { authClient } = useServices()
23
+ const { data: session } = authClient.useSession()
24
+
25
+ const user = session?.user
26
+ const initials =
27
+ user?.name
28
+ ?.split(' ')
29
+ .map((n) => n[0])
30
+ .join('')
31
+ .toUpperCase() || 'U'
32
+
33
+ const handleLogout = async () => {
34
+ await authClient.signOut()
35
+ navigate({ to: '/' })
36
+ }
37
+
38
+ return (
39
+ <SidebarMenu>
40
+ <SidebarMenuItem>
41
+ <DropdownMenu>
42
+ <DropdownMenuTrigger asChild>
43
+ <SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent">
44
+ <Avatar className="h-8 w-8 rounded-lg">
45
+ <AvatarImage src={user?.image || undefined} alt={user?.name || ''} />
46
+ <AvatarFallback className="rounded-lg bg-gradient-to-br from-primary to-primary/80 text-primary-foreground">
47
+ {initials}
48
+ </AvatarFallback>
49
+ </Avatar>
50
+ <div className="grid flex-1 text-left text-sm leading-tight">
51
+ <span className="truncate font-semibold">{user?.name || 'User'}</span>
52
+ <span className="truncate text-xs text-muted-foreground">{user?.email || ''}</span>
53
+ </div>
54
+ <ChevronsUpDown className="ml-auto size-4" />
55
+ </SidebarMenuButton>
56
+ </DropdownMenuTrigger>
57
+ <DropdownMenuContent
58
+ className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
59
+ side={isMobile ? 'bottom' : 'right'}
60
+ align="end"
61
+ sideOffset={4}
62
+ >
63
+ <DropdownMenuItem onClick={() => navigate({ to: '/dashboard/settings' })}>
64
+ <User className="mr-2 h-4 w-4" />
65
+ Settings
66
+ </DropdownMenuItem>
67
+ <DropdownMenuSeparator />
68
+ <DropdownMenuItem onClick={handleLogout}>
69
+ <LogOut className="mr-2 h-4 w-4" />
70
+ Log out
71
+ </DropdownMenuItem>
72
+ </DropdownMenuContent>
73
+ </DropdownMenu>
74
+ </SidebarMenuItem>
75
+ </SidebarMenu>
76
+ )
77
+ }
@@ -0,0 +1,39 @@
1
+ import * as React from 'react'
2
+ import * as AvatarPrimitive from '@radix-ui/react-avatar'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
7
+ return (
8
+ <AvatarPrimitive.Root
9
+ data-slot="avatar"
10
+ className={cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', className)}
11
+ {...props}
12
+ />
13
+ )
14
+ }
15
+
16
+ function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
17
+ return (
18
+ <AvatarPrimitive.Image
19
+ data-slot="avatar-image"
20
+ className={cn('aspect-square size-full', className)}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ function AvatarFallback({
27
+ className,
28
+ ...props
29
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
30
+ return (
31
+ <AvatarPrimitive.Fallback
32
+ data-slot="avatar-fallback"
33
+ className={cn('bg-muted flex size-full items-center justify-center rounded-full', className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,102 @@
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { ChevronRight, MoreHorizontal } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
8
+ return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
9
+ }
10
+
11
+ function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
12
+ return (
13
+ <ol
14
+ data-slot="breadcrumb-list"
15
+ className={cn(
16
+ 'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
25
+ return (
26
+ <li
27
+ data-slot="breadcrumb-item"
28
+ className={cn('inline-flex items-center gap-1.5', className)}
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function BreadcrumbLink({
35
+ asChild,
36
+ className,
37
+ ...props
38
+ }: React.ComponentProps<'a'> & {
39
+ asChild?: boolean
40
+ }) {
41
+ const Comp = asChild ? Slot : 'a'
42
+
43
+ return (
44
+ <Comp
45
+ data-slot="breadcrumb-link"
46
+ className={cn('hover:text-foreground transition-colors', className)}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+
52
+ function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
53
+ return (
54
+ <span
55
+ data-slot="breadcrumb-page"
56
+ role="link"
57
+ aria-disabled="true"
58
+ aria-current="page"
59
+ className={cn('text-foreground font-normal', className)}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {
66
+ return (
67
+ <li
68
+ data-slot="breadcrumb-separator"
69
+ role="presentation"
70
+ aria-hidden="true"
71
+ className={cn('[&>svg]:size-3.5', className)}
72
+ {...props}
73
+ >
74
+ {children ?? <ChevronRight />}
75
+ </li>
76
+ )
77
+ }
78
+
79
+ function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
80
+ return (
81
+ <span
82
+ data-slot="breadcrumb-ellipsis"
83
+ role="presentation"
84
+ aria-hidden="true"
85
+ className={cn('flex size-9 items-center justify-center', className)}
86
+ {...props}
87
+ >
88
+ <MoreHorizontal className="size-4" />
89
+ <span className="sr-only">More</span>
90
+ </span>
91
+ )
92
+ }
93
+
94
+ export {
95
+ Breadcrumb,
96
+ BreadcrumbList,
97
+ BreadcrumbItem,
98
+ BreadcrumbLink,
99
+ BreadcrumbPage,
100
+ BreadcrumbSeparator,
101
+ BreadcrumbEllipsis,
102
+ }
@@ -0,0 +1,21 @@
1
+ 'use client'
2
+
3
+ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
4
+
5
+ function Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
6
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
7
+ }
8
+
9
+ function CollapsibleTrigger({
10
+ ...props
11
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
12
+ return <CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} />
13
+ }
14
+
15
+ function CollapsibleContent({
16
+ ...props
17
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
18
+ return <CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} />
19
+ }
20
+
21
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
@@ -0,0 +1,26 @@
1
+ import * as React from 'react'
2
+ import * as SeparatorPrimitive from '@radix-ui/react-separator'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Separator({
7
+ className,
8
+ orientation = 'horizontal',
9
+ decorative = true,
10
+ ...props
11
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
12
+ return (
13
+ <SeparatorPrimitive.Root
14
+ data-slot="separator"
15
+ decorative={decorative}
16
+ orientation={orientation}
17
+ className={cn(
18
+ 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ export { Separator }