nl-d365boilerplate-vite 1.0.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 (46) hide show
  1. package/.env.example +22 -0
  2. package/README.md +75 -0
  3. package/bin/cli.js +84 -0
  4. package/components.json +22 -0
  5. package/eslint.config.js +23 -0
  6. package/getToken.js +197 -0
  7. package/index.html +30 -0
  8. package/package.json +69 -0
  9. package/src/App.tsx +28 -0
  10. package/src/assets/images/novalogica-logo.svg +24 -0
  11. package/src/components/nl-header.tsx +165 -0
  12. package/src/components/page-layout.tsx +78 -0
  13. package/src/components/page-render.tsx +16 -0
  14. package/src/components/ui/alert.tsx +66 -0
  15. package/src/components/ui/badge.tsx +49 -0
  16. package/src/components/ui/button.tsx +165 -0
  17. package/src/components/ui/card.tsx +92 -0
  18. package/src/components/ui/dialog.tsx +156 -0
  19. package/src/components/ui/input.tsx +23 -0
  20. package/src/components/ui/label.tsx +23 -0
  21. package/src/components/ui/separator.tsx +26 -0
  22. package/src/components/ui/table.tsx +116 -0
  23. package/src/components/ui/tabs.tsx +91 -0
  24. package/src/components/ui/theme-toggle.tsx +28 -0
  25. package/src/config/pages.config.ts +34 -0
  26. package/src/contexts/dataverse-context.tsx +12 -0
  27. package/src/contexts/navigation-context.tsx +14 -0
  28. package/src/hooks/useAccounts.ts +194 -0
  29. package/src/hooks/useDataverse.ts +11 -0
  30. package/src/hooks/useNavigation.ts +41 -0
  31. package/src/index.css +147 -0
  32. package/src/lib/nav-items.ts +25 -0
  33. package/src/lib/utils.ts +6 -0
  34. package/src/main.tsx +12 -0
  35. package/src/pages/Demo.tsx +465 -0
  36. package/src/pages/Documentation.tsx +850 -0
  37. package/src/pages/Home.tsx +132 -0
  38. package/src/pages/index.ts +4 -0
  39. package/src/providers/dataverse-provider.tsx +81 -0
  40. package/src/providers/navigation-provider.tsx +33 -0
  41. package/src/providers/theme-provider.tsx +92 -0
  42. package/src/public/novalogica-logo.svg +24 -0
  43. package/tsconfig.app.json +32 -0
  44. package/tsconfig.json +17 -0
  45. package/tsconfig.node.json +26 -0
  46. package/vite.config.ts +26 -0
