commit 8c60cf0ecab403178002fe1ee029d9afac7b00d4 Author: ahao Date: Fri Jun 27 08:05:29 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f650315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..d5077f8 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,202 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } +} + +/* Enhanced White Liquid Glass Effects */ +.glass-card-white { + background: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.3); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.4); +} + +.glass-card-enhanced { + background: rgba(255, 255, 255, 0.5); + backdrop-filter: blur(25px); + border: 1px solid rgba(255, 255, 255, 0.4); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1); +} + +.glass-button-white { + backdrop-filter: blur(20px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.4); +} + +/* Glass Pattern Background */ +.glass-pattern { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: radial-gradient(circle at 25% 25%, rgba(59, 130, 246, 0.1) 0%, transparent 50%), + radial-gradient(circle at 75% 75%, rgba(147, 51, 234, 0.1) 0%, transparent 50%), + radial-gradient(circle at 50% 50%, rgba(236, 72, 153, 0.05) 0%, transparent 50%); + animation: glass-float 20s ease-in-out infinite; +} + +@keyframes glass-float { + 0%, + 100% { + transform: translate(0, 0) scale(1); + filter: hue-rotate(0deg); + } + 33% { + transform: translate(20px, -10px) scale(1.05); + filter: hue-rotate(120deg); + } + 66% { + transform: translate(-15px, 15px) scale(0.95); + filter: hue-rotate(240deg); + } +} + +/* Soft Pattern Background */ +.soft-pattern { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: linear-gradient(rgba(59, 130, 246, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(147, 51, 234, 0.05) 1px, transparent 1px); + background-size: 60px 60px; + animation: soft-flow 30s ease-in-out infinite; +} + +@keyframes soft-flow { + 0%, + 100% { + transform: translate(0, 0) scale(1); + filter: hue-rotate(0deg); + } + 25% { + transform: translate(15px, 8px) scale(1.02); + filter: hue-rotate(45deg); + } + 50% { + transform: translate(30px, 15px) scale(1.05); + filter: hue-rotate(90deg); + } + 75% { + transform: translate(15px, 23px) scale(1.02); + filter: hue-rotate(135deg); + } +} + +/* Enhanced Scrolling Animation */ +@keyframes scroll-smooth { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-50%); + } +} + +.animate-scroll-smooth { + animation: scroll-smooth 40s linear infinite; +} + +/* Line Clamp Utility */ +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Custom Scrollbar with Enhanced Glass */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, rgba(59, 130, 246, 0.8), rgba(147, 51, 234, 0.8)); + border-radius: 5px; + box-shadow: 0 0 10px rgba(59, 130, 246, 0.3); + backdrop-filter: blur(10px); +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(180deg, rgba(147, 51, 234, 0.9), rgba(236, 72, 153, 0.9)); + box-shadow: 0 0 15px rgba(147, 51, 234, 0.5); +} + +/* Responsive Typography */ +@media (max-width: 768px) { + .soft-pattern { + background-size: 40px 40px; + animation-duration: 25s; + } + + .glass-pattern { + animation-duration: 15s; + } +} + +/* Performance Optimizations */ +.animate-pulse, +.animate-scroll-smooth, +.soft-pattern, +.glass-pattern { + will-change: transform; +} + +/* Focus States for Accessibility */ +button:focus-visible, +input:focus-visible, +textarea:focus-visible { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} + +/* Enhanced Hover Effects for Interactive Elements */ +.glass-card-white:hover { + transform: translateY(-5px); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.5); +} + +.glass-card-enhanced:hover { + transform: translateY(-3px); + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 0 0 1px + rgba(255, 255, 255, 0.2); +} + +.glass-button-white:hover { + transform: translateY(-2px); + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.5); +} + +/* Floating Glass Orb Animation */ +@keyframes float-orb { + 0%, + 100% { + transform: translateY(0px) scale(1); + opacity: 0.6; + } + 50% { + transform: translateY(-10px) scale(1.1); + opacity: 0.8; + } +} + +.animate-float-orb { + animation: float-orb 4s ease-in-out infinite; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..17b2ce8 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.dev', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..595d2ca --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,21 @@ +import Header from "@/components/header" +import HeroSection from "@/components/hero-section" +import ServicesSection from "@/components/services-section" +import TechStackSection from "@/components/tech_stack-section" +import ContactSection from "@/components/contact-section" +import Footer from "@/components/footer" + +export default function HomePage() { + return ( +
+
+
+ + + + +
+
+
+ ) +} diff --git a/app/services/[slug]/ServiceDetailPageClient.tsx b/app/services/[slug]/ServiceDetailPageClient.tsx new file mode 100644 index 0000000..696afd9 --- /dev/null +++ b/app/services/[slug]/ServiceDetailPageClient.tsx @@ -0,0 +1,155 @@ +"use client" + +import { getServiceById } from "@/config/services" +import Link from "next/link" +import { ArrowLeft, Mail } from "lucide-react" +import { Button } from "@/components/ui/button" +import { notFound } from "next/navigation" +import type { FC } from "react" + +interface ServiceDetailPageProps { + params: { + slug: string + } +} + +export const ServiceDetailPageClient: FC = ({ params }) => { + const service = getServiceById(params.slug) + + if (!service) { + notFound() + } + + const IconComponent = service.icon + + return ( +
+ {/* Enhanced Glass Header */} +
+
+
+ + + + + +
+ XZ +
+ + 鑫泽焓界 + + +
+
+
+ + {/* Service Detail Content */} +
+ {/* Static Light Background */} +
+
+
+ +
+ {/* Service Header */} +
+
+ {/* Service Icon */} +
+
+
+ +
+
+ +

+ {service.name} +

+ +
+ + {service.category} + +
+ +

{service.description}

+
+
+ + {/* Service Details */} +
+ {/* Features */} +
+

+ 服务内容 +

+
    +
  • +
    + 技术实现 +
  • +
  • +
    + 项目交付 +
  • +
  • +
    + 技术支持 +
  • +
  • +
    + 文档提供 +
  • +
+
+ + {/* Contact */} +
+

+ 联系咨询 +

+
+
+
+ +
+
+

邮箱咨询

+

+ zichen.hao@entalnet.com +

+
+
+ + +
+
+
+
+
+
+ ) +} diff --git a/app/services/[slug]/page.tsx b/app/services/[slug]/page.tsx new file mode 100644 index 0000000..2bbd54b --- /dev/null +++ b/app/services/[slug]/page.tsx @@ -0,0 +1,18 @@ +import { servicesConfig } from "@/config/services" +import { ServiceDetailPageClient } from "./ServiceDetailPageClient" + +interface ServiceDetailPageProps { + params: { + slug: string + } +} + +export async function generateStaticParams() { + return servicesConfig.map((service) => ({ + slug: service.id, + })) +} + +export default function ServiceDetailPage({ params }: ServiceDetailPageProps) { + return +} diff --git a/app/services/page.tsx b/app/services/page.tsx new file mode 100644 index 0000000..6c63dd8 --- /dev/null +++ b/app/services/page.tsx @@ -0,0 +1,89 @@ +import { getServicesByCategory } from "@/config/services" +import ServiceCard from "@/components/service-card" +import Link from "next/link" +import { ArrowLeft } from "lucide-react" +import { Button } from "@/components/ui/button" + +export default function ServicesPage() { + const servicesByCategory = getServicesByCategory() + + return ( +
+ {/* Enhanced Glass Header */} +
+
+
+ + + + +
+
+ XZ +
+ + 鑫泽焓界 + +
+
+
+
+ + {/* Hero Section with Enhanced Glass */} +
+ {/* Enhanced Background */} +
+
+
+ + {/* Glass Pattern Overlay */} +
+
+
+ +
+
+ {/* Enhanced Glass Title Card */} +
+

+ 服务列表 +

+

+ 提供的技术服务和解决方案 +

+
+
+ + {/* Services by Category */} +
+ {Object.entries(servicesByCategory).map(([category, services]) => ( +
+ {/* Category Header with Glass Effect */} +
+
+

+ {category} +

+
+
+ + {/* Services Grid */} +
+ {services.map((service) => ( + + ))} +
+
+ ))} +
+
+
+
+ ) +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..d9ef0ae --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/components/contact-section.tsx b/components/contact-section.tsx new file mode 100644 index 0000000..0182e53 --- /dev/null +++ b/components/contact-section.tsx @@ -0,0 +1,72 @@ +"use client" + +import { Mail, MapPin } from "lucide-react" +import { Card, CardContent } from "@/components/ui/card" + +export default function ContactSection() { + return ( +
+
+
+

+ 联系方式 +

+

项目咨询和技术支持

+
+ +
+ {/* Contact Information */} + + +
+
+ +
+
+

邮箱联系

+

zichen.hao@entalnet.com

+
+
+
+
+ + + +
+
+ +
+
+

服务地区

+

中国大陆

+
+
+
+
+
+ +
+
+

+ 服务特点 +

+
    +
  • +
    + 技术支持 +
  • +
  • +
    + 项目交付 +
  • +
  • +
    + 持续维护 +
  • +
+
+
+
+
+ ) +} diff --git a/components/footer.tsx b/components/footer.tsx new file mode 100644 index 0000000..b3be86c --- /dev/null +++ b/components/footer.tsx @@ -0,0 +1,51 @@ +export default function Footer() { + return ( +
+
+
+ {/* Company Info */} +
+
+
+ XZ +
+ + 鑫泽焓界 + +
+

+ 专注于前沿技术的创新型企业,致力于为客户提供高质量的软件开发和网络解决方案。 +

+
+ + {/* Contact */} +
+

+ 联系方式 +

+
+

+ 邮箱: zichen.hao@entalnet.com +

+

地址: 中国大陆

+

服务时间: 7x24小时

+
+
+
+ + {/* Bottom Bar */} +
+
© 2024 鑫泽焓界 版权所有
+
网站备案号:[待填入具体备案号]
+
+ + {/* Legal Notice */} +
+

+ 本网站所有内容均受到知识产权法保护,未经授权不得复制或使用。 +

+
+
+
+ ) +} diff --git a/components/header.tsx b/components/header.tsx new file mode 100644 index 0000000..5abd079 --- /dev/null +++ b/components/header.tsx @@ -0,0 +1,42 @@ +"use client" + +import { useState, useEffect } from "react" + +export default function Header() { + const [isScrolled, setIsScrolled] = useState(false) + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50) + } + window.addEventListener("scroll", handleScroll) + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + return ( +
+
+
+ {/* Centered Logo */} +
+
+
+ XZ +
+
+
+
+ + 鑫泽焓界 + +
+
+
+
+
+ ) +} diff --git a/components/hero-section.tsx b/components/hero-section.tsx new file mode 100644 index 0000000..5a53a3d --- /dev/null +++ b/components/hero-section.tsx @@ -0,0 +1,83 @@ +"use client" + +import { useEffect, useState } from "react" +import { Zap, Sparkles } from "lucide-react" +import { Button } from "@/components/ui/button" + +export default function HeroSection() { + const [displayText, setDisplayText] = useState("") + const fullText = "构建数字未来,连接无限可能" + + useEffect(() => { + let index = 0 + const timer = setInterval(() => { + if (index < fullText.length) { + setDisplayText(fullText.slice(0, index + 1)) + index++ + } else { + clearInterval(timer) + } + }, 150) + + return () => clearInterval(timer) + }, []) + + return ( +
+ {/* Soft Background Gradient */} +
+
+
+ + {/* Subtle Pattern */} +
+
+
+ + {/* Content */} +
+
+ {/* Soft glowing orb behind text */} +
+ +

+ + {displayText} + | + +

+ +
+

+ Web开发 | + 服务器架构 | + 网络解决方案 +

+
+
+ +
+ +
+ + {/* Soft Floating Elements */} +
+
+
+
+
+
+ ) +} diff --git a/components/service-card.tsx b/components/service-card.tsx new file mode 100644 index 0000000..f0ee1c5 --- /dev/null +++ b/components/service-card.tsx @@ -0,0 +1,68 @@ +"use client" + +import Link from "next/link" +import type { ServiceConfig } from "@/config/services" +import { ArrowRight } from "lucide-react" + +interface ServiceCardProps { + service: ServiceConfig +} + +export default function ServiceCard({ service }: ServiceCardProps) { + const IconComponent = service.icon + + return ( + +
+ {/* Enhanced Background Gradient */} +
+ + {/* Floating Glass Orbs */} +
+
+ +
+ {/* Enhanced Icon with Multiple Glass Layers */} +
+
+ {/* Glass overlay on icon */} +
+ +
+ + {/* Icon Glow Effect */} +
+
+ + {/* Enhanced Title */} +

+ {service.name} +

+ + {/* Description */} +

{service.description}

+ + {/* Enhanced CTA with Glass Effect */} +
+
+ + {service.category} + +
+ +
+ 了解更多 + +
+
+
+
+ + ) +} diff --git a/components/services-section.tsx b/components/services-section.tsx new file mode 100644 index 0000000..d63fa10 --- /dev/null +++ b/components/services-section.tsx @@ -0,0 +1,101 @@ +"use client" + +import Link from "next/link" +import { ArrowRight } from "lucide-react" +import { Button } from "@/components/ui/button" + +const services = [ + { + title: "Web程序开发", + description: "前端和后端开发服务,包括响应式网站、Web应用程序和API接口开发。", + gradient: "from-blue-400 to-cyan-500", + borderColor: "border-blue-200/50", + glowColor: "rgba(59,130,246,0.2)", + }, + { + title: "服务器后端计算", + description: "服务器架构设计、数据库管理、后端服务开发和系统优化。", + gradient: "from-purple-400 to-indigo-500", + borderColor: "border-purple-200/50", + glowColor: "rgba(147,51,234,0.2)", + }, + { + title: "网络架构搭建", + description: "网络基础设施规划、网络安全配置和系统集成服务。", + gradient: "from-pink-400 to-rose-500", + borderColor: "border-pink-200/50", + glowColor: "rgba(236,72,153,0.2)", + }, +] + +export default function ServicesSection() { + return ( +
+
+
+

+ 服务范围 +

+

+ 提供Web开发、服务器管理和网络架构相关的技术服务 +

+
+ +
+ {services.map((service, index) => ( +
+
+ {/* Enhanced Background gradient overlay */} +
+ + {/* Floating Glass Orbs */} +
+
+ +
+
+ {/* Enhanced 纯色背景图标,无图标内容,增强玻璃效果 */} +
+ {/* Glass overlay */} +
+
+

+ {service.title} +

+
+

{service.description}

+
+
+
+ ))} +
+ + {/* Enhanced CTA to Services Page */} +
+
+

+ 更多服务 +

+

查看完整的服务列表

+ + + +
+
+
+
+ ) +} diff --git a/components/tech-stack-section.tsx b/components/tech-stack-section.tsx new file mode 100644 index 0000000..38045b3 --- /dev/null +++ b/components/tech-stack-section.tsx @@ -0,0 +1,95 @@ +"use client" + +import { useEffect, useState } from "react" + +const technologies = [ + "React", + "Next.js", + "TypeScript", + "Node.js", + "Python", + "Docker", + "Kubernetes", + "AWS", + "MongoDB", + "PostgreSQL", + "Redis", + "GraphQL", + "Tailwind CSS", + "Three.js", + "WebGL", + "Microservices", + "DevOps", + "CI/CD", +] + +export default function TechStackSection() { + const [currentIndex, setCurrentIndex] = useState(0) + + useEffect(() => { + const timer = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % technologies.length) + }, 2000) + return () => clearInterval(timer) + }, []) + + return ( +
+
+
+

+ 技术栈 +

+

使用的主要技术和工具

+
+ + {/* Enhanced Scrolling Tech Tags */} +
+
+
+ {[...technologies, ...technologies].map((tech, index) => ( +
+ {tech} +
+ ))} +
+
+
+ + {/* Enhanced Value Propositions */} +
+ {[ + { title: "技术栈", desc: "现代化技术选型", gradient: "from-blue-400 to-cyan-500" }, + { title: "代码质量", desc: "规范化开发流程", gradient: "from-purple-400 to-indigo-500" }, + { title: "项目交付", desc: "按时完成项目", gradient: "from-pink-400 to-rose-500" }, + { title: "技术支持", desc: "持续维护服务", gradient: "from-indigo-400 to-blue-500" }, + ].map((item, index) => ( +
+
+
+
+

+ {item.title} +

+

{item.desc}

+
+ ))} +
+
+
+ ) +} diff --git a/components/tech_stack-section.tsx b/components/tech_stack-section.tsx new file mode 100644 index 0000000..f167404 --- /dev/null +++ b/components/tech_stack-section.tsx @@ -0,0 +1,95 @@ +"use client" + +import { useEffect, useState } from "react" + +const technologies = [ + "React", + "Next.js", + "TypeScript", + "Node.js", + "Python", + "Docker", + "Kubernetes", + "AWS", + "MongoDB", + "PostgreSQL", + "Redis", + "GraphQL", + "Tailwind CSS", + "Three.js", + "WebGL", + "Microservices", + "DevOps", + "CI/CD", +] + +export default function TechStackSection() { + const [currentIndex, setCurrentIndex] = useState(0) + + useEffect(() => { + const timer = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % technologies.length) + }, 2000) + return () => clearInterval(timer) + }, []) + + return ( +
+
+
+

+ 技术栈 +

+

始终跟踪最新技术趋势,应用前沿解决方案

+
+ + {/* Enhanced Scrolling Tech Tags */} +
+
+
+ {[...technologies, ...technologies].map((tech, index) => ( +
+ {tech} +
+ ))} +
+
+
+ + {/* Enhanced Value Propositions */} +
+ {[ + { title: "技术领先", desc: "始终跟踪最新技术趋势", gradient: "from-blue-400 to-cyan-500" }, + { title: "质量保障", desc: "严格的代码规范和测试", gradient: "from-purple-400 to-indigo-500" }, + { title: "快速交付", desc: "敏捷开发模式", gradient: "from-pink-400 to-rose-500" }, + { title: "持续支持", desc: "长期技术支持和维护", gradient: "from-indigo-400 to-blue-500" }, + ].map((item, index) => ( +
+
+
+
+

+ {item.title} +

+

{item.desc}

+
+ ))} +
+
+
+ ) +} diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..55c2f6e --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as React from 'react' +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx new file mode 100644 index 0000000..24c788c --- /dev/null +++ b/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..25e7b47 --- /dev/null +++ b/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"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" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..41fa7e0 --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,59 @@ +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 p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..d6a5226 --- /dev/null +++ b/components/ui/aspect-ratio.tsx @@ -0,0 +1,7 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..60e6c96 --- /dev/null +++ b/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>