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.
- package/.env.example +22 -0
- package/README.md +75 -0
- package/bin/cli.js +84 -0
- package/components.json +22 -0
- package/eslint.config.js +23 -0
- package/getToken.js +197 -0
- package/index.html +30 -0
- package/package.json +69 -0
- package/src/App.tsx +28 -0
- package/src/assets/images/novalogica-logo.svg +24 -0
- package/src/components/nl-header.tsx +165 -0
- package/src/components/page-layout.tsx +78 -0
- package/src/components/page-render.tsx +16 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +165 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +156 -0
- package/src/components/ui/input.tsx +23 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/theme-toggle.tsx +28 -0
- package/src/config/pages.config.ts +34 -0
- package/src/contexts/dataverse-context.tsx +12 -0
- package/src/contexts/navigation-context.tsx +14 -0
- package/src/hooks/useAccounts.ts +194 -0
- package/src/hooks/useDataverse.ts +11 -0
- package/src/hooks/useNavigation.ts +41 -0
- package/src/index.css +147 -0
- package/src/lib/nav-items.ts +25 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +12 -0
- package/src/pages/Demo.tsx +465 -0
- package/src/pages/Documentation.tsx +850 -0
- package/src/pages/Home.tsx +132 -0
- package/src/pages/index.ts +4 -0
- package/src/providers/dataverse-provider.tsx +81 -0
- package/src/providers/navigation-provider.tsx +33 -0
- package/src/providers/theme-provider.tsx +92 -0
- package/src/public/novalogica-logo.svg +24 -0
- package/tsconfig.app.json +32 -0
- package/tsconfig.json +17 -0
- package/tsconfig.node.json +26 -0
- 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
|
+
}
|