删除网站

This commit is contained in:
2026-05-20 14:39:58 +08:00
parent 2a1d8d8191
commit 9e852d788f
99 changed files with 0 additions and 12504 deletions
-16
View File
@@ -1,16 +0,0 @@
# v0 sandbox internal files
__v0_runtime_loader.js
__v0_devtools.tsx
__v0_jsx-dev-runtime.ts
.snowflake/
.v0-trash/
.vercel/
next.user-config.*
# Environment variables
.env*.local
# Common ignores
node_modules
.next/
.DS_Store
-147
View File
@@ -1,147 +0,0 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(0.965 0.025 77);
--foreground: oklch(0.18 0.03 238);
--card: oklch(0.99 0.012 83);
--card-foreground: oklch(0.18 0.03 238);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.18 0.03 238);
--primary: oklch(0.34 0.09 39);
--primary-foreground: oklch(0.985 0.012 83);
--secondary: oklch(0.91 0.045 78);
--secondary-foreground: oklch(0.24 0.055 43);
--muted: oklch(0.93 0.026 81);
--muted-foreground: oklch(0.48 0.035 238);
--accent: oklch(0.67 0.14 156);
--accent-foreground: oklch(0.16 0.04 169);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.84 0.034 79);
--input: oklch(0.84 0.034 79);
--ring: oklch(0.54 0.12 43);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 1.1rem;
--sidebar: oklch(0.985 0.02 244);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: oklch(0.91 0.045 78);
--sidebar-accent-foreground: oklch(0.24 0.055 43);
--sidebar-border: oklch(0.84 0.034 79);
--sidebar-ring: var(--ring);
}
.dark {
--background: oklch(0.15 0.025 235);
--foreground: oklch(0.93 0.02 252);
--card: oklch(0.2 0.03 235);
--card-foreground: oklch(0.93 0.02 252);
--popover: oklch(0.225 0.03 281);
--popover-foreground: oklch(0.93 0.02 252);
--primary: oklch(0.76 0.14 63);
--primary-foreground: oklch(0.16 0.025 235);
--secondary: oklch(0.28 0.04 222);
--secondary-foreground: oklch(0.93 0.02 252);
--muted: oklch(0.26 0.03 278);
--muted-foreground: oklch(0.76 0.025 252);
--accent: oklch(0.71 0.13 156);
--accent-foreground: oklch(0.14 0.03 160);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.35 0.06 286);
--input: oklch(0.35 0.06 286);
--ring: oklch(0.76 0.14 63);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.2 0.03 282);
--sidebar-foreground: oklch(0.92 0.02 252);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: oklch(0.31 0.08 300);
--sidebar-accent-foreground: oklch(0.93 0.02 252);
--sidebar-border: oklch(0.35 0.06 286);
--sidebar-ring: var(--ring);
}
@theme inline {
--font-sans: 'Avenir Next', 'Gill Sans', 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
background-image:
radial-gradient(
circle at 10% 0%,
color-mix(in oklch, var(--primary) 18%, transparent) 0,
transparent 34%
),
radial-gradient(
circle at 92% 16%,
color-mix(in oklch, var(--accent) 16%, transparent) 0,
transparent 31%
),
radial-gradient(
circle at 50% 100%,
color-mix(in oklch, var(--primary) 10%, transparent) 0,
transparent 44%
),
linear-gradient(
135deg,
color-mix(in oklch, var(--background) 92%, white),
color-mix(in oklch, var(--secondary) 45%, var(--background))
);
background-attachment: fixed;
}
}
-23
View File
@@ -1,23 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { Footer } from "@/components/footer";
import { Header } from "@/components/header";
import { LaunchPage } from "@/components/launch-page";
import type { Lang } from "@/app/page";
export default function LaunchRoute() {
const [lang, setLang] = useState<Lang>("zh");
useEffect(() => {
document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
}, [lang]);
return (
<main className="min-h-screen">
<Header lang={lang} setLang={setLang} />
<LaunchPage lang={lang} />
<Footer lang={lang} />
</main>
);
}
-41
View File
@@ -1,41 +0,0 @@
import type { Metadata } from 'next'
import { Analytics } from '@vercel/analytics/next'
import './globals.css'
export const metadata: Metadata = {
title: 'Heicode - From one idea to production software',
description:
'Heicode organizes AI development teams, connects your code, documents, SK skills and cloud resources, and carries work from product design to production deployment.',
icons: {
icon: [
{
url: '/taiji-logo.png',
media: '(prefers-color-scheme: light)',
},
{
url: '/taiji-logo.png',
media: '(prefers-color-scheme: dark)',
},
{
url: '/taiji-logo.png',
type: 'image/png',
},
],
apple: '/taiji-logo.png',
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="bg-background">
<body className="font-sans antialiased">
{children}
{process.env.NODE_ENV === 'production' && <Analytics />}
</body>
</html>
)
}
-38
View File
@@ -1,38 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { Header } from "@/components/header";
import { LaunchPopup } from "@/components/launch-popup";
import { Hero } from "@/components/hero";
import { Products } from "@/components/products";
import { Features } from "@/components/features";
import { Paradigm } from "@/components/paradigm";
import { Principles } from "@/components/principles";
import { Roadmap } from "@/components/roadmap";
import { CTA } from "@/components/cta";
import { Footer } from "@/components/footer";
export type Lang = "en" | "zh";
export default function HomePage() {
const [lang, setLang] = useState<Lang>("en");
useEffect(() => {
document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
}, [lang]);
return (
<main className="min-h-screen">
<Header lang={lang} setLang={setLang} />
<LaunchPopup lang={lang} />
<Hero lang={lang} />
<Paradigm lang={lang} />
<Products lang={lang} />
<Features lang={lang} />
<Principles lang={lang} />
<Roadmap lang={lang} />
<CTA lang={lang} />
<Footer lang={lang} />
</main>
);
}
-23
View File
@@ -1,23 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import { SwarmPage } from "@/components/swarm-page";
import type { Lang } from "@/app/page";
export default function AgnetSwarmRoute() {
const [lang, setLang] = useState<Lang>("zh");
useEffect(() => {
document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
}, [lang]);
return (
<main className="min-h-screen">
<Header lang={lang} setLang={setLang} />
<SwarmPage lang={lang} />
<Footer lang={lang} />
</main>
);
}
-21
View File
@@ -1,21 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
-60
View File
@@ -1,60 +0,0 @@
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import type { Lang } from "@/app/page";
const MANAGER_URL = "https://code.xinghanlab.com/";
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
const copy = {
en: {
title: "Move an idea into a real development flow",
description:
"Heicode does more than generate code. It organizes product intent, code, permissions, models, deployment, and maintenance into one auditable software lifecycle.",
primary: "Enter Heicode",
secondary: "Register account",
},
zh: {
title: "让想法进入真实开发流程",
description:
"不是只生成代码,而是把产品、代码、权限、模型、部署和维护组织成一个可审计的软件生命周期。",
primary: "进入 Heicode",
secondary: "注册账号",
},
} satisfies Record<Lang, {
title: string;
description: string;
primary: string;
secondary: string;
}>;
export function CTA({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section className="border-t border-border bg-foreground py-20 text-background sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 text-lg text-background/70">
{t.description}
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Button size="lg" className="h-12 rounded-full bg-primary px-7 text-primary-foreground hover:bg-primary/90" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" size="lg" className="h-12 rounded-full border-background/30 bg-transparent px-7 text-background hover:bg-background hover:text-foreground" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
{t.secondary}
</a>
</Button>
</div>
</div>
</div>
</section>
);
}
-114
View File
@@ -1,114 +0,0 @@
import { FileText, GitBranch, ShieldCheck, Workflow } from "lucide-react";
import type { Lang } from "@/app/page";
const icons = [FileText, GitBranch, Workflow, ShieldCheck];
const copy = {
en: {
eyebrow: "Core abilities",
title: "Four loops Heicode must close",
description:
"From idea, resources, team, and permissions to execution, launch, and maintenance, Heicode turns software delivery into an auditable lifecycle.",
features: [
{
title: "Ideas become development work",
description:
"Users do not need a complete PRD first. Heicode turns natural-language goals into requirement summaries, task drafts, prototype notes, role suggestions, and resource needs.",
},
{
title: "Resource binding and permission assignment",
description:
"Git, SK, project docs, and cloud resources are modeled as Resource Bindings, then assigned to specific sub Agnet roles through Resource Grants.",
},
{
title: "Sub Agnets execute continuously",
description:
"Work is not thrown over the wall once. Agnets keep moving through requirements, design, development, testing, fixes, and deployment.",
},
{
title: "Usage, logs, security, and audit loop",
description:
"Model balance, call logs, resource access, risky approvals, and deployment records return to Heicode. Long-lived secrets never enter Git, Markdown, or logs.",
},
],
},
zh: {
eyebrow: "Core abilities",
title: "Heicode 要闭环的四件事",
description:
"从想法、资源、团队、权限到执行、上线、维护,形成一条可审计的软件生命周期。",
features: [
{
title: "想法转开发任务",
description:
"用户不需要先写完整 PRD。Heicode 会把自然语言目标整理成需求摘要、任务草案、原型描述、角色建议和资源需求。",
},
{
title: "资源绑定和权限分配",
description:
"Git、SK、项目文档和云资源被建模为 Resource Binding,再通过 Resource Grant 分配给具体子 Agnet 角色。",
},
{
title: "子 Agnet 持续执行",
description:
"不是一次性把任务丢出去,而是在需求、设计、开发、测试、修复、部署的子环节中持续调用 Agnet 推进。",
},
{
title: "用量、日志、安全和审计闭环",
description:
"模型余额、调用日志、资源访问、高危审批、部署记录统一回到 Heicode,长期密钥不进入 Git、Markdown 或日志。",
},
],
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
description: string;
features: Array<{ title: string; description: string }>;
}>;
export function Features({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section id="features" className="border-y border-border bg-card/30 py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-bold tracking-widest text-primary uppercase">
{t.eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 text-muted-foreground">
{t.description}
</p>
</div>
<div className="mt-14 grid gap-5 sm:grid-cols-2 lg:gap-6">
{t.features.map((feature, index) => {
const Icon = icons[index];
return (
<div
key={feature.title}
className="relative flex gap-6 overflow-hidden rounded-[1.7rem] border border-border bg-background/80 p-6 shadow-sm sm:p-8"
>
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<Icon className="h-5 w-5" />
</div>
<div>
<h3 className="text-lg font-semibold">{feature.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{feature.description}
</p>
</div>
<span className="absolute -bottom-4 right-5 text-7xl font-black text-secondary/55">
{String(index + 1).padStart(2, "0")}
</span>
</div>
)})}
</div>
</div>
</section>
);
}
-137
View File
@@ -1,137 +0,0 @@
import Link from "next/link";
import Image from "next/image";
import type { Lang } from "@/app/page";
const DOCS_URL = "https://doc.xinghanlab.com/";
const copy = {
en: {
tagline: ["From one idea to production software", "Organize an AI development team with code and cloud resources"],
copyright: "All rights reserved.",
footerLinks: {
Product: [
{ label: "Heicode Client", href: "#" },
{ label: "Heicode Manager", href: "https://code.xinghanlab.com/" },
{ label: "Agnet Platform", href: "https://agnet.taijiaicloud.com/" },
{ label: "Agnet Swarm", href: "/swarm" },
],
Resources: [
{ label: "Documentation", href: DOCS_URL },
{ label: "Resource binding", href: "#" },
{ label: "Secret vault", href: "#" },
{ label: "CodeGW boundary", href: "#" },
{ label: "Product demo", href: "#" },
],
Company: [
{ label: "About us", href: "https://www.taijiaicloud.com/" },
{ label: "Blog", href: "#" },
{ label: "Careers", href: "#" },
{ label: "Contact", href: "mailto:november@taijiaicloud.com" },
],
Legal: [
{ label: "Privacy", href: "#" },
{ label: "Terms", href: "#" },
{ label: "Security", href: "#" },
],
},
},
zh: {
tagline: ["从一个想法,到可上线的软件产品", "组织 AI 开发团队,接入代码和云资源"],
copyright: "保留所有权利。",
footerLinks: {
产品: [
{ label: "Heicode 客户端", href: "#" },
{ label: "Heicode Manager", href: "https://code.xinghanlab.com/" },
{ label: "Agnet 平台", href: "https://agnet.taijiaicloud.com/" },
{ label: "Agnet 蜂群", href: "/swarm" },
],
资源: [
{ label: "文档", href: DOCS_URL },
{ label: "资源绑定", href: "#" },
{ label: "密钥保管器", href: "#" },
{ label: "CodeGW 边界", href: "#" },
{ label: "产品演示", href: "#" },
],
公司: [
{ label: "关于我们", href: "https://www.taijiaicloud.com/" },
{ label: "博客", href: "#" },
{ label: "加入我们", href: "#" },
{ label: "联系方式", href: "mailto:november@taijiaicloud.com" },
],
法律: [
{ label: "隐私政策", href: "#" },
{ label: "服务条款", href: "#" },
{ label: "安全", href: "#" },
],
},
},
} satisfies Record<Lang, {
tagline: [string, string];
copyright: string;
footerLinks: Record<string, Array<{ label: string; href: string }>>;
}>;
export function Footer({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<footer className="border-t border-border bg-background">
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8 lg:py-16">
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-5">
<div className="lg:col-span-1">
<Link href="/" className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-md border border-border/70 bg-card/60 text-foreground">
<Image src="/taiji-logo.png" alt="Heicode" width={18} height={18} />
</div>
<span className="text-lg font-black tracking-tight text-foreground">
Heicode
</span>
</Link>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
{t.tagline[0]}
<br />
{t.tagline[1]}
</p>
</div>
{Object.entries(t.footerLinks).map(([category, links]) => (
<div key={category}>
<h3 className="text-sm font-semibold">{category}</h3>
<ul className="mt-4 space-y-3">
{links.map((link) => (
<li key={link.label}>
{link.href.startsWith("http") || link.href.startsWith("mailto:") ? (
<a
href={link.href}
target="_blank"
rel="noreferrer"
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{link.label}
</a>
) : (
<Link
href={link.href}
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{link.label}
</Link>
)}
</li>
))}
</ul>
</div>
))}
</div>
<div className="mt-12 border-t border-border pt-8">
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
<p className="text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} Taiji. {t.copyright}
</p>
</div>
</div>
</div>
</footer>
);
}
-160
View File
@@ -1,160 +0,0 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import { Menu, X } from "lucide-react";
import type { Lang } from "@/app/page";
const copy = {
en: {
navLinks: [
{ href: "/#workflow", label: "Workflow" },
{ href: "/#products", label: "Surfaces" },
{ href: "/#features", label: "Capabilities" },
{ href: "/swarm", label: "Swarm" },
{ href: "/launch", label: "Launch" },
{ href: "/#security", label: "Security" },
],
register: "Register",
start: "Get started",
toggle: "中文",
menuOpen: "Open menu",
menuClose: "Close menu",
},
zh: {
navLinks: [
{ href: "/#workflow", label: "流程" },
{ href: "/#products", label: "产品面" },
{ href: "/#features", label: "能力" },
{ href: "/swarm", label: "蜂群" },
{ href: "/launch", label: "上线" },
{ href: "/#security", label: "安全" },
],
register: "注册",
start: "开始使用",
toggle: "EN",
menuOpen: "打开菜单",
menuClose: "关闭菜单",
},
} satisfies Record<Lang, {
navLinks: Array<{ href: string; label: string }>;
register: string;
start: string;
toggle: string;
menuOpen: string;
menuClose: string;
}>;
const MANAGER_URL = "https://code.xinghanlab.com/";
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
const DOCS_URL = "https://doc.xinghanlab.com/";
export function Header({
lang,
setLang,
}: {
lang: Lang;
setLang: (lang: Lang) => void;
}) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const t = copy[lang];
const toggleLang = () => setLang(lang === "en" ? "zh" : "en");
return (
<header className="fixed top-0 left-0 right-0 z-50 border-b border-border/60 bg-background/75 backdrop-blur-xl">
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="/" className="flex items-center gap-2">
<div className="flex h-9 w-9 items-center justify-center rounded-xl border border-border/70 bg-card/80 text-foreground shadow-sm">
<Image src="/taiji-logo.png" alt="Heicode" width={18} height={18} />
</div>
<span className="text-lg font-black tracking-tight text-foreground">
Heicode
</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-8 md:flex">
{t.navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{link.label}
</Link>
))}
</nav>
<div className="hidden items-center gap-4 md:flex">
<Button variant="outline" size="sm" className="rounded-full bg-card/50" onClick={toggleLang}>
{t.toggle}
</Button>
<Button variant="ghost" size="sm" className="rounded-full" asChild>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
{lang === "zh" ? "文档" : "Docs"}
</a>
</Button>
<Button variant="outline" size="sm" className="rounded-full bg-card/50" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
{t.register}
</a>
</Button>
<Button size="sm" className="rounded-full bg-foreground text-background hover:bg-foreground/90" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.start}
</a>
</Button>
</div>
{/* Mobile Menu Button */}
<button
className="md:hidden"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? t.menuClose : t.menuOpen}
>
{mobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<div className="border-t border-border bg-background md:hidden">
<nav className="flex flex-col px-4 py-4">
{t.navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="py-3 text-sm text-muted-foreground transition-colors hover:text-foreground"
onClick={() => setMobileMenuOpen(false)}
>
{link.label}
</Link>
))}
<div className="mt-4 flex flex-col gap-2">
<Button variant="outline" size="sm" className="justify-start rounded-full bg-card/50" onClick={toggleLang}>
{t.toggle}
</Button>
<Button variant="ghost" size="sm" className="justify-start rounded-full" asChild>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
{lang === "zh" ? "文档" : "Docs"}
</a>
</Button>
<Button variant="outline" size="sm" className="justify-start rounded-full bg-card/50" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
{t.register}
</a>
</Button>
<Button size="sm" className="rounded-full bg-foreground text-background hover:bg-foreground/90" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.start}
</a>
</Button>
</div>
</nav>
</div>
)}
</header>
);
}
-174
View File
@@ -1,174 +0,0 @@
import { Button } from "@/components/ui/button";
import { ArrowRight, CheckCircle2, LockKeyhole, Play, Sparkles } from "lucide-react";
import type { Lang } from "@/app/page";
const MANAGER_URL = "https://code.xinghanlab.com/";
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
const copy = {
en: {
eyebrow: "From idea to production software",
titleA: "One idea,",
titleB: "one AI development team.",
intro:
"Heicode organizes sub-Agnet teams, connects your code, documents, SK skills and cloud resources, and continuously carries work through requirements, development, testing, fixes, deployment and maintenance.",
primary: "Start with Heicode",
secondary: "Register",
bullets: ["Input ideas in the client", "Bind resources in Manager", "Ship through Agnet"],
cockpit: "Heicode Task Cockpit",
project: "Small-team task management SaaS",
running: "Running",
inputLabel: "User input",
prompt:
"I want to build a small-team task management SaaS with login, projects, tasks, comments, notifications and admin, deployed to Azure.",
agents: [
["Product Agnet", "Requirements and acceptance", "Done"],
["Backend Agnet", "APIs and data model", "Building"],
["Frontend Agnet", "Pages and interaction", "Waiting for API"],
["Ops Agnet", "Deployment and observability", "Approval pending"],
],
approvalTitle: "Production deployment requires client approval",
approvalDesc:
"The secret vault will derive a 15-minute short-lived credential. Sub Agnets never receive long-term secrets.",
},
zh: {
eyebrow: "从想法到上线的软件生命周期平台",
titleA: "一个想法,",
titleB: "一支 AI 开发团队。",
intro:
"Heicode 组织子 Agnet,接入你的代码、文档、SK 和云资源,持续完成需求、开发、测试、修复、部署和后续维护。",
primary: "开始使用 Heicode",
secondary: "注册",
bullets: ["客户端输入想法", "Manager 绑定资源", "Agnet 交付部署"],
cockpit: "Heicode Task Cockpit",
project: "小团队任务管理 SaaS",
running: "运行中",
inputLabel: "用户输入",
prompt:
"我想做一个小团队任务管理 SaaS,需要登录、项目、任务、评论、通知和后台管理,希望部署到 Azure。",
agents: [
["Product Agnet", "需求和验收标准", "完成"],
["Backend Agnet", "API 与数据模型", "开发中"],
["Frontend Agnet", "页面与交互", "等待接口"],
["Ops Agnet", "部署与观测", "待审批"],
],
approvalTitle: "生产部署需要客户端审批",
approvalDesc: "密钥保管器将派生 15 分钟短期凭证,子 Agnet 不接触长期密钥。",
},
} satisfies Record<Lang, {
eyebrow: string;
titleA: string;
titleB: string;
intro: string;
primary: string;
secondary: string;
bullets: string[];
cockpit: string;
project: string;
running: string;
inputLabel: string;
prompt: string;
agents: string[][];
approvalTitle: string;
approvalDesc: string;
}>;
export function Hero({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section className="relative overflow-hidden pt-28 pb-16 sm:pt-36 sm:pb-24">
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,color-mix(in_oklch,var(--foreground)_5%,transparent)_1px,transparent_1px),linear-gradient(to_bottom,color-mix(in_oklch,var(--foreground)_5%,transparent)_1px,transparent_1px)] bg-[size:42px_42px]" />
<div className="pointer-events-none absolute left-1/2 top-24 h-[420px] w-[70vw] -translate-x-1/2 rounded-full bg-primary/10 blur-[120px]" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[1.03fr_0.97fr] lg:items-center">
<div>
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-border bg-card/70 px-4 py-2 text-sm font-semibold shadow-sm backdrop-blur">
<Sparkles className="h-4 w-4 text-primary" />
{t.eyebrow}
</div>
<h1 className="max-w-4xl text-5xl font-black leading-[0.95] tracking-[-0.06em] sm:text-7xl lg:text-8xl">
{t.titleA}
<span className="block text-primary">{t.titleB}</span>
</h1>
<p className="mt-7 max-w-2xl text-lg leading-8 text-muted-foreground sm:text-xl">
{t.intro}
</p>
<div className="mt-9 flex flex-col gap-4 sm:flex-row">
<Button size="lg" className="h-12 rounded-full bg-foreground px-7 text-background hover:bg-foreground/90" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" size="lg" className="h-12 rounded-full border-foreground/20 bg-card/50 px-7" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
<Play className="h-4 w-4" />
{t.secondary}
</a>
</Button>
</div>
<div className="mt-8 grid max-w-2xl gap-3 text-sm text-muted-foreground sm:grid-cols-3">
{t.bullets.map((item) => (
<div key={item} className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-accent" />
{item}
</div>
))}
</div>
</div>
<div className="relative">
<div className="absolute -left-6 -top-6 hidden h-24 w-24 rounded-full border border-primary/30 lg:block" />
<div className="rounded-[2rem] border border-foreground/10 bg-foreground p-3 shadow-2xl">
<div className="overflow-hidden rounded-[1.5rem] bg-[#10151d] text-white">
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
<div>
<p className="text-xs uppercase tracking-[0.22em] text-white/45">{t.cockpit}</p>
<p className="mt-1 font-semibold">{t.project}</p>
</div>
<span className="rounded-full bg-emerald-400/15 px-3 py-1 text-xs font-semibold text-emerald-200">
{t.running}
</span>
</div>
<div className="grid gap-3 p-5">
<div className="rounded-2xl bg-white/[0.06] p-4">
<p className="text-xs text-white/45">{t.inputLabel}</p>
<p className="mt-2 text-sm leading-6 text-white/85">{t.prompt}</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{t.agents.map(([role, work, state]) => (
<div key={role} className="rounded-2xl border border-white/10 bg-white/[0.04] p-4">
<p className="font-semibold">{role}</p>
<p className="mt-1 text-xs text-white/50">{work}</p>
<p className="mt-4 text-xs font-semibold text-primary-foreground/90">{state}</p>
</div>
))}
</div>
<div className="rounded-2xl border border-amber-300/20 bg-amber-300/10 p-4">
<div className="flex items-start gap-3">
<LockKeyhole className="mt-0.5 h-5 w-5 text-amber-200" />
<div>
<p className="text-sm font-semibold text-amber-100">{t.approvalTitle}</p>
<p className="mt-1 text-xs leading-5 text-amber-100/65">
{t.approvalDesc}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
-184
View File
@@ -1,184 +0,0 @@
import { Button } from "@/components/ui/button";
import { ArrowRight, CalendarDays, CheckCircle2, Rocket, ShieldCheck, Sparkles, Workflow } from "lucide-react";
import type { Lang } from "@/app/page";
const MANAGER_URL = "https://code.xinghanlab.com/";
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
const copy = {
en: {
eyebrow: "Full launch",
date: "June 5, 2026",
title: "Heicode goes fully live on June 5, 2026.",
lead:
"The public launch brings together Heicode Client, Heicode Manager, Agnet Swarm, model usage, resource binding and approval audit into one delivery-ready product flow.",
primary: "Register now",
secondary: "Enter Heicode",
highlights: ["Client-first workflow", "Agnet Swarm execution", "Resource and secret governance", "Usage, billing and audit visibility"],
sections: [
{
icon: Rocket,
title: "Launch scope",
desc: "Users can register, enter Heicode, bind development resources, start an Agnet workflow and keep software delivery moving through the client.",
},
{
icon: Workflow,
title: "Delivery loop",
desc: "Idea intake, task decomposition, resource authorization, Agnet execution, review, approval and deployment records stay in one traceable loop.",
},
{
icon: ShieldCheck,
title: "Governed access",
desc: "Production actions, cloud resources, secrets and model budget are controlled by explicit approvals and auditable grants.",
},
],
timelineTitle: "Launch path",
timeline: [
["Now", "Open registration and product preview entry."],
["Before launch", "Complete onboarding, docs, Manager links and launch readiness checks."],
["June 5, 2026", "Full public launch of the Heicode product flow."],
],
note:
"The launch page is designed as the public landing point for announcements, registration, and product readiness communication.",
},
zh: {
eyebrow: "全面上线",
date: "2026年6月5日",
title: "Heicode 将于 2026年6月5日全面上线。",
lead:
"全面上线版本会把 Heicode 客户端、Heicode Manager、Agnet 蜂群、模型用量、资源绑定和审批审计整合成一条可交付的产品流程。",
primary: "立即注册",
secondary: "进入 Heicode",
highlights: ["客户端主流程", "Agnet 蜂群执行", "资源与密钥治理", "用量、计费与审计可见"],
sections: [
{
icon: Rocket,
title: "上线范围",
desc: "用户可以注册、进入 Heicode、绑定开发资源、启动 Agnet 工作流,并通过客户端持续推进软件交付。",
},
{
icon: Workflow,
title: "交付闭环",
desc: "想法输入、任务拆解、资源授权、Agnet 执行、复核、审批和部署记录会留在同一个可追踪流程中。",
},
{
icon: ShieldCheck,
title: "受控访问",
desc: "生产动作、云资源、密钥和模型预算都通过明确审批和可审计授权控制。",
},
],
timelineTitle: "上线节奏",
timeline: [
["现在", "开放注册入口和产品预览入口。"],
["上线前", "完成新手引导、文档、Manager 链接和上线检查。"],
["2026年6月5日", "Heicode 产品流程全面公开上线。"],
],
note:
"该页面作为对外上线公告、注册引导和产品准备状态说明的统一入口。",
},
} satisfies Record<Lang, {
eyebrow: string;
date: string;
title: string;
lead: string;
primary: string;
secondary: string;
highlights: string[];
sections: Array<{ icon: typeof Rocket; title: string; desc: string }>;
timelineTitle: string;
timeline: Array<[string, string]>;
note: string;
}>;
export function LaunchPage({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section className="relative overflow-hidden pt-28 pb-20 sm:pt-36 sm:pb-28">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_15%_10%,color-mix(in_oklch,var(--primary)_18%,transparent),transparent_32%),radial-gradient(circle_at_85%_15%,color-mix(in_oklch,var(--accent)_16%,transparent),transparent_30%)]" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[1fr_0.9fr] lg:items-center">
<div>
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-border bg-card/75 px-4 py-2 text-sm font-semibold shadow-sm backdrop-blur">
<Sparkles className="h-4 w-4 text-primary" />
{t.eyebrow}
</div>
<div className="mb-5 inline-flex items-center gap-2 rounded-2xl bg-foreground px-4 py-3 text-background shadow-xl">
<CalendarDays className="h-5 w-5 text-primary" />
<span className="text-sm font-black tracking-wide">{t.date}</span>
</div>
<h1 className="max-w-4xl text-5xl font-black leading-[0.95] tracking-[-0.06em] sm:text-7xl">
{t.title}
</h1>
<p className="mt-7 max-w-2xl text-lg leading-8 text-muted-foreground sm:text-xl">
{t.lead}
</p>
<div className="mt-9 flex flex-col gap-4 sm:flex-row">
<Button size="lg" className="h-12 rounded-full bg-primary px-7 text-primary-foreground hover:bg-primary/90" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" size="lg" className="h-12 rounded-full border-foreground/20 bg-card/50 px-7" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.secondary}
</a>
</Button>
</div>
<div className="mt-8 grid gap-3 text-sm text-muted-foreground sm:grid-cols-2">
{t.highlights.map((item) => (
<div key={item} className="flex items-center gap-2 rounded-full bg-card/70 px-4 py-3">
<CheckCircle2 className="h-4 w-4 text-accent" />
{item}
</div>
))}
</div>
</div>
<div className="rounded-[2rem] border border-border bg-card/70 p-5 shadow-2xl backdrop-blur">
<div className="rounded-[1.5rem] bg-foreground p-6 text-background">
<p className="text-sm font-bold uppercase tracking-[0.22em] text-background/45">
{t.timelineTitle}
</p>
<div className="mt-6 space-y-4">
{t.timeline.map(([time, desc], index) => (
<div key={time} className="grid grid-cols-[auto_1fr] gap-4">
<div className="flex flex-col items-center">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-sm font-black text-primary-foreground">
{index + 1}
</div>
{index < t.timeline.length - 1 && <div className="h-full w-px bg-background/20" />}
</div>
<div className="pb-5">
<h3 className="font-semibold">{time}</h3>
<p className="mt-1 text-sm leading-6 text-background/65">{desc}</p>
</div>
</div>
))}
</div>
<p className="mt-2 rounded-2xl border border-background/10 bg-background/5 p-4 text-sm leading-6 text-background/70">
{t.note}
</p>
</div>
</div>
</div>
<div className="mt-16 grid gap-5 md:grid-cols-3">
{t.sections.map((section) => {
const Icon = section.icon;
return (
<div key={section.title} className="rounded-[1.6rem] border border-border bg-card/70 p-6 shadow-sm">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary">
<Icon className="h-6 w-6" />
</div>
<h2 className="text-xl font-bold">{section.title}</h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">{section.desc}</p>
</div>
);
})}
</div>
</div>
</section>
);
}
-103
View File
@@ -1,103 +0,0 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { ArrowRight, CalendarDays, Rocket, X } from "lucide-react";
import type { Lang } from "@/app/page";
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
const LAUNCH_URL = "/launch";
const copy = {
en: {
eyebrow: "Full launch",
title: "Heicode goes fully live on June 5, 2026.",
desc: "Register now and follow launch readiness for Client, Manager, Agnet Swarm, resource binding and audit.",
date: "June 5, 2026",
primary: "Register now",
secondary: "Launch details",
close: "Close launch announcement",
},
zh: {
eyebrow: "全面上线",
title: "Heicode 将于 2026年6月5日全面上线。",
desc: "现在可以注册,并查看客户端、Manager、Agnet 蜂群、资源绑定和审计能力的上线准备。",
date: "2026年6月5日",
primary: "立即注册",
secondary: "查看上线详情",
close: "关闭上线公告",
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
desc: string;
date: string;
primary: string;
secondary: string;
close: string;
}>;
export function LaunchPopup({ lang }: { lang: Lang }) {
const [open, setOpen] = useState(true);
const previousLang = useRef(lang);
const t = copy[lang];
useEffect(() => {
if (previousLang.current !== "zh" && lang === "zh") {
setOpen(true);
}
previousLang.current = lang;
}, [lang]);
if (!open) {
return null;
}
return (
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-foreground/55 px-4 py-8 backdrop-blur-sm">
<div className="relative w-full max-w-2xl overflow-hidden rounded-[2rem] border border-border bg-background p-6 shadow-2xl sm:p-8">
<button
type="button"
aria-label={t.close}
onClick={() => setOpen(false)}
className="absolute right-4 top-4 rounded-full border border-border bg-card/80 p-2 text-muted-foreground transition-colors hover:text-foreground"
>
<X className="h-5 w-5" />
</button>
<div className="pointer-events-none absolute -right-24 -top-24 h-56 w-56 rounded-full bg-primary/15 blur-3xl" />
<div className="pointer-events-none absolute -bottom-24 -left-24 h-56 w-56 rounded-full bg-accent/15 blur-3xl" />
<div className="relative">
<div className="mb-5 inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/10 px-4 py-2 text-sm font-bold text-primary">
<Rocket className="h-4 w-4" />
{t.eyebrow}
</div>
<div className="mb-6 flex w-fit items-center gap-2 rounded-2xl bg-foreground px-4 py-3 text-background">
<CalendarDays className="h-5 w-5 text-primary" />
<span className="text-sm font-black tracking-wide">{t.date}</span>
</div>
<h2 className="max-w-xl text-3xl font-black leading-tight tracking-[-0.04em] sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 max-w-xl text-base leading-7 text-muted-foreground sm:text-lg">
{t.desc}
</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Button className="h-12 rounded-full bg-primary px-7 text-primary-foreground hover:bg-primary/90" asChild>
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" className="h-12 rounded-full bg-card/50 px-7" asChild>
<a href={LAUNCH_URL}>{t.secondary}</a>
</Button>
</div>
</div>
</div>
</div>
);
}
-123
View File
@@ -1,123 +0,0 @@
import { ArrowRight } from "lucide-react";
import type { Lang } from "@/app/page";
const copy = {
en: {
eyebrow: "User journey",
title: "Users see one continuous flow, not a wall of admin panels",
description:
"The homepage and primary workflow stay anchored to the active job: idea, resources, team, permissions, execution, launch, and maintenance.",
pathTitle: "Minimum MVP path",
pathDescription:
"Sign in, download the client, describe the idea, bind Git and cloud resources, confirm role permissions, preview the manifest, create an Agnet deployment placeholder, then keep development and testing moving.",
steps: [
["Sign in", "Users only sign in to Heicode, not CodeGW or the OpenBao console."],
["Describe the idea", "Capture the product, constraints, existing code, and deployment target in the client."],
["Bind resources", "Manager authorizes Git, SK, docs, and cloud accounts while storing secret_ref only."],
["Generate the team", "Recommend Product, Architect, Frontend, Backend, Reviewer, Ops, and other sub Agnets."],
["Assign permissions", "Confirm resources, actions, TTL, and approval requirements through role cards."],
["Close the loop", "Agnets keep pushing requirements, development, testing, fixes, deployment, and maintenance."],
],
cards: [
["Users do not write manifests", "The default flow shows roles, resources, actions, and approvals. Advanced users can expand the permission manifest."],
["The web app is not the IDE", "Manager handles auxiliary management and observation. Main development, continuation, and approvals return to the client."],
["Risky actions need explicit approval", "Production deploys, cloud operations, database writes, and production secret access must be confirmed in the client."],
],
},
zh: {
eyebrow: "User journey",
title: "用户看到的是连续流程,不是一堆后台菜单",
description:
"首页第一屏和主流程都围绕当前任务展开:想法、资源、团队、权限、执行、上线、维护。",
pathTitle: "MVP 最小路径",
pathDescription:
"登录、下载客户端、输入想法、绑定 Git 和云资源、确认角色权限、预览 manifest、创建 Agnet 部署占位任务、持续推进开发与测试。",
steps: [
["登录", "用户只登录 Heicode,不进入 CodeGW 后台或 OpenBao 控制台。"],
["输入想法", "在客户端描述产品、约束、已有代码和部署目标。"],
["绑定资源", "Manager 授权 Git、SK、文档、云账号并保存 secret_ref。"],
["生成团队", "推荐 Product、Architect、Frontend、Backend、Reviewer、Ops 等子 Agnet。"],
["分配权限", "用角色卡确认资源、动作、有效期和审批要求。"],
["执行闭环", "Agnet 持续推进需求、开发、测试、修复、部署和维护。"],
],
cards: [
["用户不手写 manifest", "普通流程只展示角色、资源、动作和审批要求,高级用户可以展开查看 permission manifest。"],
["网页不是 IDE", "Manager 完成辅助管理和观测,主开发对话、继续推进和审批仍回到客户端。"],
["高危操作单独审批", "生产部署、云操作、数据库写入、访问生产密钥都必须在客户端确认。"],
],
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
description: string;
pathTitle: string;
pathDescription: string;
steps: Array<[string, string]>;
cards: Array<[string, string]>;
}>;
export function Paradigm({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section id="workflow" className="py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[0.8fr_1.2fr] lg:items-start">
<div>
<p className="text-sm font-bold uppercase tracking-widest text-primary">
{t.eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-5 text-muted-foreground">
{t.description}
</p>
<div className="mt-8 rounded-[1.7rem] border border-primary/20 bg-primary/10 p-6">
<p className="text-sm font-semibold text-primary">{t.pathTitle}</p>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
{t.pathDescription}
</p>
</div>
</div>
<div className="rounded-[2rem] border border-border bg-card/60 p-4 shadow-sm">
<div className="grid gap-3">
{t.steps.map(([title, description], index) => (
<div
key={title}
className="grid gap-3 rounded-[1.4rem] border border-border bg-background/70 p-5 sm:grid-cols-[auto_1fr_auto] sm:items-center"
>
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-foreground text-sm font-black text-background">
{String(index + 1).padStart(2, "0")}
</div>
<div>
<h3 className="font-semibold">{title}</h3>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
{description}
</p>
</div>
{index < t.steps.length - 1 && (
<ArrowRight className="hidden h-5 w-5 text-muted-foreground sm:block" />
)}
</div>
))}
</div>
</div>
</div>
<div className="mt-14 grid gap-5 md:grid-cols-3">
{t.cards.map(([title, desc]) => (
<div
key={title}
className="rounded-[1.5rem] border border-border bg-card/60 p-6"
>
<h3 className="font-semibold">{title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{desc}</p>
</div>
))}
</div>
</div>
</section>
);
}
-121
View File
@@ -1,121 +0,0 @@
import type { Lang } from "@/app/page";
const copy = {
en: {
eyebrow: "Security model",
title: "Resources are usable. Secrets do not leak.",
description:
"Users authorize resources, the platform holds credentials, and sub Agnets only receive short-lived access inside the approved scope.",
principles: [
{
number: "01",
title: "Secrets never enter the product surface",
description:
"Git tokens, cloud access keys, SSH private keys, database passwords, and CodeGW keys never enter Git, Markdown, frontend responses, or ordinary logs.",
},
{
number: "02",
title: "Only secret_ref is stored",
description:
"The Manager database stores resource metadata, permission relationships, and secret_ref. Real long-lived credentials are held by the vault.",
},
{
number: "03",
title: "Sub Agnets only receive short-lived credentials",
description:
"After approval, the platform derives short-lived, least-privilege, auditable credentials by permission and TTL. Sub Agnets do not keep long-lived secrets.",
},
{
number: "04",
title: "Risky actions are approved in the client",
description:
"Production deploys, cloud changes, database writes, production secret access, and large budget consumption require explicit client confirmation.",
},
{
number: "05",
title: "Audit answers the critical questions",
description:
"Who used which resource, for which task, through which sub Agnet, at what time, with what action, and whether approval happened.",
},
],
},
zh: {
eyebrow: "Security model",
title: "资源可用,密钥不外泄",
description:
"用户授权资源,平台托管凭证,子 Agnet 只获得被批准范围内的短期访问能力。",
principles: [
{
number: "01",
title: "密钥不进入产品表面",
description: "Git token、云 access key、SSH 私钥、数据库密码和 CodeGW key 不进入 Git、Markdown、前端响应或普通日志。",
},
{
number: "02",
title: "只保存 secret_ref",
description: "Manager 数据库保存资源元数据、权限关系和 secret_ref,真实长期凭证由密钥保管器托管。",
},
{
number: "03",
title: "子 Agnet 只拿短期凭证",
description: "审批通过后,平台按权限和 TTL 派生短期、最小权限、可审计凭证;子 Agnet 不保存长期密钥。",
},
{
number: "04",
title: "高危操作只在客户端审批",
description: "生产部署、云资源变更、数据库写入、访问生产密钥和大额预算消耗,必须由用户在客户端明确确认。",
},
{
number: "05",
title: "审计能回答关键问题",
description: "谁在什么时候,为了哪个任务,让哪个子 Agnet 使用了哪个资源,执行了什么动作,是否经过审批。",
},
],
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
description: string;
principles: Array<{ number: string; title: string; description: string }>;
}>;
export function Principles({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section id="security" className="border-t border-border bg-card/30 py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-bold tracking-widest text-primary uppercase">
{t.eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 text-muted-foreground">
{t.description}
</p>
</div>
<div className="mt-16 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{t.principles.map((principle) => (
<div
key={principle.number}
className="group relative overflow-hidden rounded-[1.6rem] border border-border bg-background/80 p-6 transition-colors hover:border-primary/40"
>
<span className="absolute -top-2 -right-2 text-7xl font-black text-secondary/40 transition-colors group-hover:text-primary/10">
{principle.number}
</span>
<div className="relative">
<h3 className="text-lg font-semibold">{principle.title}</h3>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
{principle.description}
</p>
</div>
</div>
))}
</div>
</div>
</section>
);
}
-154
View File
@@ -1,154 +0,0 @@
import { Bot, CreditCard, KeyRound, Layers, Monitor, Terminal } from "lucide-react";
import type { Lang } from "@/app/page";
const icons = [Terminal, Monitor, Bot, CreditCard, KeyRound, Layers];
const copy = {
en: {
eyebrow: "Product surfaces",
title: "Users operate Heicode. The underlying systems stay in their lanes.",
description:
"The client is the primary experience and Manager is the auxiliary console. CodeGW, the vault, and the Agnet platform are not exposed as ordinary user backends.",
products: [
{
name: "Heicode Client",
description:
"The primary user experience. Describe ideas, continue development, review Agnet execution feedback, and approve risky operations before production deploys, secret access, or cloud changes.",
features: ["Idea input", "Task progress", "Execution feedback", "Risk approval"],
},
{
name: "Heicode Manager",
description:
"The browser-side control console for account safety, client downloads, resource binding, Agnet deployment, task status, balance, and audit.",
features: ["Resource binding", "Deploy Agnets", "Model balance", "Audit logs"],
},
{
name: "Agnet Platform",
description:
"The AI development-team execution layer. It runs Product, Frontend, Backend, Reviewer, Ops, and other sub Agnets on AKS, then reports state and events back.",
features: ["Role teams", "Continuous execution", "SK calls", "State callback"],
},
{
name: "CodeGW",
description:
"The internal model gateway and billing foundation. Heicode only exposes the models, balance, quota, usage, and call logs ordinary users need.",
features: ["Model gateway", "Balance quota", "Call logs", "No admin exposure"],
},
{
name: "Secret Vault",
description:
"Credential custody backed by OpenBao. Long-lived secrets only enter the vault; sub Agnets receive short-lived, least-privilege credentials.",
features: ["secret_ref", "Short-lived creds", "Rotate and revoke", "Audit trail"],
},
{
name: "Resource Context",
description:
"Git, SK, documents, cloud accounts, and cloud resources become Resource Bindings and Grants that can be authorized, revoked, and audited.",
features: ["Git", "SK", "Project docs", "Cloud resources"],
},
],
},
zh: {
eyebrow: "Product surfaces",
title: "用户只操作 Heicode,底层能力各归其位",
description:
"客户端是主体验,Manager 是辅助控制台;CodeGW、密钥保管器和 Agnet 平台不作为普通用户后台。",
products: [
{
name: "Heicode 客户端",
description:
"用户主体验。输入想法、继续开发、查看 Agnet 执行反馈,并在生产部署、密钥访问、云操作前完成高危审批。",
features: ["想法输入", "任务推进", "执行反馈", "高危审批"],
},
{
name: "Heicode Manager",
description:
"浏览器辅助控制台。负责账号安全、客户端下载、资源绑定、Agnet 部署、任务状态、余额和审计。",
features: ["资源绑定", "部署 Agnet", "模型余额", "审计日志"],
},
{
name: "Agnet 平台",
description:
"AI 开发团队执行层。在 AKS 上运行 Product、Frontend、Backend、Reviewer、Ops 等子 Agnet,并回传状态和事件。",
features: ["角色团队", "持续执行", "SK 调用", "状态回传"],
},
{
name: "CodeGW",
description:
"内部模型网关和计费底座。Heicode 只展示普通用户需要的模型、余额、额度、用量和调用日志。",
features: ["模型网关", "余额额度", "调用日志", "不开放后台"],
},
{
name: "密钥保管器",
description:
"OpenBao 实现的凭证托管服务。长期密钥只进入密钥保管器,子 Agnet 只拿短期、最小权限凭证。",
features: ["secret_ref", "短期凭证", "轮换撤销", "审计记录"],
},
{
name: "资源上下文",
description:
"把 Git、SK、文档、云账号和云资源变成可授权、可撤销、可审计的 Resource Binding 和 Grant。",
features: ["Git", "SK", "项目文档", "云资源"],
},
],
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
description: string;
products: Array<{ name: string; description: string; features: string[] }>;
}>;
export function Products({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section id="products" className="py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-bold tracking-widest text-primary uppercase">
{t.eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 text-muted-foreground">
{t.description}
</p>
</div>
<div className="mt-14 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{t.products.map((product, index) => {
const Icon = icons[index];
return (
<div
key={product.name}
className="group relative overflow-hidden rounded-[1.7rem] border border-border bg-card/65 p-7 shadow-sm transition-all hover:-translate-y-1 hover:border-primary/40 hover:shadow-xl"
>
<div className="absolute -right-10 -top-10 h-28 w-28 rounded-full bg-primary/5 transition-colors group-hover:bg-primary/10" />
<div className="mb-6 flex h-12 w-12 items-center justify-center rounded-2xl bg-secondary text-primary">
<Icon className="h-6 w-6" />
</div>
<h3 className="text-xl font-semibold">{product.name}</h3>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
{product.description}
</p>
<div className="mt-6 flex flex-wrap gap-2">
{product.features.map((feature) => (
<span
key={feature}
className="rounded-full bg-secondary px-3 py-1 text-xs text-muted-foreground"
>
{feature}
</span>
))}
</div>
</div>
)})}
</div>
</div>
</section>
);
}
-108
View File
@@ -1,108 +0,0 @@
import type { Lang } from "@/app/page";
const copy = {
en: {
eyebrow: "MVP to lifecycle",
title: "Prove the loop first, then expand the full lifecycle",
description:
"The real Agnet platform deployment API can arrive later. Manager payloads, manifests, audit trails, and the client approval loop must be ready first.",
phases: [
{
phase: "Current MVP",
description:
"Heicode, CodeGW, and OpenBao deploy separately. Resource bindings, Grants, manifests, and secret custody form the first closed loop. The client signs in only to Heicode.",
status: "Converging",
},
{
phase: "Next",
description:
"Expose user-side CodeGW model and usage views, close the client login and approval download path, and integrate the real Agnet deployment API.",
status: "Priority",
},
{
phase: "Long term",
description:
"Add automated maintenance, upgrade plans, and continuous cost and quality observation so development, deployment, and maintenance live in one lifecycle.",
status: "Product direction",
},
],
},
zh: {
eyebrow: "MVP to lifecycle",
title: "当前先证明闭环,再做完整生命周期",
description:
"真实 Agnet 平台部署 API 可以后置,但 Manager 侧 payload、manifest、审计和客户端闭环必须先准备好。",
phases: [
{
phase: "当前 MVP",
description: "Heicode、CodeGW、OpenBao 解耦部署;资源绑定、Grant、manifest 和密钥保管闭环;客户端只登录 Heicode。",
status: "正在收敛",
},
{
phase: "下一步",
description: "CodeGW 用户侧模型与用量展示、客户端登录审批下载闭环、Agnet 平台真实部署接口联调。",
status: "优先推进",
},
{
phase: "长期形态",
description: "自动维护、升级计划、成本和质量持续观测,把开发、部署、维护纳入同一个生命周期闭环。",
status: "产品方向",
},
],
},
} satisfies Record<Lang, {
eyebrow: string;
title: string;
description: string;
phases: Array<{ phase: string; description: string; status: string }>;
}>;
export function Roadmap({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<section className="py-20 sm:py-32">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-bold tracking-widest text-primary uppercase">
{t.eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.title}
</h2>
<p className="mt-4 text-sm text-muted-foreground">
{t.description}
</p>
</div>
<div className="mt-16">
<div className="relative">
{/* Connection Line */}
<div className="absolute left-1/2 top-0 hidden h-full w-px -translate-x-1/2 bg-gradient-to-b from-primary/50 via-border to-border lg:block" />
<div className="grid gap-8 lg:grid-cols-3">
{t.phases.map((item, index) => (
<div key={item.phase} className="relative">
{/* Circle Indicator */}
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full border-2 border-primary/30 bg-background">
<span className="text-2xl font-black text-primary">{index + 1}</span>
</div>
<div className="rounded-[1.6rem] border border-border bg-card/60 p-6 text-center shadow-sm">
<span className="inline-block rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold text-primary">
{item.status}
</span>
<h3 className="mt-4 text-xl font-bold">{item.phase}</h3>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
{item.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
}
-570
View File
@@ -1,570 +0,0 @@
import { Button } from "@/components/ui/button";
import {
Activity,
ArrowRight,
BadgeCheck,
Boxes,
BrainCircuit,
CloudCog,
Cpu,
DatabaseZap,
FileCheck2,
Fingerprint,
GitBranch,
KeyRound,
Layers3,
LockKeyhole,
Network,
ShieldCheck,
Sparkles,
SquareKanban,
Target,
Workflow,
} from "lucide-react";
import type { Lang } from "@/app/page";
const MANAGER_URL = "https://code.xinghanlab.com/";
const DOCS_URL = "https://doc.xinghanlab.com/";
const CONTACT_EMAIL = "november@taijiaicloud.com";
const copy = {
en: {
eyebrow: "Agnet Swarm",
heroTitle: "A controlled autonomous AI development team.",
heroLead:
"Heicode Swarm turns one product goal into a Swarm Run, then coordinates tasks, shared context, permissions, artifacts, usage and audit across multiple Agnets.",
primary: "Start from Heicode",
secondary: "Read docs",
proof: ["Goal-driven", "Shared resource layer", "Approval and audit", "Azure-ready"],
cockpitTitle: "Swarm command map",
cockpitMeta: "One target, many controlled executors",
cockpitGoal: "Goal",
cockpitGoalText:
"Build a SaaS with login, project workspaces, billing, audit and Azure deployment.",
cockpitNodes: [
["Product", "Acceptance criteria"],
["Architect", "Task graph"],
["Frontend", "User flows"],
["Backend", "API and data"],
["Reviewer", "Risk checks"],
["Ops", "Deployment"],
],
layerTitle: "Four layers customers can understand",
layerDesc:
"The swarm is not a group chat of bots. It is a productized operating model with clear layers and boundaries.",
layers: [
{
title: "Goal layer",
desc: "The customer describes the outcome, constraints, resources and delivery target. Heicode keeps the run anchored to the target.",
},
{
title: "Task layer",
desc: "The platform creates a dynamic task graph. Tasks can split, merge, block, hand off, retry and return to review.",
},
{
title: "Shared resource layer",
desc: "Task pool, cache, context, artifacts, approvals, model usage and resource permissions become shared infrastructure.",
},
{
title: "Governance layer",
desc: "Secrets, cloud actions, production deployment, model spending and audit are controlled instead of exposed to every Agnet.",
},
],
flowTitle: "From an idea to an auditable delivery flow",
flowDesc:
"Each Swarm Run produces visible progress, reviewable artifacts and a traceable operating record.",
flowNote:
"Heicode keeps every run tied to the original target while recording task state, resource usage and approval history.",
flow: [
["Input goal", "User describes product intent and constraints."],
["Create Swarm Run", "Heicode builds the controlled execution instance."],
["Build task graph", "Tasks are decomposed, scored and assigned."],
["Execute with Agnets", "Specialized Agnets work with minimal context."],
["Review artifacts", "Outputs return as specs, code, tests and deployment records."],
["Approve risk", "Production actions and sensitive access need explicit approval."],
],
resourceTitle: "The common resource layer behind the swarm",
resourceEyebrow: "Shared layer",
resourceDesc:
"This is the key difference from ordinary multi-agent demos: Agnets do not freely share secrets or improvise infrastructure. They work through a controlled shared layer.",
resources: [
["Task pool", "Stores waiting, running, blocked, review and completed work."],
["Context cache", "Keeps project facts, decisions, summaries and dependency state."],
["Score signals", "Pheromone-style signals help prioritize useful paths and avoid repeated dead ends."],
["Artifact pool", "Stores requirement drafts, code changes, test reports, deployment manifests and review notes."],
["Approval center", "Routes risky actions back to the client for explicit confirmation."],
["Secret vault", "Keeps long-lived credentials out of Git, prompts, Markdown and ordinary logs."],
],
azureTitle: "Mapped to an Azure-style infrastructure model",
azureDesc:
"Heicode explains swarm capability through concepts enterprise customers already recognize: orchestration, identity, storage, secrets, observability and cost control.",
azure: [
["Orchestration", "Swarm Run, dynamic task graph, queue and worker scheduling"],
["Compute", "Agnet execution runtime, sandbox, review worker and deployment worker"],
["Storage", "Artifacts, task state, context pack, cache and run history"],
["Identity", "Resource Binding, Resource Grant, least-privilege scope and TTL"],
["Secrets", "Vault-backed secret_ref with short-lived derived credentials"],
["Observability", "Event stream, audit log, usage, cost and approval records"],
],
featureTitle: "What makes Heicode Swarm different",
featureEyebrow: "Product features",
featurePanelEyebrow: "Visible to customer",
features: [
{
title: "Controlled autonomy",
desc: "Agnets can continue work, but cannot cross permission, budget or production boundaries without approval.",
},
{
title: "Customer-readable progress",
desc: "Users see what is being built, why a task is blocked, what was produced and what needs approval.",
},
{
title: "Resource-aware execution",
desc: "Git, docs, SK, model balance, cloud accounts and deployment targets are explicit resources, not hidden prompt text.",
},
{
title: "Delivery-grade artifacts",
desc: "The run produces specs, task records, code changes, tests, deployment notes and audit history.",
},
],
ctaTitle: "Use a swarm when the task is larger than one prompt",
ctaDesc:
"Heicode is designed for software work that needs requirements, roles, code, cloud resources, safety checks and long-running delivery.",
contact: "Contact",
},
zh: {
eyebrow: "Agnet 蜂群",
heroTitle: "受控自治的 AI 开发团队。",
heroLead:
"Heicode 蜂群把一个产品目标转成一次 Swarm Run,并在多个 Agnet 之间协调任务、共享上下文、权限、成果物、用量和审计。",
primary: "进入 Heicode",
secondary: "查看文档",
proof: ["目标驱动", "共享资源层", "审批与审计", "面向 Azure 部署"],
cockpitTitle: "蜂群作战地图",
cockpitMeta: "一个目标,多名受控执行者",
cockpitGoal: "目标",
cockpitGoalText:
"构建一个包含登录、项目空间、计费、审计和 Azure 部署的 SaaS 产品。",
cockpitNodes: [
["Product", "验收标准"],
["Architect", "任务图"],
["Frontend", "用户流程"],
["Backend", "API 与数据"],
["Reviewer", "风险检查"],
["Ops", "部署上线"],
],
layerTitle: "客户能理解的四个层面",
layerDesc:
"蜂群不是一组机器人聊天,而是一套产品化运行模型,每一层都有清晰边界。",
layers: [
{
title: "目标层",
desc: "客户描述交付结果、约束、资源和上线目标,Heicode 让整次执行始终围绕目标推进。",
},
{
title: "任务层",
desc: "平台生成动态任务图,任务可以拆分、合并、阻塞、交接、重试并回到复核。",
},
{
title: "共享资源层",
desc: "任务池、缓存、上下文、成果物、审批、模型用量和资源权限成为蜂群共同使用的基础设施。",
},
{
title: "治理层",
desc: "密钥、云操作、生产部署、模型消耗和审计被统一控制,而不是暴露给每个 Agnet。",
},
],
flowTitle: "从一个想法到可审计的交付流程",
flowDesc:
"每一次 Swarm Run 都产生可见进展、可验收成果物和可追踪操作记录。",
flowNote:
"Heicode 会让每次运行始终绑定原始目标,同时记录任务状态、资源使用和审批历史。",
flow: [
["输入目标", "用户描述产品意图和约束。"],
["创建 Swarm Run", "Heicode 建立受控执行实例。"],
["生成任务图", "任务被拆解、评分和分配。"],
["Agnet 执行", "专业 Agnet 使用最小上下文推进工作。"],
["成果物复核", "输出需求、代码、测试和部署记录。"],
["风险审批", "生产动作和敏感访问需要明确确认。"],
],
resourceTitle: "蜂群背后的共同资源层",
resourceEyebrow: "共享资源层",
resourceDesc:
"这是区别于普通多 Agent Demo 的关键:Agnet 不自由共享密钥,也不临时乱接基础设施,而是通过受控共享层工作。",
resources: [
["任务池", "保存待执行、执行中、阻塞、待复核和已完成的任务。"],
["上下文缓存", "保存项目事实、决策、摘要和依赖状态。"],
["评分信号", "类似信息素的信号帮助优先选择有效路径,避免重复踩坑。"],
["成果物池", "保存需求草案、代码变更、测试报告、部署清单和复核记录。"],
["审批中心", "把高风险操作回传到客户端,由用户明确确认。"],
["密钥保管库", "长期凭证不进入 Git、Prompt、Markdown 或普通日志。"],
],
azureTitle: "对标 Azure 的基础设施模型",
azureDesc:
"Heicode 用企业客户熟悉的概念解释蜂群能力:编排、身份、存储、密钥、观测和成本控制。",
azure: [
["编排", "Swarm Run、动态任务图、队列和 Worker 调度"],
["计算", "Agnet 执行运行时、沙箱、复核 Worker 和部署 Worker"],
["存储", "成果物、任务状态、Context Pack、缓存和运行历史"],
["身份", "Resource Binding、Resource Grant、最小权限范围和 TTL"],
["密钥", "基于 Vault 的 secret_ref 和短期派生凭证"],
["观测", "事件流、审计日志、用量、成本和审批记录"],
],
featureTitle: "Heicode 蜂群的产品特色",
featureEyebrow: "产品特色",
featurePanelEyebrow: "客户可见",
features: [
{
title: "受控自治",
desc: "Agnet 可以持续推进工作,但不能越过权限、预算或生产边界。",
},
{
title: "客户可读的进展",
desc: "用户可以看到正在做什么、为什么阻塞、产出了什么、哪些动作需要审批。",
},
{
title: "资源感知执行",
desc: "Git、文档、SK、模型余额、云账号和部署目标都是明确资源,不是隐藏在 Prompt 里的文字。",
},
{
title: "交付级成果物",
desc: "一次执行会沉淀需求、任务记录、代码变更、测试、部署说明和审计历史。",
},
],
ctaTitle: "当任务大于一次 Prompt,就需要蜂群",
ctaDesc:
"Heicode 面向真实软件工作:需求、角色、代码、云资源、安全检查和长周期交付必须被组织起来。",
contact: "联系咨询",
},
} satisfies Record<Lang, {
eyebrow: string;
heroTitle: string;
heroLead: string;
primary: string;
secondary: string;
proof: string[];
cockpitTitle: string;
cockpitMeta: string;
cockpitGoal: string;
cockpitGoalText: string;
cockpitNodes: Array<[string, string]>;
layerTitle: string;
layerDesc: string;
layers: Array<{ title: string; desc: string }>;
flowTitle: string;
flowDesc: string;
flowNote: string;
flow: Array<[string, string]>;
resourceTitle: string;
resourceEyebrow: string;
resourceDesc: string;
resources: Array<[string, string]>;
azureTitle: string;
azureDesc: string;
azure: Array<[string, string]>;
featureTitle: string;
featureEyebrow: string;
featurePanelEyebrow: string;
features: Array<{ title: string; desc: string }>;
ctaTitle: string;
ctaDesc: string;
contact: string;
}>;
const layerIcons = [Target, GitBranch, DatabaseZap, ShieldCheck];
const resourceIcons = [SquareKanban, BrainCircuit, Activity, FileCheck2, BadgeCheck, KeyRound];
const featureIcons = [LockKeyhole, Fingerprint, Boxes, Layers3];
export function SwarmPage({ lang }: { lang: Lang }) {
const t = copy[lang];
return (
<>
<section className="relative isolate overflow-hidden bg-[#111820] pt-28 text-white sm:pt-36">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_16%_18%,rgba(245,158,11,0.24),transparent_30%),radial-gradient(circle_at_80%_0%,rgba(20,184,166,0.22),transparent_34%),linear-gradient(135deg,#111820_0%,#182330_45%,#0c1117_100%)]" />
<div className="absolute inset-0 -z-10 opacity-[0.18] [background-image:linear-gradient(to_right,white_1px,transparent_1px),linear-gradient(to_bottom,white_1px,transparent_1px)] [background-size:44px_44px]" />
<div className="absolute left-1/2 top-20 -z-10 h-80 w-80 -translate-x-1/2 rounded-full border border-amber-200/20" />
<div className="mx-auto max-w-7xl px-4 pb-20 sm:px-6 sm:pb-28 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[0.95fr_1.05fr] lg:items-center">
<div>
<div className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/8 px-4 py-2 text-sm font-semibold text-amber-100 shadow-2xl backdrop-blur">
<Sparkles className="h-4 w-4" />
{t.eyebrow}
</div>
<h1 className="mt-7 max-w-4xl text-5xl font-black leading-[0.94] tracking-[-0.06em] sm:text-7xl lg:text-8xl">
{t.heroTitle}
</h1>
<p className="mt-7 max-w-2xl text-lg leading-8 text-white/70 sm:text-xl">
{t.heroLead}
</p>
<div className="mt-9 flex flex-col gap-4 sm:flex-row">
<Button size="lg" className="h-12 rounded-full bg-amber-300 px-7 text-[#151a20] hover:bg-amber-200" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" size="lg" className="h-12 rounded-full border-white/25 bg-white/5 px-7 text-white hover:bg-white hover:text-[#151a20]" asChild>
<a href={DOCS_URL} target="_blank" rel="noreferrer">
{t.secondary}
</a>
</Button>
</div>
<div className="mt-8 grid max-w-2xl gap-3 text-sm text-white/70 sm:grid-cols-2">
{t.proof.map((item) => (
<div key={item} className="flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-4 py-2">
<span className="h-2 w-2 rounded-full bg-teal-300" />
{item}
</div>
))}
</div>
</div>
<div className="relative">
<div className="absolute -inset-6 rounded-[2.5rem] bg-amber-300/10 blur-2xl" />
<div className="relative overflow-hidden rounded-[2rem] border border-white/15 bg-[#0d1218]/90 p-5 shadow-2xl">
<div className="flex items-center justify-between border-b border-white/10 pb-4">
<div>
<p className="text-xs uppercase tracking-[0.3em] text-white/45">{t.cockpitMeta}</p>
<h2 className="mt-2 text-xl font-black">{t.cockpitTitle}</h2>
</div>
<Network className="h-7 w-7 text-teal-200" />
</div>
<div className="mt-5 rounded-3xl border border-amber-200/20 bg-amber-200/10 p-5">
<p className="text-xs font-bold uppercase tracking-[0.22em] text-amber-100/70">{t.cockpitGoal}</p>
<p className="mt-2 text-sm leading-6 text-amber-50/80">{t.cockpitGoalText}</p>
</div>
<div className="relative mt-5 grid gap-3 sm:grid-cols-2">
<div className="absolute left-1/2 top-0 hidden h-full w-px bg-gradient-to-b from-transparent via-white/15 to-transparent sm:block" />
{t.cockpitNodes.map(([role, work], index) => (
<div key={role} className="group rounded-3xl border border-white/10 bg-white/[0.045] p-4 transition-colors hover:border-teal-200/40">
<div className="flex items-center justify-between">
<p className="font-semibold">{role}</p>
<span className="rounded-full bg-white/10 px-2 py-1 text-[10px] font-bold text-white/55">
{String(index + 1).padStart(2, "0")}
</span>
</div>
<p className="mt-2 text-xs text-white/50">{work}</p>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</section>
<section className="py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<SectionIntro title={t.layerTitle} desc={t.layerDesc} eyebrow={t.eyebrow} />
<div className="mt-14 grid gap-5 md:grid-cols-2 xl:grid-cols-4">
{t.layers.map((layer, index) => {
const Icon = layerIcons[index];
return (
<article key={layer.title} className="relative overflow-hidden rounded-[1.7rem] border border-border bg-card/70 p-6 shadow-sm">
<span className="absolute -right-5 -top-5 text-8xl font-black text-secondary/60">
{String(index + 1).padStart(2, "0")}
</span>
<div className="relative flex h-12 w-12 items-center justify-center rounded-2xl bg-foreground text-background">
<Icon className="h-6 w-6" />
</div>
<h3 className="relative mt-6 text-xl font-semibold">{layer.title}</h3>
<p className="relative mt-3 text-sm leading-7 text-muted-foreground">{layer.desc}</p>
</article>
);
})}
</div>
</div>
</section>
<section className="border-y border-border bg-card/30 py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[0.85fr_1.15fr] lg:items-start">
<div>
<p className="text-sm font-bold uppercase tracking-widest text-primary">Swarm Run</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.flowTitle}
</h2>
<p className="mt-5 text-muted-foreground">{t.flowDesc}</p>
<div className="mt-8 rounded-[1.7rem] border border-primary/20 bg-primary/10 p-6">
<div className="flex items-center gap-3">
<Workflow className="h-5 w-5 text-primary" />
<p className="font-semibold text-primary">Target - Task - Resource - Audit</p>
</div>
<p className="mt-3 text-sm leading-6 text-muted-foreground">
{t.flowNote}
</p>
</div>
</div>
<div className="relative rounded-[2rem] border border-border bg-background/80 p-4 shadow-sm">
<div className="absolute left-9 top-12 hidden h-[calc(100%-6rem)] w-px bg-border sm:block" />
<div className="grid gap-3">
{t.flow.map(([title, desc], index) => (
<div key={title} className="relative grid gap-3 rounded-[1.4rem] border border-border bg-card/70 p-5 sm:grid-cols-[auto_1fr]">
<div className="z-10 flex h-11 w-11 items-center justify-center rounded-2xl bg-foreground text-sm font-black text-background">
{index + 1}
</div>
<div>
<h3 className="font-semibold">{title}</h3>
<p className="mt-1 text-sm leading-6 text-muted-foreground">{desc}</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
<section className="py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<SectionIntro title={t.resourceTitle} desc={t.resourceDesc} eyebrow={t.resourceEyebrow} />
<div className="mt-14 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{t.resources.map(([title, desc], index) => {
const Icon = resourceIcons[index];
return (
<article key={title} className="group rounded-[1.7rem] border border-border bg-card/70 p-6 shadow-sm transition-all hover:-translate-y-1 hover:border-primary/40 hover:shadow-xl">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-secondary text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground">
<Icon className="h-6 w-6" />
</div>
<h3 className="text-lg font-semibold">{title}</h3>
<p className="mt-3 text-sm leading-7 text-muted-foreground">{desc}</p>
</article>
);
})}
</div>
</div>
</section>
<section className="relative overflow-hidden border-y border-border bg-[#111820] py-20 text-white sm:py-28">
<div className="absolute inset-0 opacity-20 [background-image:linear-gradient(135deg,white_1px,transparent_1px)] [background-size:30px_30px]" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[0.8fr_1.2fr] lg:items-start">
<div>
<div className="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-teal-300 text-[#111820]">
<CloudCog className="h-6 w-6" />
</div>
<h2 className="mt-6 text-3xl font-black tracking-tight sm:text-5xl">{t.azureTitle}</h2>
<p className="mt-5 text-white/68">{t.azureDesc}</p>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{t.azure.map(([title, desc]) => (
<article key={title} className="rounded-[1.5rem] border border-white/12 bg-white/[0.055] p-5 backdrop-blur">
<div className="mb-4 flex items-center gap-3">
<Cpu className="h-5 w-5 text-amber-200" />
<h3 className="font-semibold">{title}</h3>
</div>
<p className="text-sm leading-6 text-white/58">{desc}</p>
</article>
))}
</div>
</div>
</div>
</section>
<section className="py-20 sm:py-28">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid gap-12 lg:grid-cols-[0.95fr_1.05fr] lg:items-center">
<div className="rounded-[2rem] border border-border bg-card/60 p-5 shadow-sm">
<div className="rounded-[1.55rem] border border-border bg-background/80 p-5">
<div className="flex items-center justify-between">
<div>
<p className="text-xs font-bold uppercase tracking-[0.24em] text-primary">{t.featurePanelEyebrow}</p>
<h3 className="mt-2 text-2xl font-black">{t.featureTitle}</h3>
</div>
<Fingerprint className="h-7 w-7 text-primary" />
</div>
<div className="mt-6 space-y-3">
{t.features.map((feature, index) => {
const Icon = featureIcons[index];
return (
<div key={feature.title} className="flex gap-4 rounded-2xl border border-border bg-card/60 p-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-foreground text-background">
<Icon className="h-5 w-5" />
</div>
<div>
<h4 className="font-semibold">{feature.title}</h4>
<p className="mt-1 text-sm leading-6 text-muted-foreground">{feature.desc}</p>
</div>
</div>
);
})}
</div>
</div>
</div>
<div>
<p className="text-sm font-bold uppercase tracking-widest text-primary">{t.featureEyebrow}</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{t.featureTitle}
</h2>
<p className="mt-5 text-muted-foreground">
{t.ctaDesc}
</p>
<div className="mt-8 grid gap-4 sm:grid-cols-2">
<Metric value="01" label={lang === "zh" ? "目标驱动执行" : "Goal-driven execution"} />
<Metric value="02" label={lang === "zh" ? "任务动态回流" : "Dynamic task backflow"} />
<Metric value="03" label={lang === "zh" ? "资源安全隔离" : "Safe resource isolation"} />
<Metric value="04" label={lang === "zh" ? "成果可验收" : "Reviewable artifacts"} />
</div>
</div>
</div>
</div>
</section>
<section className="border-t border-border bg-foreground py-20 text-background sm:py-28">
<div className="mx-auto max-w-7xl px-4 text-center sm:px-6 lg:px-8">
<h2 className="mx-auto max-w-3xl text-3xl font-black tracking-tight sm:text-5xl">
{t.ctaTitle}
</h2>
<p className="mx-auto mt-5 max-w-2xl text-lg text-background/70">
{t.ctaDesc}
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Button size="lg" className="h-12 rounded-full bg-primary px-7 text-primary-foreground hover:bg-primary/90" asChild>
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
{t.primary}
<ArrowRight className="h-4 w-4" />
</a>
</Button>
<Button variant="outline" size="lg" className="h-12 rounded-full border-background/30 bg-transparent px-7 text-background hover:bg-background hover:text-foreground" asChild>
<a href={`mailto:${CONTACT_EMAIL}`}>
{t.contact}: {CONTACT_EMAIL}
</a>
</Button>
</div>
</div>
</section>
</>
);
}
function SectionIntro({
eyebrow,
title,
desc,
}: {
eyebrow: string;
title: string;
desc: string;
}) {
return (
<div className="mx-auto max-w-2xl text-center">
<p className="text-sm font-bold uppercase tracking-widest text-primary">
{eyebrow}
</p>
<h2 className="mt-4 text-3xl font-black tracking-tight sm:text-5xl">
{title}
</h2>
<p className="mt-4 leading-7 text-muted-foreground">{desc}</p>
</div>
);
}
function Metric({ value, label }: { value: string; label: string }) {
return (
<div className="rounded-[1.4rem] border border-border bg-card/70 p-5">
<p className="text-3xl font-black text-primary">{value}</p>
<p className="mt-2 text-sm font-semibold text-muted-foreground">{label}</p>
</div>
);
}
-11
View File
@@ -1,11 +0,0 @@
'use client'
import * as React from 'react'
import {
ThemeProvider as NextThemesProvider,
type ThemeProviderProps,
} from 'next-themes'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
-66
View File
@@ -1,66 +0,0 @@
'use client'
import * as React from 'react'
import * as AccordionPrimitive from '@radix-ui/react-accordion'
import { ChevronDownIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn('border-b last:border-b-0', className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
className,
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn('pt-0 pb-4', className)}>{children}</div>
</AccordionPrimitive.Content>
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
-157
View File
@@ -1,157 +0,0 @@
'use client'
import * as React from 'react'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}
function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}
function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
}
function AlertDialogContent({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className,
)}
{...props}
/>
</AlertDialogPortal>
)
}
function AlertDialogHeader({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-dialog-header"
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...props}
/>
)
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
className,
)}
{...props}
/>
)
}
function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn('text-lg font-semibold', className)}
{...props}
/>
)
}
function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return (
<AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)}
{...props}
/>
)
}
function AlertDialogCancel({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: 'outline' }), className)}
{...props}
/>
)
}
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
-66
View File
@@ -1,66 +0,0 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const alertVariants = cva(
'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',
{
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive:
'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
},
},
defaultVariants: {
variant: 'default',
},
},
)
function Alert({
className,
variant,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-title"
className={cn(
'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
className,
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-description"
className={cn(
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className,
)}
{...props}
/>
)
}
export { Alert, AlertTitle, AlertDescription }
-11
View File
@@ -1,11 +0,0 @@
'use client'
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio }
-53
View File
@@ -1,53 +0,0 @@
'use client'
import * as React from 'react'
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import { cn } from '@/lib/utils'
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
'relative flex size-8 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn('aspect-square size-full', className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
'bg-muted flex size-full items-center justify-center rounded-full',
className,
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }
-46
View File
@@ -1,46 +0,0 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border 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',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
secondary:
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
destructive:
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'span'
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }
-109
View File
@@ -1,109 +0,0 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { ChevronRight, MoreHorizontal } from 'lucide-react'
import { cn } from '@/lib/utils'
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
className,
)}
{...props}
/>
)
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
return (
<li
data-slot="breadcrumb-item"
className={cn('inline-flex items-center gap-1.5', className)}
{...props}
/>
)
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<'a'> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'a'
return (
<Comp
data-slot="breadcrumb-link"
className={cn('hover:text-foreground transition-colors', className)}
{...props}
/>
)
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn('text-foreground font-normal', className)}
{...props}
/>
)
}
function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<'li'>) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
}
function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
)
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}
-83
View File
@@ -1,83 +0,0 @@
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
const buttonGroupVariants = cva(
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
{
variants: {
orientation: {
horizontal:
'[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
vertical:
'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
},
},
defaultVariants: {
orientation: 'horizontal',
},
},
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<'div'> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'div'
return (
<Comp
className={cn(
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
'bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto',
className,
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}
-60
View File
@@ -1,60 +0,0 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'button'
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
-213
View File
@@ -1,213 +0,0 @@
'use client'
import * as React from 'react'
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from 'lucide-react'
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
import { cn } from '@/lib/utils'
import { Button, buttonVariants } from '@/components/ui/button'
function Calendar({
className,
classNames,
showOutsideDays = true,
captionLayout = 'label',
buttonVariant = 'ghost',
formatters,
components,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>['variant']
}) {
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn(
'bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
)}
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString('default', { month: 'short' }),
...formatters,
}}
classNames={{
root: cn('w-fit', defaultClassNames.root),
months: cn(
'flex gap-4 flex-col md:flex-row relative',
defaultClassNames.months,
),
month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
nav: cn(
'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
defaultClassNames.nav,
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
defaultClassNames.button_previous,
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
defaultClassNames.button_next,
),
month_caption: cn(
'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
defaultClassNames.month_caption,
),
dropdowns: cn(
'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
defaultClassNames.dropdowns,
),
dropdown_root: cn(
'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
defaultClassNames.dropdown_root,
),
dropdown: cn(
'absolute bg-popover inset-0 opacity-0',
defaultClassNames.dropdown,
),
caption_label: cn(
'select-none font-medium',
captionLayout === 'label'
? 'text-sm'
: 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
defaultClassNames.caption_label,
),
table: 'w-full border-collapse',
weekdays: cn('flex', defaultClassNames.weekdays),
weekday: cn(
'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
defaultClassNames.weekday,
),
week: cn('flex w-full mt-2', defaultClassNames.week),
week_number_header: cn(
'select-none w-(--cell-size)',
defaultClassNames.week_number_header,
),
week_number: cn(
'text-[0.8rem] select-none text-muted-foreground',
defaultClassNames.week_number,
),
day: cn(
'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
defaultClassNames.day,
),
range_start: cn(
'rounded-l-md bg-accent',
defaultClassNames.range_start,
),
range_middle: cn('rounded-none', defaultClassNames.range_middle),
range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
today: cn(
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
defaultClassNames.today,
),
outside: cn(
'text-muted-foreground aria-selected:text-muted-foreground',
defaultClassNames.outside,
),
disabled: cn(
'text-muted-foreground opacity-50',
defaultClassNames.disabled,
),
hidden: cn('invisible', defaultClassNames.hidden),
...classNames,
}}
components={{
Root: ({ className, rootRef, ...props }) => {
return (
<div
data-slot="calendar"
ref={rootRef}
className={cn(className)}
{...props}
/>
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === 'left') {
return (
<ChevronLeftIcon className={cn('size-4', className)} {...props} />
)
}
if (orientation === 'right') {
return (
<ChevronRightIcon
className={cn('size-4', className)}
{...props}
/>
)
}
return (
<ChevronDownIcon className={cn('size-4', className)} {...props} />
)
},
DayButton: CalendarDayButton,
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center">
{children}
</div>
</td>
)
},
...components,
}}
{...props}
/>
)
}
function CalendarDayButton({
className,
day,
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
ref={ref}
variant="ghost"
size="icon"
data-day={day.date.toLocaleDateString()}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&
!modifiers.range_end &&
!modifiers.range_middle
}
data-range-start={modifiers.range_start}
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70',
defaultClassNames.day,
className,
)}
{...props}
/>
)
}
export { Calendar, CalendarDayButton }
-92
View File
@@ -1,92 +0,0 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card"
className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
className,
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
'@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',
className,
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-title"
className={cn('leading-none font-semibold', className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn('px-6', className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
-241
View File
@@ -1,241 +0,0 @@
'use client'
import * as React from 'react'
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from 'embla-carousel-react'
import { ArrowLeft, ArrowRight } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: 'horizontal' | 'vertical'
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error('useCarousel must be used within a <Carousel />')
}
return context
}
function Carousel({
orientation = 'horizontal',
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<'div'> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === 'horizontal' ? 'x' : 'y',
},
plugins,
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'ArrowLeft') {
event.preventDefault()
scrollPrev()
} else if (event.key === 'ArrowRight') {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext],
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on('reInit', onSelect)
api.on('select', onSelect)
return () => {
api?.off('select', onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn('relative', className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
'flex',
orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',
className,
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
'min-w-0 shrink-0 grow-0 basis-full',
orientation === 'horizontal' ? 'pl-4' : 'pt-4',
className,
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = 'outline',
size = 'icon',
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
'absolute size-8 rounded-full',
orientation === 'horizontal'
? 'top-1/2 -left-12 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90',
className,
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = 'outline',
size = 'icon',
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
'absolute size-8 rounded-full',
orientation === 'horizontal'
? 'top-1/2 -right-12 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',
className,
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}
-351
View File
@@ -1,351 +0,0 @@
'use client'
import * as React from 'react'
import * as RechartsPrimitive from 'recharts'
import { cn } from '@/lib/utils'
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: '', dark: '.dark' } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error('useChart must be used within a <ChartContainer />')
}
return context
}
function ChartContainer({
id,
className,
children,
config,
...props
}: React.ComponentProps<'div'> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>['children']
}) {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
}
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([, config]) => config.theme || config.color,
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join('\n')}
}
`,
)
.join('\n'),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({
active,
payload,
className,
indicator = 'dot',
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<'div'> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: 'line' | 'dot' | 'dashed'
nameKey?: string
labelKey?: string
}) {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || 'value'}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === 'string'
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn('font-medium', labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn('font-medium', labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== 'dot'
return (
<div
className={cn(
'border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl',
className,
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || 'value'}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
'[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',
indicator === 'dot' && 'items-center',
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',
{
'h-2.5 w-2.5': indicator === 'dot',
'w-1': indicator === 'line',
'w-0 border-[1.5px] border-dashed bg-transparent':
indicator === 'dashed',
'my-0.5': nestLabel && indicator === 'dashed',
},
)}
style={
{
'--color-bg': indicatorColor,
'--color-border': indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
'flex flex-1 justify-between leading-none',
nestLabel ? 'items-end' : 'items-center',
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="text-foreground font-mono font-medium tabular-nums">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
const ChartLegend = RechartsPrimitive.Legend
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = 'bottom',
nameKey,
}: React.ComponentProps<'div'> &
Pick<RechartsPrimitive.LegendProps, 'payload' | 'verticalAlign'> & {
hideIcon?: boolean
nameKey?: string
}) {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
className={cn(
'flex items-center justify-center gap-4',
verticalAlign === 'top' ? 'pb-3' : 'pt-3',
className,
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || 'value'}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className="[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string,
) {
if (typeof payload !== 'object' || payload === null) {
return undefined
}
const payloadPayload =
'payload' in payload &&
typeof payload.payload === 'object' &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === 'string'
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}
-32
View File
@@ -1,32 +0,0 @@
'use client'
import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { CheckIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }
-33
View File
@@ -1,33 +0,0 @@
'use client'
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
-184
View File
@@ -1,184 +0,0 @@
'use client'
import * as React from 'react'
import { Command as CommandPrimitive } from 'cmdk'
import { SearchIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
className,
)}
{...props}
/>
)
}
function CommandDialog({
title = 'Command Palette',
description = 'Search for a command to run...',
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn('overflow-hidden p-0', className)}
showCloseButton={showCloseButton}
>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3"
>
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
/>
</div>
)
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto',
className,
)}
{...props}
/>
)
}
function CommandEmpty({
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className="py-6 text-center text-sm"
{...props}
/>
)
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
className,
)}
{...props}
/>
)
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
)
}
function CommandItem({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="command-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
-252
View File
@@ -1,252 +0,0 @@
'use client'
import * as React from 'react'
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function ContextMenu({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
}
function ContextMenuTrigger({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
)
}
function ContextMenuGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
)
}
function ContextMenuPortal({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
)
}
function ContextMenuSub({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
}
function ContextMenuRadioGroup({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
)
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
)
}
function ContextMenuSubContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className,
)}
{...props}
/>
)
}
function ContextMenuContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
)
}
function ContextMenuItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
variant?: 'default' | 'destructive'
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
)
}
function ContextMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
)
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn(
'text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
className,
)}
{...props}
/>
)
}
function ContextMenuSeparator({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="context-menu-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
/>
)
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
-143
View File
@@ -1,143 +0,0 @@
'use client'
import * as React from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { XIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-footer"
className={cn(
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
className,
)}
{...props}
/>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('text-lg leading-none font-semibold', className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}
-135
View File
@@ -1,135 +0,0 @@
'use client'
import * as React from 'react'
import { Drawer as DrawerPrimitive } from 'vaul'
import { cn } from '@/lib/utils'
function Drawer({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerOverlay({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
}
function DrawerContent({
className,
children,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return (
<DrawerPortal data-slot="drawer-portal">
<DrawerOverlay />
<DrawerPrimitive.Content
data-slot="drawer-content"
className={cn(
'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',
'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t',
'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',
'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',
className,
)}
{...props}
>
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
)
}
function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-header"
className={cn(
'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',
className,
)}
{...props}
/>
)
}
function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="drawer-footer"
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
)
}
function DrawerTitle({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn('text-foreground font-semibold', className)}
{...props}
/>
)
}
function DrawerDescription({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}
-257
View File
@@ -1,257 +0,0 @@
'use client'
import * as React from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: 'default' | 'destructive'
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
)
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
)
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
className,
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className,
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}
-104
View File
@@ -1,104 +0,0 @@
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
function Empty({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty"
className={cn(
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12',
className,
)}
{...props}
/>
)
}
function EmptyHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-header"
className={cn(
'flex max-w-sm flex-col items-center gap-2 text-center',
className,
)}
{...props}
/>
)
}
const emptyMediaVariants = cva(
'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-transparent',
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: 'default',
},
},
)
function EmptyMedia({
className,
variant = 'default',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
)
}
function EmptyTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-title"
className={cn('text-lg font-medium tracking-tight', className)}
{...props}
/>
)
}
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<div
data-slot="empty-description"
className={cn(
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
className,
)}
{...props}
/>
)
}
function EmptyContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="empty-content"
className={cn(
'flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance',
className,
)}
{...props}
/>
)
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}
-244
View File
@@ -1,244 +0,0 @@
'use client'
import { useMemo } from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { Label } from '@/components/ui/label'
import { Separator } from '@/components/ui/separator'
function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
return (
<fieldset
data-slot="field-set"
className={cn(
'flex flex-col gap-6',
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
className,
)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = 'legend',
...props
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
'mb-3 font-medium',
'data-[variant=legend]:text-base',
'data-[variant=label]:text-sm',
className,
)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-group"
className={cn(
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
className,
)}
{...props}
/>
)
}
const fieldVariants = cva(
'group/field flex w-full gap-3 data-[invalid=true]:text-destructive',
{
variants: {
orientation: {
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
horizontal: [
'flex-row items-center',
'[&>[data-slot=field-label]]:flex-auto',
'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
],
responsive: [
'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
],
},
},
defaultVariants: {
orientation: 'vertical',
},
},
)
function Field({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-content"
className={cn(
'group/field-content flex flex-1 flex-col gap-1.5 leading-snug',
className,
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
className,
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="field-label"
className={cn(
'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
className,
)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<p
data-slot="field-description"
className={cn(
'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
className,
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<'div'> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn(
'relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2',
className,
)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<'div'> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors) {
return null
}
if (errors.length === 1 && errors[0]?.message) {
return errors[0].message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{errors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>,
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn('text-destructive text-sm font-normal', className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}
-167
View File
@@ -1,167 +0,0 @@
'use client'
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import {
Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps,
type FieldPath,
type FieldValues,
} from 'react-hook-form'
import { cn } from '@/lib/utils'
import { Label } from '@/components/ui/label'
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>')
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
)
function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div
data-slot="form-item"
className={cn('grid gap-2', className)}
{...props}
/>
</FormItemContext.Provider>
)
}
function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField()
return (
<Label
data-slot="form-label"
data-error={!!error}
className={cn('data-[error=true]:text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
)
}
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
data-slot="form-control"
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
}
function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
const { formDescriptionId } = useFormField()
return (
<p
data-slot="form-description"
id={formDescriptionId}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? '') : props.children
if (!body) {
return null
}
return (
<p
data-slot="form-message"
id={formMessageId}
className={cn('text-destructive text-sm', className)}
{...props}
>
{body}
</p>
)
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
-44
View File
@@ -1,44 +0,0 @@
'use client'
import * as React from 'react'
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
import { cn } from '@/lib/utils'
function HoverCard({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
}
function HoverCardTrigger({
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
)
}
function HoverCardContent({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
return (
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
<HoverCardPrimitive.Content
data-slot="hover-card-content"
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
className,
)}
{...props}
/>
</HoverCardPrimitive.Portal>
)
}
export { HoverCard, HoverCardTrigger, HoverCardContent }
-169
View File
@@ -1,169 +0,0 @@
'use client'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="input-group"
role="group"
className={cn(
'group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none',
'h-9 has-[>textarea]:h-auto',
// Variants based on alignment.
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
// Focus state.
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
// Error state.
'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
className,
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
{
variants: {
align: {
'inline-start':
'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
'inline-end':
'order-last pr-3 has-[>button]:mr-[-0.4rem] has-[>kbd]:mr-[-0.35rem]',
'block-start':
'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5',
'block-end':
'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5',
},
},
defaultVariants: {
align: 'inline-start',
},
},
)
function InputGroupAddon({
className,
align = 'inline-start',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest('button')) {
return
}
e.currentTarget.parentElement?.querySelector('input')?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
'text-sm shadow-none flex gap-2 items-center',
{
variants: {
size: {
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5',
'icon-xs':
'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
},
},
defaultVariants: {
size: 'xs',
},
},
)
function InputGroupButton({
className,
type = 'button',
variant = 'ghost',
size = 'xs',
...props
}: Omit<React.ComponentProps<typeof Button>, 'size'> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
className={cn(
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<'input'>) {
return (
<Input
data-slot="input-group-control"
className={cn(
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
className,
)}
{...props}
/>
)
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<'textarea'>) {
return (
<Textarea
data-slot="input-group-control"
className={cn(
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
className,
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}
-77
View File
@@ -1,77 +0,0 @@
'use client'
import * as React from 'react'
import { OTPInput, OTPInputContext } from 'input-otp'
import { MinusIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function InputOTP({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn(
'flex items-center gap-2 has-disabled:opacity-50',
containerClassName,
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
)
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="input-otp-group"
className={cn('flex items-center', className)}
{...props}
/>
)
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<'div'> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
'data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]',
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
</div>
)}
</div>
)
}
function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
return (
<div data-slot="input-otp-separator" role="separator" {...props}>
<MinusIcon />
</div>
)
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
-21
View File
@@ -1,21 +0,0 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
data-slot="input"
className={cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'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',
className,
)}
{...props}
/>
)
}
export { Input }
-193
View File
@@ -1,193 +0,0 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
function ItemGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
role="list"
data-slot="item-group"
className={cn('group/item-group flex flex-col', className)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn('my-0', className)}
{...props}
/>
)
}
const itemVariants = cva(
'group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a&]:hover:bg-accent/50 [a&]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
{
variants: {
variant: {
default: 'bg-transparent',
outline: 'border-border',
muted: 'bg-muted/50',
},
size: {
default: 'p-4 gap-4 ',
sm: 'py-3 px-4 gap-2.5',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function Item({
className,
variant = 'default',
size = 'default',
asChild = false,
...props
}: React.ComponentProps<'div'> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'div'
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
const itemMediaVariants = cva(
'flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5',
{
variants: {
variant: {
default: 'bg-transparent',
icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
image:
'size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover',
},
},
defaultVariants: {
variant: 'default',
},
},
)
function ItemMedia({
className,
variant = 'default',
...props
}: React.ComponentProps<'div'> & VariantProps<typeof itemMediaVariants>) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(itemMediaVariants({ variant, className }))}
{...props}
/>
)
}
function ItemContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="item-content"
className={cn(
'flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none',
className,
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="item-title"
className={cn(
'flex w-fit items-center gap-2 text-sm leading-snug font-medium',
className,
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<'p'>) {
return (
<p
data-slot="item-description"
className={cn(
'text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance',
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
className,
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="item-actions"
className={cn('flex items-center gap-2', className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="item-header"
className={cn(
'flex basis-full items-center justify-between gap-2',
className,
)}
{...props}
/>
)
}
function ItemFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="item-footer"
className={cn(
'flex basis-full items-center justify-between gap-2',
className,
)}
{...props}
/>
)
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}
-28
View File
@@ -1,28 +0,0 @@
import { cn } from '@/lib/utils'
function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {
return (
<kbd
data-slot="kbd"
className={cn(
'bg-muted w-fit text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none',
"[&_svg:not([class*='size-'])]:size-3",
'[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10',
className,
)}
{...props}
/>
)
}
function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<kbd
data-slot="kbd-group"
className={cn('inline-flex items-center gap-1', className)}
{...props}
/>
)
}
export { Kbd, KbdGroup }
-24
View File
@@ -1,24 +0,0 @@
'use client'
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { cn } from '@/lib/utils'
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
)
}
export { Label }
-276
View File
@@ -1,276 +0,0 @@
'use client'
import * as React from 'react'
import * as MenubarPrimitive from '@radix-ui/react-menubar'
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Menubar({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
return (
<MenubarPrimitive.Root
data-slot="menubar"
className={cn(
'bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs',
className,
)}
{...props}
/>
)
}
function MenubarMenu({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
}
function MenubarGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
}
function MenubarPortal({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
}
function MenubarRadioGroup({
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return (
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
)
}
function MenubarTrigger({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
return (
<MenubarPrimitive.Trigger
data-slot="menubar-trigger"
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none',
className,
)}
{...props}
/>
)
}
function MenubarContent({
className,
align = 'start',
alignOffset = -4,
sideOffset = 8,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
return (
<MenubarPortal>
<MenubarPrimitive.Content
data-slot="menubar-content"
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md',
className,
)}
{...props}
/>
</MenubarPortal>
)
}
function MenubarItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
inset?: boolean
variant?: 'default' | 'destructive'
}) {
return (
<MenubarPrimitive.Item
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function MenubarCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
return (
<MenubarPrimitive.CheckboxItem
data-slot="menubar-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
)
}
function MenubarRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
return (
<MenubarPrimitive.RadioItem
data-slot="menubar-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
)
}
function MenubarLabel({
className,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.Label
data-slot="menubar-label"
data-inset={inset}
className={cn(
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
className,
)}
{...props}
/>
)
}
function MenubarSeparator({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
return (
<MenubarPrimitive.Separator
data-slot="menubar-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function MenubarShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="menubar-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
/>
)
}
function MenubarSub({
...props
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
}
function MenubarSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.SubTrigger
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8',
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
)
}
function MenubarSubContent({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
return (
<MenubarPrimitive.SubContent
data-slot="menubar-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className,
)}
{...props}
/>
)
}
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
}
-166
View File
@@ -1,166 +0,0 @@
import * as React from 'react'
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'
import { cva } from 'class-variance-authority'
import { ChevronDownIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center',
className,
)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
)
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
'group flex flex-1 list-none items-center justify-center gap-1',
className,
)}
{...props}
/>
)
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn('relative', className)}
{...props}
/>
)
}
const navigationMenuTriggerStyle = cva(
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
)
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), 'group', className)}
{...props}
>
{children}{' '}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
)
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto',
'group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none',
className,
)}
{...props}
/>
)
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div
className="absolute top-full left-0 isolate z-50 flex justify-center"
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
'origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]',
className,
)}
{...props}
/>
</div>
)
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
)
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
}
-127
View File
@@ -1,127 +0,0 @@
import * as React from 'react'
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button, buttonVariants } from '@/components/ui/button'
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
)
}
function PaginationContent({
className,
...props
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
)
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />
}
type PaginationLinkProps = {
isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>
function PaginationLink({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size,
}),
className,
)}
{...props}
/>
)
}
function PaginationPrevious({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
)
}
function PaginationNext({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
</PaginationLink>
)
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
)
}
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
-48
View File
@@ -1,48 +0,0 @@
'use client'
import * as React from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { cn } from '@/lib/utils'
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = 'center',
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
-31
View File
@@ -1,31 +0,0 @@
'use client'
import * as React from 'react'
import * as ProgressPrimitive from '@radix-ui/react-progress'
import { cn } from '@/lib/utils'
function Progress({
className,
value,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className,
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
)
}
export { Progress }
-45
View File
@@ -1,45 +0,0 @@
'use client'
import * as React from 'react'
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn('grid gap-3', className)}
{...props}
/>
)
}
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }
-56
View File
@@ -1,56 +0,0 @@
'use client'
import * as React from 'react'
import { GripVerticalIcon } from 'lucide-react'
import * as ResizablePrimitive from 'react-resizable-panels'
import { cn } from '@/lib/utils'
function ResizablePanelGroup({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
return (
<ResizablePrimitive.PanelGroup
data-slot="resizable-panel-group"
className={cn(
'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',
className,
)}
{...props}
/>
)
}
function ResizablePanel({
...props
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
}
function ResizableHandle({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean
}) {
return (
<ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn(
'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90',
className,
)}
{...props}
>
{withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
<GripVerticalIcon className="size-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
)
}
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
-58
View File
@@ -1,58 +0,0 @@
'use client'
import * as React from 'react'
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
import { cn } from '@/lib/utils'
function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn('relative', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}
function ScrollBar({
className,
orientation = 'vertical',
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
'flex touch-none p-px transition-colors select-none',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent',
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}
export { ScrollArea, ScrollBar }
-185
View File
@@ -1,185 +0,0 @@
'use client'
import * as React from 'react'
import * as SelectPrimitive from '@radix-ui/react-select'
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = 'default',
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: 'sm' | 'default'
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = 'popper',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}
-28
View File
@@ -1,28 +0,0 @@
'use client'
import * as React from 'react'
import * as SeparatorPrimitive from '@radix-ui/react-separator'
import { cn } from '@/lib/utils'
function Separator({
className,
orientation = 'horizontal',
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
className,
)}
{...props}
/>
)
}
export { Separator }
-139
View File
@@ -1,139 +0,0 @@
'use client'
import * as React from 'react'
import * as SheetPrimitive from '@radix-ui/react-dialog'
import { XIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className,
)}
{...props}
/>
)
}
function SheetContent({
className,
children,
side = 'right',
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: 'top' | 'right' | 'bottom' | 'left'
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
side === 'right' &&
'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
side === 'left' &&
'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
side === 'top' &&
'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
side === 'bottom' &&
'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
className,
)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sheet-header"
className={cn('flex flex-col gap-1.5 p-4', className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sheet-footer"
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props}
/>
)
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn('text-foreground font-semibold', className)}
{...props}
/>
)
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
-726
View File
@@ -1,726 +0,0 @@
'use client'
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, VariantProps } from 'class-variance-authority'
import { PanelLeftIcon } from 'lucide-react'
import { useIsMobile } from '@/hooks/use-mobile'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import { Skeleton } from '@/components/ui/skeleton'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
const SIDEBAR_COOKIE_NAME = 'sidebar_state'
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = '16rem'
const SIDEBAR_WIDTH_MOBILE = '18rem'
const SIDEBAR_WIDTH_ICON = '3rem'
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
type SidebarContextProps = {
state: 'expanded' | 'collapsed'
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider.')
}
return context
}
function SidebarProvider({
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
}: React.ComponentProps<'div'> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === 'function' ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open],
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? 'expanded' : 'collapsed'
const contextValue = React.useMemo<SidebarContextProps>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
)
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
data-slot="sidebar-wrapper"
style={
{
'--sidebar-width': SIDEBAR_WIDTH,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full',
className,
)}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
)
}
function Sidebar({
side = 'left',
variant = 'sidebar',
collapsible = 'offcanvas',
className,
children,
...props
}: React.ComponentProps<'div'> & {
side?: 'left' | 'right'
variant?: 'sidebar' | 'floating' | 'inset'
collapsible?: 'offcanvas' | 'icon' | 'none'
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === 'none') {
return (
<div
data-slot="sidebar"
className={cn(
'bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col',
className,
)}
{...props}
>
{children}
</div>
)
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-slot="sidebar"
data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
style={
{
'--sidebar-width': SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
</SheetHeader>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
)
}
return (
<div
className="group peer text-sidebar-foreground hidden md:block"
data-state={state}
data-collapsible={state === 'collapsed' ? collapsible : ''}
data-variant={variant}
data-side={side}
data-slot="sidebar"
>
{/* This is what handles the sidebar gap on desktop */}
<div
data-slot="sidebar-gap"
className={cn(
'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
'group-data-[collapsible=offcanvas]:w-0',
'group-data-[side=right]:rotate-180',
variant === 'floating' || variant === 'inset'
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
)}
/>
<div
data-slot="sidebar-container"
className={cn(
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
side === 'left'
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
// Adjust the padding for floating and inset variants.
variant === 'floating' || variant === 'inset'
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
className,
)}
{...props}
>
<div
data-sidebar="sidebar"
data-slot="sidebar-inner"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
>
{children}
</div>
</div>
</div>
)
}
function SidebarTrigger({
className,
onClick,
...props
}: React.ComponentProps<typeof Button>) {
const { toggleSidebar } = useSidebar()
return (
<Button
data-sidebar="trigger"
data-slot="sidebar-trigger"
variant="ghost"
size="icon"
className={cn('size-7', className)}
onClick={(event) => {
onClick?.(event)
toggleSidebar()
}}
{...props}
>
<PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span>
</Button>
)
}
function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
const { toggleSidebar } = useSidebar()
return (
<button
data-sidebar="rail"
data-slot="sidebar-rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
className,
)}
{...props}
/>
)
}
function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
return (
<main
data-slot="sidebar-inset"
className={cn(
'bg-background relative flex w-full flex-1 flex-col',
'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
className,
)}
{...props}
/>
)
}
function SidebarInput({
className,
...props
}: React.ComponentProps<typeof Input>) {
return (
<Input
data-slot="sidebar-input"
data-sidebar="input"
className={cn('bg-background h-8 w-full shadow-none', className)}
{...props}
/>
)
}
function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-header"
data-sidebar="header"
className={cn('flex flex-col gap-2 p-2', className)}
{...props}
/>
)
}
function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-footer"
data-sidebar="footer"
className={cn('flex flex-col gap-2 p-2', className)}
{...props}
/>
)
}
function SidebarSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="sidebar-separator"
data-sidebar="separator"
className={cn('bg-sidebar-border mx-2 w-auto', className)}
{...props}
/>
)
}
function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-content"
data-sidebar="content"
className={cn(
'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
className,
)}
{...props}
/>
)
}
function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-group"
data-sidebar="group"
className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
{...props}
/>
)
}
function SidebarGroupLabel({
className,
asChild = false,
...props
}: React.ComponentProps<'div'> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'div'
return (
<Comp
data-slot="sidebar-group-label"
data-sidebar="group-label"
className={cn(
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
className,
)}
{...props}
/>
)
}
function SidebarGroupAction({
className,
asChild = false,
...props
}: React.ComponentProps<'button'> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'button'
return (
<Comp
data-slot="sidebar-group-action"
data-sidebar="group-action"
className={cn(
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 md:after:hidden',
'group-data-[collapsible=icon]:hidden',
className,
)}
{...props}
/>
)
}
function SidebarGroupContent({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-group-content"
data-sidebar="group-content"
className={cn('w-full text-sm', className)}
{...props}
/>
)
}
function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="sidebar-menu"
data-sidebar="menu"
className={cn('flex w-full min-w-0 flex-col gap-1', className)}
{...props}
/>
)
}
function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
return (
<li
data-slot="sidebar-menu-item"
data-sidebar="menu-item"
className={cn('group/menu-item relative', className)}
{...props}
/>
)
}
const sidebarMenuButtonVariants = cva(
'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
{
variants: {
variant: {
default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
outline:
'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
},
size: {
default: 'h-8 text-sm',
sm: 'h-7 text-xs',
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function SidebarMenuButton({
asChild = false,
isActive = false,
variant = 'default',
size = 'default',
tooltip,
className,
...props
}: React.ComponentProps<'button'> & {
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>) {
const Comp = asChild ? Slot : 'button'
const { isMobile, state } = useSidebar()
const button = (
<Comp
data-slot="sidebar-menu-button"
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
)
if (!tooltip) {
return button
}
if (typeof tooltip === 'string') {
tooltip = {
children: tooltip,
}
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== 'collapsed' || isMobile}
{...tooltip}
/>
</Tooltip>
)
}
function SidebarMenuAction({
className,
asChild = false,
showOnHover = false,
...props
}: React.ComponentProps<'button'> & {
asChild?: boolean
showOnHover?: boolean
}) {
const Comp = asChild ? Slot : 'button'
return (
<Comp
data-slot="sidebar-menu-action"
data-sidebar="menu-action"
className={cn(
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
// Increases the hit area of the button on mobile.
'after:absolute after:-inset-2 md:after:hidden',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
showOnHover &&
'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
className,
)}
{...props}
/>
)
}
function SidebarMenuBadge({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="sidebar-menu-badge"
data-sidebar="menu-badge"
className={cn(
'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',
'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
'peer-data-[size=sm]/menu-button:top-1',
'peer-data-[size=default]/menu-button:top-1.5',
'peer-data-[size=lg]/menu-button:top-2.5',
'group-data-[collapsible=icon]:hidden',
className,
)}
{...props}
/>
)
}
function SidebarMenuSkeleton({
className,
showIcon = false,
...props
}: React.ComponentProps<'div'> & {
showIcon?: boolean
}) {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
}, [])
return (
<div
data-slot="sidebar-menu-skeleton"
data-sidebar="menu-skeleton"
className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text"
style={
{
'--skeleton-width': width,
} as React.CSSProperties
}
/>
</div>
)
}
function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="sidebar-menu-sub"
data-sidebar="menu-sub"
className={cn(
'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',
'group-data-[collapsible=icon]:hidden',
className,
)}
{...props}
/>
)
}
function SidebarMenuSubItem({
className,
...props
}: React.ComponentProps<'li'>) {
return (
<li
data-slot="sidebar-menu-sub-item"
data-sidebar="menu-sub-item"
className={cn('group/menu-sub-item relative', className)}
{...props}
/>
)
}
function SidebarMenuSubButton({
asChild = false,
size = 'md',
isActive = false,
className,
...props
}: React.ComponentProps<'a'> & {
asChild?: boolean
size?: 'sm' | 'md'
isActive?: boolean
}) {
const Comp = asChild ? Slot : 'a'
return (
<Comp
data-slot="sidebar-menu-sub-button"
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
size === 'sm' && 'text-xs',
size === 'md' && 'text-sm',
'group-data-[collapsible=icon]:hidden',
className,
)}
{...props}
/>
)
}
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
}
-13
View File
@@ -1,13 +0,0 @@
import { cn } from '@/lib/utils'
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="skeleton"
className={cn('bg-accent animate-pulse rounded-md', className)}
{...props}
/>
)
}
export { Skeleton }
-59
View File
@@ -1,59 +0,0 @@
'use client'
import * as React from 'react'
import * as SliderPrimitive from '@radix-ui/react-slider'
import { cn } from '@/lib/utils'
function Slider({
className,
defaultValue,
value,
min = 0,
max = 100,
...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
const _values = React.useMemo(
() =>
Array.isArray(value)
? value
: Array.isArray(defaultValue)
? defaultValue
: [min, max],
[value, defaultValue, min, max],
)
return (
<SliderPrimitive.Root
data-slot="slider"
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn(
'relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col',
className,
)}
{...props}
>
<SliderPrimitive.Track
data-slot="slider-track"
className="bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
>
<SliderPrimitive.Range
data-slot="slider-range"
className="bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
/>
</SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => (
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Root>
)
}
export { Slider }
-25
View File
@@ -1,25 +0,0 @@
'use client'
import { useTheme } from 'next-themes'
import { Toaster as Sonner, ToasterProps } from 'sonner'
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme()
return (
<Sonner
theme={theme as ToasterProps['theme']}
className="toaster group"
style={
{
'--normal-bg': 'var(--popover)',
'--normal-text': 'var(--popover-foreground)',
'--normal-border': 'var(--border)',
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }
-16
View File
@@ -1,16 +0,0 @@
import { Loader2Icon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Spinner({ className, ...props }: React.ComponentProps<'svg'>) {
return (
<Loader2Icon
role="status"
aria-label="Loading"
className={cn('size-4 animate-spin', className)}
{...props}
/>
)
}
export { Spinner }
-29
View File
@@ -1,29 +0,0 @@
'use client'
import * as React from 'react'
import * as SwitchPrimitive from '@radix-ui/react-switch'
import { cn } from '@/lib/utils'
function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className="bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
/>
</SwitchPrimitive.Root>
)
}
export { Switch }
-116
View File
@@ -1,116 +0,0 @@
'use client'
import * as React from 'react'
import { cn } from '@/lib/utils'
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
>
<table
data-slot="table"
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
)
}
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return (
<thead
data-slot="table-header"
className={cn('[&_tr]:border-b', className)}
{...props}
/>
)
}
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return (
<tbody
data-slot="table-body"
className={cn('[&_tr:last-child]:border-0', className)}
{...props}
/>
)
}
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return (
<tfoot
data-slot="table-footer"
className={cn(
'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
className,
)}
{...props}
/>
)
}
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return (
<tr
data-slot="table-row"
className={cn(
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className,
)}
{...props}
/>
)
}
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
<th
data-slot="table-head"
className={cn(
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
)
}
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
<td
data-slot="table-cell"
className={cn(
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
/>
)
}
function TableCaption({
className,
...props
}: React.ComponentProps<'caption'>) {
return (
<caption
data-slot="table-caption"
className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props}
/>
)
}
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
-66
View File
@@ -1,66 +0,0 @@
'use client'
import * as React from 'react'
import * as TabsPrimitive from '@radix-ui/react-tabs'
import { cn } from '@/lib/utils'
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn('flex-1 outline-none', className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
-18
View File
@@ -1,18 +0,0 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
return (
<textarea
data-slot="textarea"
className={cn(
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
)}
{...props}
/>
)
}
export { Textarea }
-129
View File
@@ -1,129 +0,0 @@
'use client'
import * as React from 'react'
import * as ToastPrimitives from '@radix-ui/react-toast'
import { cva, type VariantProps } from 'class-variance-authority'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
className,
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{
variants: {
variant: {
default: 'border bg-background text-foreground',
destructive:
'destructive group border-destructive bg-destructive text-destructive-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className,
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn('text-sm font-semibold', className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
-35
View File
@@ -1,35 +0,0 @@
'use client'
import { useToast } from '@/hooks/use-toast'
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from '@/components/ui/toast'
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
-73
View File
@@ -1,73 +0,0 @@
'use client'
import * as React from 'react'
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'
import { type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import { toggleVariants } from '@/components/ui/toggle'
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: 'default',
variant: 'default',
})
function ToggleGroup({
className,
variant,
size,
children,
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<ToggleGroupPrimitive.Root
data-slot="toggle-group"
data-variant={variant}
data-size={size}
className={cn(
'group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs',
className,
)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
)
}
function ToggleGroupItem({
className,
children,
variant,
size,
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
data-slot="toggle-group-item"
data-variant={context.variant || variant}
data-size={context.size || size}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
'min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l',
className,
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
}
export { ToggleGroup, ToggleGroupItem }
-47
View File
@@ -1,47 +0,0 @@
'use client'
import * as React from 'react'
import * as TogglePrimitive from '@radix-ui/react-toggle'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
{
variants: {
variant: {
default: 'bg-transparent',
outline:
'border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-9 px-2 min-w-9',
sm: 'h-8 px-1.5 min-w-8',
lg: 'h-10 px-2.5 min-w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function Toggle({
className,
variant,
size,
...props
}: React.ComponentProps<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<TogglePrimitive.Root
data-slot="toggle"
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Toggle, toggleVariants }
-61
View File
@@ -1,61 +0,0 @@
'use client'
import * as React from 'react'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
import { cn } from '@/lib/utils'
function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
-19
View File
@@ -1,19 +0,0 @@
import * as React from 'react'
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener('change', onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener('change', onChange)
}, [])
return !!isMobile
}
-191
View File
@@ -1,191 +0,0 @@
'use client'
// Inspired by react-hot-toast library
import * as React from 'react'
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',
DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST',
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType['ADD_TOAST']
toast: ToasterToast
}
| {
type: ActionType['UPDATE_TOAST']
toast: Partial<ToasterToast>
}
| {
type: ActionType['DISMISS_TOAST']
toastId?: ToasterToast['id']
}
| {
type: ActionType['REMOVE_TOAST']
toastId?: ToasterToast['id']
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: 'REMOVE_TOAST',
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case 'UPDATE_TOAST':
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
}
case 'DISMISS_TOAST': {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
}
}
case 'REMOVE_TOAST':
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, 'id'>
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: 'UPDATE_TOAST',
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
dispatch({
type: 'ADD_TOAST',
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
}
}
export { useToast, toast }
-19
View File
@@ -1,19 +0,0 @@
import * as React from 'react'
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener('change', onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener('change', onChange)
}, [])
return !!isMobile
}
-191
View File
@@ -1,191 +0,0 @@
'use client'
// Inspired by react-hot-toast library
import * as React from 'react'
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',
DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST',
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType['ADD_TOAST']
toast: ToasterToast
}
| {
type: ActionType['UPDATE_TOAST']
toast: Partial<ToasterToast>
}
| {
type: ActionType['DISMISS_TOAST']
toastId?: ToasterToast['id']
}
| {
type: ActionType['REMOVE_TOAST']
toastId?: ToasterToast['id']
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: 'REMOVE_TOAST',
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case 'UPDATE_TOAST':
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
}
case 'DISMISS_TOAST': {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
}
}
case 'REMOVE_TOAST':
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, 'id'>
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: 'UPDATE_TOAST',
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
dispatch({
type: 'ADD_TOAST',
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
}
}
export { useToast, toast }
-6
View File
@@ -1,6 +0,0 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
-12
View File
@@ -1,12 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
}
export default nextConfig
-72
View File
@@ -1,72 +0,0 @@
{
"name": "my-project",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint ."
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@vercel/analytics": "1.6.1",
"@radix-ui/react-accordion": "1.2.12",
"@radix-ui/react-alert-dialog": "1.1.15",
"@radix-ui/react-aspect-ratio": "1.1.8",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-context-menu": "2.2.16",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-hover-card": "1.1.15",
"@radix-ui/react-label": "2.1.8",
"@radix-ui/react-menubar": "1.1.16",
"@radix-ui/react-navigation-menu": "1.2.14",
"@radix-ui/react-popover": "1.1.15",
"@radix-ui/react-progress": "1.1.8",
"@radix-ui/react-radio-group": "1.3.8",
"@radix-ui/react-scroll-area": "1.2.10",
"@radix-ui/react-select": "2.2.6",
"@radix-ui/react-separator": "1.1.8",
"@radix-ui/react-slider": "1.3.6",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-toggle": "1.1.10",
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "1.2.8",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.1.1",
"date-fns": "4.1.0",
"embla-carousel-react": "8.6.0",
"input-otp": "1.4.2",
"lucide-react": "^0.564.0",
"next": "16.2.4",
"next-themes": "^0.4.6",
"react": "^19",
"react-day-picker": "9.13.2",
"react-dom": "^19",
"react-hook-form": "^7.54.1",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.0",
"sonner": "^1.7.1",
"tailwind-merge": "^3.3.1",
"vaul": "^1.1.2",
"zod": "^3.24.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.0",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8.5",
"tailwindcss": "^4.2.0",
"tw-animate-css": "1.3.3",
"typescript": "5.7.3"
}
}
-3208
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<title>Heicode Manager</title>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

-26
View File
@@ -1,26 +0,0 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
@media (prefers-color-scheme: light) {
.background { fill: black; }
.foreground { fill: white; }
}
@media (prefers-color-scheme: dark) {
.background { fill: white; }
.foreground { fill: black; }
}
</style>
<g clip-path="url(#clip0_7960_43945)">
<rect class="background" width="180" height="180" rx="37" />
<g style="transform: scale(95%); transform-origin: center">
<path class="foreground"
d="M101.141 53H136.632C151.023 53 162.689 64.6662 162.689 79.0573V112.904H148.112V79.0573C148.112 78.7105 148.098 78.3662 148.072 78.0251L112.581 112.898C112.701 112.902 112.821 112.904 112.941 112.904H148.112V126.672H112.941C98.5504 126.672 86.5638 114.891 86.5638 100.5V66.7434H101.141V100.5C101.141 101.15 101.191 101.792 101.289 102.422L137.56 66.7816C137.255 66.7563 136.945 66.7434 136.632 66.7434H101.141V53Z" />
<path class="foreground"
d="M65.2926 124.136L14 66.7372H34.6355L64.7495 100.436V66.7372H80.1365V118.47C80.1365 126.278 70.4953 129.958 65.2926 124.136Z" />
</g>
</g>
<defs>
<clipPath id="clip0_7960_43945">
<rect width="180" height="180" fill="white" />
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

-11
View File
@@ -1,11 +0,0 @@
{
"version": "0.2.4",
"notes": "0.2.4 - fix updater progress jumping and click-twice issues",
"pub_date": "2026-05-19T03:58:21.792Z",
"platforms": {
"windows-x86_64": {
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVSa3lBOFkxcW5LdDZCVWFwbWxBVS8yeHFHSFp6TFBid1JMSzNENnhMalpJSk50SVJ1R1UxdzQydzN4akw2VmVWb3ZiVlVXOTZvME1ZM3Z2WjJmaW5GajBTaFd2cXQ4c1FrPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc5MTYyOTY0CWZpbGU6SGVpQ29kZV8wLjIuNF94NjRfZW4tVVMubXNpCnhkTUJYUkQycWZ4UmpOVEJycGJ0RVpVTWRlcXBoeHI0NllUQmtDWURPNmRnZ2FRWFhjNzk0Q1p4LzR5MDVvR2ptTjBhSGFCLzJPb0l0QU5TZU5FdkRnPT0K",
"url": "https://heicodeblob.blob.core.windows.net/msi/desktop/0.2.4/HeiCode_0.2.4_x64_en-US.msi"
}
}
}
-125
View File
@@ -1,125 +0,0 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--font-sans: 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
-41
View File
@@ -1,41 +0,0 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}