@naeil/ui 0.1.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/README.md +36 -0
- package/messages/en.json +221 -0
- package/messages/ja.json +221 -0
- package/messages/ko.json +221 -0
- package/package.json +73 -0
- package/postcss.config.mjs +7 -0
- package/src/app/[locale]/(content)/baby-agent/page.tsx +30 -0
- package/src/app/[locale]/(content)/blog/page.tsx +68 -0
- package/src/app/[locale]/(content)/cc/page.tsx +216 -0
- package/src/app/[locale]/(content)/design/page.tsx +703 -0
- package/src/app/[locale]/(content)/layout.tsx +7 -0
- package/src/app/[locale]/(content)/pkm/page.tsx +31 -0
- package/src/app/[locale]/(content)/projects/page.tsx +100 -0
- package/src/app/[locale]/(content)/sa/page.tsx +179 -0
- package/src/app/[locale]/(landing)/page.tsx +157 -0
- package/src/app/[locale]/layout.tsx +72 -0
- package/src/app/[locale]/login/actions.ts +10 -0
- package/src/app/[locale]/login/oauth-buttons.tsx +78 -0
- package/src/app/[locale]/login/page.tsx +85 -0
- package/src/app/auth/callback/route.ts +78 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +32 -0
- package/src/app/layout.tsx +22 -0
- package/src/app/sitemap.ts +26 -0
- package/src/components/accent-picker.tsx +62 -0
- package/src/components/auth-slot.tsx +96 -0
- package/src/components/footer-wrapper.tsx +8 -0
- package/src/components/footer.tsx +193 -0
- package/src/components/hero-scene.tsx +948 -0
- package/src/components/hero-section.tsx +57 -0
- package/src/components/locale-switcher.tsx +74 -0
- package/src/components/logo.tsx +51 -0
- package/src/components/nav-server-wrapper.tsx +22 -0
- package/src/components/nav-wrapper.tsx +45 -0
- package/src/components/nav.tsx +550 -0
- package/src/components/paraglider-cursor.tsx +112 -0
- package/src/components/project-layout.tsx +194 -0
- package/src/components/theme-provider.tsx +10 -0
- package/src/components/theme-toggle-icon.tsx +62 -0
- package/src/components/theme-toggle.tsx +39 -0
- package/src/components/typography.tsx +27 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/button.tsx +65 -0
- package/src/components/ui/card.tsx +106 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/sonner.tsx +54 -0
- package/src/components/workflow-diagram.tsx +414 -0
- package/src/i18n/config.ts +8 -0
- package/src/i18n/request.ts +16 -0
- package/src/i18n/routing.ts +12 -0
- package/src/lib/supabase/client.ts +18 -0
- package/src/lib/supabase/middleware.ts +42 -0
- package/src/lib/supabase/server.ts +37 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +62 -0
- package/src/tokens/.gitkeep +0 -0
- package/src/tokens/color.json +56 -0
- package/src/tokens/radius.json +9 -0
- package/src/tokens/semantic.json +81 -0
- package/src/tokens/shadow.json +7 -0
- package/src/tokens/spacing.json +16 -0
- package/src/tokens/typography.json +34 -0
- package/src/types/i18n.d.ts +6 -0
- package/src/utils/.gitkeep +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
WorkflowDiagram,
|
|
5
|
+
type WorkflowNode,
|
|
6
|
+
type WorkflowEdge,
|
|
7
|
+
} from "@/components/workflow-diagram";
|
|
8
|
+
import { PageTitle, SectionTitle } from "@/components/typography";
|
|
9
|
+
import Image from "next/image";
|
|
10
|
+
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
/* Types */
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
|
|
15
|
+
export interface ProjectFeature {
|
|
16
|
+
icon: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ProjectStackItem {
|
|
22
|
+
icon: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ProjectData {
|
|
27
|
+
icon: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
accent: string;
|
|
31
|
+
tags: string[];
|
|
32
|
+
github?: string;
|
|
33
|
+
demo?: string;
|
|
34
|
+
comingSoon?: boolean;
|
|
35
|
+
overview?: string;
|
|
36
|
+
features?: ProjectFeature[];
|
|
37
|
+
workflow?: {
|
|
38
|
+
nodes: WorkflowNode[];
|
|
39
|
+
edges: WorkflowEdge[];
|
|
40
|
+
};
|
|
41
|
+
stack?: ProjectStackItem[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ------------------------------------------------------------------ */
|
|
45
|
+
/* Component */
|
|
46
|
+
/* ------------------------------------------------------------------ */
|
|
47
|
+
|
|
48
|
+
export function ProjectLayout({ data }: { data: ProjectData }) {
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
{/* Hero */}
|
|
52
|
+
<section className="mx-auto max-w-4xl px-6 pb-16">
|
|
53
|
+
<div className="flex flex-col-reverse items-start gap-8 md:flex-row md:items-start md:justify-between">
|
|
54
|
+
{/* Text */}
|
|
55
|
+
<div className="flex-1">
|
|
56
|
+
<PageTitle className="mb-3">{data.name}</PageTitle>
|
|
57
|
+
<p className="text-muted-foreground mb-5 max-w-lg text-lg leading-relaxed">
|
|
58
|
+
{data.description}
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
{/* Tags */}
|
|
62
|
+
<div className="mb-6 flex flex-wrap gap-2">
|
|
63
|
+
{data.tags.map((tag) => (
|
|
64
|
+
<span
|
|
65
|
+
key={tag}
|
|
66
|
+
className="rounded-full border border-black/[0.08] px-2.5 py-1 text-[11px] text-zinc-400 dark:border-white/[0.08]"
|
|
67
|
+
>
|
|
68
|
+
{tag}
|
|
69
|
+
</span>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Links */}
|
|
74
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
75
|
+
{data.github && (
|
|
76
|
+
<a
|
|
77
|
+
href={data.github}
|
|
78
|
+
target="_blank"
|
|
79
|
+
rel="noopener noreferrer"
|
|
80
|
+
className="inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-medium transition-colors"
|
|
81
|
+
style={{
|
|
82
|
+
background: data.accent,
|
|
83
|
+
color: "var(--background)",
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
GitHub →
|
|
87
|
+
</a>
|
|
88
|
+
)}
|
|
89
|
+
{data.demo && (
|
|
90
|
+
<a
|
|
91
|
+
href={data.demo}
|
|
92
|
+
target="_blank"
|
|
93
|
+
rel="noopener noreferrer"
|
|
94
|
+
className="text-muted-foreground hover:text-foreground inline-flex items-center gap-2 rounded-lg border border-border px-5 py-2.5 text-sm font-medium transition-colors"
|
|
95
|
+
>
|
|
96
|
+
Live Demo
|
|
97
|
+
</a>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Animal visual */}
|
|
103
|
+
<div className="flex-shrink-0">
|
|
104
|
+
<Image
|
|
105
|
+
src={data.icon}
|
|
106
|
+
alt=""
|
|
107
|
+
width={220}
|
|
108
|
+
height={220}
|
|
109
|
+
className="animate-float object-contain"
|
|
110
|
+
style={{
|
|
111
|
+
filter: `drop-shadow(0 4px 12px color-mix(in oklch, ${data.accent} 15%, transparent))`,
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
{/* Coming Soon */}
|
|
119
|
+
{data.comingSoon && (
|
|
120
|
+
<section className="mx-auto max-w-4xl px-6 pb-24">
|
|
121
|
+
<div className="rounded-2xl border border-dashed border-black/[0.08] p-12 text-center dark:border-white/[0.08]">
|
|
122
|
+
<p className="text-muted-foreground text-sm">
|
|
123
|
+
Coming Soon
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Overview */}
|
|
130
|
+
{data.overview && (
|
|
131
|
+
<section className="mx-auto max-w-4xl px-6 pb-16">
|
|
132
|
+
<SectionTitle className="mb-3">Overview</SectionTitle>
|
|
133
|
+
<p className="text-muted-foreground max-w-2xl text-sm leading-7 whitespace-pre-line">
|
|
134
|
+
{data.overview}
|
|
135
|
+
</p>
|
|
136
|
+
</section>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{/* Features */}
|
|
140
|
+
{data.features && data.features.length > 0 && (
|
|
141
|
+
<section className="mx-auto max-w-4xl px-6 pb-16">
|
|
142
|
+
<SectionTitle className="mb-6">Features</SectionTitle>
|
|
143
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
144
|
+
{data.features.map((feat) => (
|
|
145
|
+
<div
|
|
146
|
+
key={feat.title}
|
|
147
|
+
className="rounded-2xl border border-border-subtle bg-surface-subtle p-6 transition-colors hover:border-border-subtle-hover"
|
|
148
|
+
>
|
|
149
|
+
<span className="mb-3 block text-2xl">{feat.icon}</span>
|
|
150
|
+
<h3 className="text-foreground mb-1 text-sm font-semibold">
|
|
151
|
+
{feat.title}
|
|
152
|
+
</h3>
|
|
153
|
+
<p className="text-muted-foreground text-[13px] leading-relaxed">
|
|
154
|
+
{feat.description}
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</section>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Architecture */}
|
|
163
|
+
{data.workflow && (
|
|
164
|
+
<section className="mx-auto max-w-4xl px-6 pb-16">
|
|
165
|
+
<SectionTitle className="mb-8">Architecture</SectionTitle>
|
|
166
|
+
<WorkflowDiagram
|
|
167
|
+
nodes={data.workflow.nodes}
|
|
168
|
+
edges={data.workflow.edges}
|
|
169
|
+
accent={data.accent}
|
|
170
|
+
/>
|
|
171
|
+
</section>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{/* Stack */}
|
|
175
|
+
{data.stack && data.stack.length > 0 && (
|
|
176
|
+
<section className="mx-auto max-w-4xl px-6 pb-24">
|
|
177
|
+
<SectionTitle className="mb-6">Stack</SectionTitle>
|
|
178
|
+
<div className="flex flex-wrap gap-2">
|
|
179
|
+
{data.stack.map((item) => (
|
|
180
|
+
<div
|
|
181
|
+
key={item.name}
|
|
182
|
+
className="flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-[13px]"
|
|
183
|
+
>
|
|
184
|
+
<span className="text-base">{item.icon}</span>
|
|
185
|
+
<span className="text-muted-foreground">{item.name}</span>
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</section>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
4
|
+
|
|
5
|
+
export function ThemeProvider({
|
|
6
|
+
children,
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
9
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes";
|
|
4
|
+
import { useSyncExternalStore } from "react";
|
|
5
|
+
|
|
6
|
+
const emptySubscribe = () => () => {};
|
|
7
|
+
|
|
8
|
+
function useIsMounted() {
|
|
9
|
+
return useSyncExternalStore(
|
|
10
|
+
emptySubscribe,
|
|
11
|
+
() => true,
|
|
12
|
+
() => false,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ThemeToggleIcon() {
|
|
17
|
+
const { theme, setTheme } = useTheme();
|
|
18
|
+
const mounted = useIsMounted();
|
|
19
|
+
|
|
20
|
+
if (!mounted) {
|
|
21
|
+
return <div className="h-4 w-4" />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isDark = theme === "dark";
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<button
|
|
28
|
+
onClick={() => setTheme(isDark ? "light" : "dark")}
|
|
29
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
30
|
+
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
|
31
|
+
>
|
|
32
|
+
{isDark ? (
|
|
33
|
+
<svg
|
|
34
|
+
width="16"
|
|
35
|
+
height="16"
|
|
36
|
+
viewBox="0 0 24 24"
|
|
37
|
+
fill="none"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
strokeWidth="1.5"
|
|
40
|
+
strokeLinecap="round"
|
|
41
|
+
strokeLinejoin="round"
|
|
42
|
+
>
|
|
43
|
+
<circle cx="12" cy="12" r="5" />
|
|
44
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
|
|
45
|
+
</svg>
|
|
46
|
+
) : (
|
|
47
|
+
<svg
|
|
48
|
+
width="16"
|
|
49
|
+
height="16"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
strokeWidth="1.5"
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
>
|
|
57
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
58
|
+
</svg>
|
|
59
|
+
)}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useTheme } from "next-themes";
|
|
4
|
+
import { useSyncExternalStore } from "react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
const emptySubscribe = () => () => {};
|
|
8
|
+
|
|
9
|
+
function useIsMounted() {
|
|
10
|
+
return useSyncExternalStore(
|
|
11
|
+
emptySubscribe,
|
|
12
|
+
() => true,
|
|
13
|
+
() => false
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ThemeToggle() {
|
|
18
|
+
const { theme, setTheme } = useTheme();
|
|
19
|
+
const mounted = useIsMounted();
|
|
20
|
+
|
|
21
|
+
if (!mounted) {
|
|
22
|
+
return (
|
|
23
|
+
<Button variant="outline" size="sm" className="w-24">
|
|
24
|
+
…
|
|
25
|
+
</Button>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Button
|
|
31
|
+
variant="outline"
|
|
32
|
+
size="sm"
|
|
33
|
+
className="w-24"
|
|
34
|
+
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
35
|
+
>
|
|
36
|
+
{theme === "dark" ? "☀️ Light" : "🌙 Dark"}
|
|
37
|
+
</Button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> & {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const pageTitleClass =
|
|
9
|
+
"text-foreground text-4xl font-bold tracking-tight lg:text-5xl";
|
|
10
|
+
|
|
11
|
+
export const sectionTitleClass = "text-foreground text-lg font-bold";
|
|
12
|
+
|
|
13
|
+
export function PageTitle({ className, children, ...props }: HeadingProps) {
|
|
14
|
+
return (
|
|
15
|
+
<h1 className={cn(pageTitleClass, className)} {...props}>
|
|
16
|
+
{children}
|
|
17
|
+
</h1>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function SectionTitle({ className, children, ...props }: HeadingProps) {
|
|
22
|
+
return (
|
|
23
|
+
<h2 className={cn(sectionTitleClass, className)} {...props}>
|
|
24
|
+
{children}
|
|
25
|
+
</h2>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
size?: "default" | "sm" | "lg"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AvatarPrimitive.Root
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6 focus-visible:ring-accent/25 focus-visible:ring-[2px] focus-visible:outline-none",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
32
|
+
return (
|
|
33
|
+
<AvatarPrimitive.Image
|
|
34
|
+
data-slot="avatar-image"
|
|
35
|
+
className={cn("aspect-square size-full", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AvatarFallback({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Fallback
|
|
47
|
+
data-slot="avatar-fallback"
|
|
48
|
+
className={cn(
|
|
49
|
+
"bg-accent/15 text-accent flex size-full items-center justify-center rounded-full font-medium text-xs group-data-[size=sm]/avatar:text-[10px]",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
data-slot="avatar-badge"
|
|
61
|
+
className={cn(
|
|
62
|
+
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
|
|
63
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
64
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
65
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="avatar-group"
|
|
77
|
+
className={cn(
|
|
78
|
+
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AvatarGroupCount({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="avatar-group-count"
|
|
93
|
+
className={cn(
|
|
94
|
+
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Avatar,
|
|
104
|
+
AvatarImage,
|
|
105
|
+
AvatarFallback,
|
|
106
|
+
AvatarBadge,
|
|
107
|
+
AvatarGroup,
|
|
108
|
+
AvatarGroupCount,
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center rounded-sm border px-2 py-0.5 text-[11px] font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:ring-accent/25 focus-visible:ring-[2px] focus-visible:outline-none aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground border-transparent [a&]:hover:bg-primary/90",
|
|
14
|
+
secondary:
|
|
15
|
+
"bg-secondary text-secondary-foreground border-transparent [a&]:hover:bg-secondary/80",
|
|
16
|
+
outline:
|
|
17
|
+
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
18
|
+
destructive:
|
|
19
|
+
"bg-destructive text-destructive-foreground border-transparent [a&]:hover:bg-destructive/90",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
function Badge({
|
|
29
|
+
className,
|
|
30
|
+
variant = "default",
|
|
31
|
+
asChild = false,
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<"span"> &
|
|
34
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
35
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
data-slot="badge"
|
|
40
|
+
data-variant={variant}
|
|
41
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-150 cursor-pointer active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-accent focus-visible:ring-accent/25 focus-visible:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow-[inset_0_1px_0_0_oklch(1_0_0/0.1)] hover:bg-primary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
|
|
16
|
+
outline:
|
|
17
|
+
"border border-border-subtle bg-background hover:bg-surface-subtle",
|
|
18
|
+
secondary:
|
|
19
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
20
|
+
ghost:
|
|
21
|
+
"hover:bg-surface-subtle",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
26
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
28
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
29
|
+
icon: "size-9",
|
|
30
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
"icon-sm": "size-8",
|
|
32
|
+
"icon-lg": "size-10",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "default",
|
|
37
|
+
size: "default",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
function Button({
|
|
43
|
+
className,
|
|
44
|
+
variant = "default",
|
|
45
|
+
size = "default",
|
|
46
|
+
asChild = false,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<"button"> &
|
|
49
|
+
VariantProps<typeof buttonVariants> & {
|
|
50
|
+
asChild?: boolean
|
|
51
|
+
}) {
|
|
52
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Comp
|
|
56
|
+
data-slot="button"
|
|
57
|
+
data-variant={variant}
|
|
58
|
+
data-size={size}
|
|
59
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,106 @@
|
|
|
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 border-border-subtle py-6 transition-colors duration-150",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardElevated({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card"
|
|
22
|
+
className={cn(
|
|
23
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border border-border-subtle py-6 shadow-sm dark:bg-[oklch(0.18_0_0)] transition-colors duration-150", // ok-hardcoded
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-header"
|
|
35
|
+
className={cn(
|
|
36
|
+
"@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",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
data-slot="card-title"
|
|
48
|
+
className={cn("leading-none font-semibold", className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
data-slot="card-description"
|
|
58
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-action"
|
|
68
|
+
className={cn(
|
|
69
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
data-slot="card-content"
|
|
81
|
+
className={cn("px-6", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
data-slot="card-footer"
|
|
91
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
Card,
|
|
99
|
+
CardElevated,
|
|
100
|
+
CardHeader,
|
|
101
|
+
CardFooter,
|
|
102
|
+
CardTitle,
|
|
103
|
+
CardAction,
|
|
104
|
+
CardDescription,
|
|
105
|
+
CardContent,
|
|
106
|
+
}
|