@@ -0,0 +1,165 @@
1
+ "use client";
2
+
3
+ import { memo, useState } from "react";
4
+ import { Menu01Icon, Cancel01Icon } from "hugeicons-react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+ import { navItems as defaultNavItems, type NavItem } from "@/lib/nav-items";
8
+ import { Button } from "@/components/ui/button";
9
+ import appLogo from "@/assets/images/novalogica-logo.svg";
10
+ import { useNavigation } from "@/hooks/useNavigation";
11
+ import { ThemeToggle } from "./ui/theme-toggle";
12
+
13
+ export interface HeaderProps {
14
+ className?: string;
15
+ logo?: React.ReactNode;
16
+ navItems?: NavItem[];
17
+ currentPageId?: string;
18
+ onNavigate?: (pageId: string) => void;
19
+ onLogoClick?: () => void;
20
+ }
21
+
22
+ const Header = memo(function Header({
23
+ className,
24
+ navItems = defaultNavItems,
25
+ currentPageId = "home",
26
+ onLogoClick,
27
+ }: HeaderProps) {
28
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
29
+ const toggleMenu = () => setIsMobileMenuOpen(!isMobileMenuOpen);
30
+
31
+ const { currentPage, navigate } = useNavigation();
32
+
33
+ const handleNavigate = (pageId: string) => {
34
+ navigate(pageId);
35
+ setIsMobileMenuOpen(false);
36
+ };
37
+
38
+ return (
39
+ <div
40
+ className={cn(
41
+ "fixed top-0 left-0 right-0 z-50 flex justify-center px-2",
42
+ "transition-all duration-200 pt-2",
43
+ className,
44
+ )}
45
+ >
46
+ <header
47
+ className={cn(
48
+ "bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60",
49
+ "border rounded-3xl w-full max-w-7xl",
50
+ "transition-all duration-200 shadow-sm relative",
51
+ )}
52
+ >
53
+ <div className="h-16 flex items-center justify-between px-4">
54
+ {/* Logo Section */}
55
+ <div className="shrink-0 flex items-center">
56
+ <button
57
+ onClick={onLogoClick}
58
+ className="flex items-center gap-2 transition-opacity hover:opacity-80"
59
+ type="button"
60
+ >
61
+ <img
62
+ src={appLogo}
63
+ alt="App Logo"
64
+ height={45}
65
+ width={45}
66
+ className="object-contain"
67
+ />
68
+ </button>
69
+ </div>
70
+
71
+ {/* Desktop Navigation */}
72
+ <nav className="hidden md:flex absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 h-full">
73
+ <div className="flex items-center gap-8 h-full">
74
+ {navItems.map((item) => {
75
+ const isActive = currentPage.id === item.pageId;
76
+ const Icon = item.icon;
77
+ return (
78
+ <button
79
+ key={item.pageId}
80
+ onClick={() => navigate(item.pageId)}
81
+ className={cn(
82
+ "flex items-center gap-2 h-full px-1 text-sm font-medium transition-all duration-200 border-b-2",
83
+ isActive
84
+ ? "text-foreground border-primary"
85
+ : "text-muted-foreground border-transparent hover:text-foreground",
86
+ )}
87
+ type="button"
88
+ >
89
+ <Icon className="size-4" />
90
+ {item.label}
91
+ </button>
92
+ );
93
+ })}
94
+ </div>
95
+ </nav>
96
+
97
+ {/* Right Section */}
98
+ <div className="flex items-center gap-2">
99
+ <div className="hidden md:block">
100
+ <ThemeToggle />
101
+ </div>
102
+
103
+ {/* Mobile Menu Toggle */}
104
+ <div className="md:hidden">
105
+ <Button
106
+ variant="ghost"
107
+ size="icon"
108
+ onClick={toggleMenu}
109
+ className="size-9 rounded-full"
110
+ >
111
+ {isMobileMenuOpen ? (
112
+ <Cancel01Icon className="size-5" />
113
+ ) : (
114
+ <Menu01Icon className="size-5" />
115
+ )}
116
+ </Button>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ {/* Mobile Menu Dropdown */}
122
+ {isMobileMenuOpen && (
123
+ <div className="absolute top-full left-0 right-0 mt-2 p-4 bg-background border rounded-2xl shadow-lg md:hidden flex flex-col gap-4 animate-in fade-in slide-in-from-top-5">
124
+ <nav className="flex flex-col p-2 gap-6">
125
+ {navItems.map((item) => {
126
+ const Icon = item.icon;
127
+ const isActive = currentPageId === item.pageId;
128
+
129
+ return (
130
+ <button
131
+ key={item.pageId}
132
+ onClick={() => handleNavigate(item.pageId)}
133
+ className={cn(
134
+ "flex items-center gap-4 px-4 py-2 text-base font-medium transition-colors",
135
+ isActive
136
+ ? "text-foreground font-semibold"
137
+ : "text-muted-foreground hover:text-foreground",
138
+ )}
139
+ type="button"
140
+ >
141
+ <Icon className="size-5" />
142
+ {item.label}
143
+ </button>
144
+ );
145
+ })}
146
+
147
+ <div className="h-px bg-border/50 mx-2" />
148
+
149
+ <div className="flex items-center justify-between px-4">
150
+ <span className="text-sm font-medium text-muted-foreground">
151
+ Theme
152
+ </span>
153
+ <ThemeToggle />
154
+ </div>
155
+ </nav>
156
+ </div>
157
+ )}
158
+ </header>
159
+ </div>
160
+ );
161
+ });
162
+
163
+ Header.displayName = "Header";
164
+
165
+ export default Header;
@@ -0,0 +1,78 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ export interface PageLayoutProps {
4
+ /**
5
+ * Page title displayed in the header
6
+ */
7
+ title: string;
8
+
9
+ /**
10
+ * Optional subtitle/description below the title
11
+ */
12
+ description?: string;
13
+
14
+ /**
15
+ * Icon component to display next to the title
16
+ */
17
+ icon?: React.ElementType;
18
+
19
+ /**
20
+ * Optional toolbar/actions component (e.g., buttons, filters)
21
+ */
22
+ toolbar?: ReactNode;
23
+
24
+ /**
25
+ * Main page content
26
+ */
27
+ children: ReactNode;
28
+
29
+ /**
30
+ * Optional className for additional styling
31
+ */
32
+ className?: string;
33
+ }
34
+
35
+ /**
36
+ * PageLayout - Reusable page layout component
37
+ * Provides consistent structure across all application pages with header, icon, description, and content area
38
+ */
39
+ export const PageLayout = ({
40
+ title,
41
+ description,
42
+ icon: Icon,
43
+ toolbar,
44
+ children,
45
+ className = "",
46
+ }: PageLayoutProps) => {
47
+ return (
48
+ <div className="flex-1 flex flex-col h-full bg-background/50">
49
+ <div
50
+ className={`flex-1 container max-w-7xl mx-auto pt-24 pb-10 px-4 sm:px-6 lg:px-8 ${className}`}
51
+ >
52
+ {/* Header */}
53
+ <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
54
+ <div className="flex items-start gap-4">
55
+ {Icon && (
56
+ <div className="p-3 bg-primary/10 rounded-xl">
57
+ <Icon className="w-8 h-8 text-primary" />
58
+ </div>
59
+ )}
60
+ <div>
61
+ <h1 className="text-2xl font-bold tracking-tight text-foreground">
62
+ {title}
63
+ </h1>
64
+ {description && (
65
+ <p className="text-muted-foreground mt-1">{description}</p>
66
+ )}
67
+ </div>
68
+ </div>
69
+
70
+ {toolbar && <div>{toolbar}</div>}
71
+ </div>
72
+
73
+ {/* Content */}
74
+ <div className="flex-1 flex flex-col">{children}</div>
75
+ </div>
76
+ </div>
77
+ );
78
+ };
@@ -0,0 +1,16 @@
1
+ import { useNavigation } from "@/hooks/useNavigation";
2
+
3
+ export function PageRenderer() {
4
+ const { currentPage } = useNavigation();
5
+
6
+ const PageComponent = currentPage.component;
7
+
8
+ return (
9
+ <div
10
+ key={currentPage.id}
11
+ className="flex-1 flex flex-col w-full animate-in fade-in slide-in-from-bottom-4 duration-300 ease-out"
12
+ >
13
+ <PageComponent />
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-card text-card-foreground",
12
+ destructive:
13
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn(
42
+ "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function AlertDescription({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ data-slot="alert-description"
57
+ className={cn(
58
+ "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
+ destructive:
16
+ "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
+ outline:
18
+ "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
21
+ soft: "bg-primary/10 text-primary [a&]:hover:bg-primary/20 border-transparent",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ },
27
+ },
28
+ );
29
+
30
+ function Badge({
31
+ className,
32
+ variant = "default",
33
+ asChild = false,
34
+ ...props
35
+ }: React.ComponentProps<"span"> &
36
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
37
+ const Comp = asChild ? Slot : "span";
38
+
39
+ return (
40
+ <Comp
41
+ data-slot="badge"
42
+ data-variant={variant}
43
+ className={cn(badgeVariants({ variant }), className)}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ export { Badge, badgeVariants };
@@ -0,0 +1,165 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { Loading03Icon, Tick02Icon } from "hugeicons-react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const buttonVariants = cva(
9
+ "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 active:scale-95",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default:
14
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
15
+ destructive:
16
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
17
+ outline:
18
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
19
+ secondary:
20
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+
24
+ // Premium Soft Variants
25
+ soft: "bg-primary/10 text-primary hover:bg-primary/20",
26
+ "soft-blue":
27
+ "bg-blue-500/10 text-blue-600 dark:text-blue-400 hover:bg-blue-500/20",
28
+ "soft-emerald":
29
+ "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/20",
30
+ "soft-amber":
31
+ "bg-amber-500/10 text-amber-600 dark:text-amber-400 hover:bg-amber-500/20",
32
+ "soft-rose":
33
+ "bg-rose-500/10 text-rose-600 dark:text-rose-400 hover:bg-rose-500/20",
34
+ "soft-violet":
35
+ "bg-violet-500/10 text-violet-600 dark:text-violet-400 hover:bg-violet-500/20",
36
+ "soft-cyan":
37
+ "bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/20",
38
+
39
+ // Premium Gradient Variants
40
+ gradient:
41
+ "bg-gradient-to-br from-primary to-primary/60 text-primary-foreground shadow-md hover:opacity-90",
42
+ "gradient-blue":
43
+ "bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-md hover:opacity-90",
44
+ "gradient-emerald":
45
+ "bg-gradient-to-br from-emerald-500 to-emerald-600 text-white shadow-md hover:opacity-90",
46
+ "gradient-violet":
47
+ "bg-gradient-to-br from-violet-500 to-violet-600 text-white shadow-md hover:opacity-90",
48
+ "gradient-rose":
49
+ "bg-gradient-to-br from-rose-500 to-rose-600 text-white shadow-md hover:opacity-90",
50
+
51
+ // Glow Variants
52
+ glow: "bg-primary text-primary-foreground shadow-lg shadow-primary/25 hover:shadow-primary/40",
53
+ "glow-blue":
54
+ "bg-blue-500 text-white shadow-lg shadow-blue-500/25 hover:shadow-blue-500/40",
55
+ "glow-emerald":
56
+ "bg-emerald-500 text-white shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40",
57
+ "glow-violet":
58
+ "bg-violet-500 text-white shadow-lg shadow-violet-500/25 hover:shadow-violet-500/40",
59
+
60
+ // Glass Variants
61
+ glass:
62
+ "bg-white/10 text-foreground backdrop-blur-md border border-white/20 hover:bg-white/20",
63
+ "glass-dark":
64
+ "bg-black/10 text-foreground backdrop-blur-md border border-black/10 hover:bg-black/20",
65
+
66
+ // Functional Variants
67
+ success: "bg-emerald-500 text-white shadow-sm hover:bg-emerald-600",
68
+ warning: "bg-amber-500 text-white shadow-sm hover:bg-amber-600",
69
+ },
70
+ size: {
71
+ default: "h-9 px-4 py-2 [&_svg]:size-4",
72
+ sm: "h-8 px-3 text-xs [&_svg]:size-3.5",
73
+ lg: "h-10 px-8 [&_svg]:size-5",
74
+ xl: "h-12 px-10 text-base [&_svg]:size-6",
75
+ icon: "h-9 w-9 [&_svg]:size-4",
76
+ "icon-sm": "h-8 w-8 [&_svg]:size-3.5",
77
+ "icon-md": "h-9 w-9 [&_svg]:size-4",
78
+ "icon-lg": "h-10 w-10 [&_svg]:size-5",
79
+ "icon-xl": "h-12 w-12 [&_svg]:size-6",
80
+ },
81
+ rounded: {
82
+ none: "rounded-none",
83
+ sm: "rounded-sm",
84
+ default: "rounded-md",
85
+ md: "rounded-md",
86
+ lg: "rounded-lg",
87
+ xl: "rounded-xl",
88
+ "2xl": "rounded-2xl",
89
+ full: "rounded-full",
90
+ },
91
+ },
92
+ defaultVariants: {
93
+ variant: "default",
94
+ size: "default",
95
+ rounded: "default",
96
+ },
97
+ },
98
+ );
99
+
100
+ export interface ButtonProps
101
+ extends
102
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
103
+ VariantProps<typeof buttonVariants> {
104
+ asChild?: boolean;
105
+ isLoading?: boolean;
106
+ isSuccess?: boolean;
107
+ leftIcon?: React.ElementType;
108
+ rightIcon?: React.ElementType;
109
+ }
110
+
111
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
112
+ (
113
+ {
114
+ className,
115
+ variant,
116
+ size,
117
+ rounded,
118
+ asChild = false,
119
+ isLoading = false,
120
+ isSuccess = false,
121
+ leftIcon: LeftIcon,
122
+ rightIcon: RightIcon,
123
+ children,
124
+ disabled,
125
+ ...props
126
+ },
127
+ ref,
128
+ ) => {
129
+ const Comp = asChild ? Slot : "button";
130
+
131
+ // When using asChild, Slot expects exactly one child, so we can't add icons/loading states
132
+ if (asChild) {
133
+ return (
134
+ <Comp
135
+ className={cn(buttonVariants({ variant, size, rounded, className }))}
136
+ ref={ref}
137
+ {...props}
138
+ >
139
+ {children}
140
+ </Comp>
141
+ );
142
+ }
143
+
144
+ return (
145
+ <Comp
146
+ className={cn(
147
+ buttonVariants({ variant, size, rounded, className }),
148
+ isLoading && "disabled:cursor-progress",
149
+ )}
150
+ ref={ref}
151
+ disabled={isLoading || disabled}
152
+ {...props}
153
+ >
154
+ {isLoading && <Loading03Icon className="animate-spin" />}
155
+ {isSuccess && !isLoading && <Tick02Icon />}
156
+ {!isLoading && !isSuccess && LeftIcon && <LeftIcon />}
157
+ {children}
158
+ {!isLoading && RightIcon && <RightIcon />}
159
+ </Comp>
160
+ );
161
+ },
162
+ );
163
+ Button.displayName = "Button";
164
+
165
+ export { Button, buttonVariants };
@@ -0,0 +1,92 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
24
+ className
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return (
33
+ <div
34
+ data-slot="card-title"
35
+ className={cn("leading-none font-semibold", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="card-description"
45
+ className={cn("text-muted-foreground text-sm", className)}
46
+ {...props}
47
+ />
48
+ )
49
+ }
50
+
51
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
52
+ return (
53
+ <div
54
+ data-slot="card-action"
55
+ className={cn(
56
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
57
+ className
58
+ )}
59
+ {...props}
60
+ />
61
+ )
62
+ }
63
+
64
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
65
+ return (
66
+ <div
67
+ data-slot="card-content"
68
+ className={cn("px-6", className)}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="card-footer"
78
+ className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ export {
85
+ Card,
86
+ CardHeader,
87
+ CardFooter,
88
+ CardTitle,
89
+ CardAction,
90
+ CardDescription,
91
+ CardContent,
92
+ }