├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── app
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx
├── components.json
├── components
├── icons
│ ├── discord-icon.tsx
│ ├── github-icon.tsx
│ ├── linkedin-icon.tsx
│ └── x-icon.tsx
├── layout
│ ├── navbar.tsx
│ ├── sections
│ │ ├── benefits.tsx
│ │ ├── community.tsx
│ │ ├── contact.tsx
│ │ ├── faq.tsx
│ │ ├── features.tsx
│ │ ├── footer.tsx
│ │ ├── hero.tsx
│ │ ├── pricing.tsx
│ │ ├── services.tsx
│ │ ├── sponsors.tsx
│ │ ├── team.tsx
│ │ └── testimonial.tsx
│ ├── theme-provider.tsx
│ └── toogle-theme.tsx
└── ui
│ ├── accordion.tsx
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── carousel.tsx
│ ├── collapsible.tsx
│ ├── form.tsx
│ ├── icon.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── navigation-menu.tsx
│ ├── scroll-area.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── sheet.tsx
│ └── textarea.tsx
├── lib
└── utils.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── demo-img.jpg
├── hero-image-dark.jpeg
└── hero-image-light.jpeg
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Bruno Felipy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shadcn Landing Page Template
2 |
3 | ## Shadcn + Next.js + TypeScript + Tailwind .
4 |
5 | ### This is a project conversion Shadcn-Vue to NextJS
6 |
7 | 
8 |
9 | ## Sections
10 |
11 | - [x] Navbar
12 | - [x] Sidebar(mobile)
13 | - [x] Hero
14 | - [x] Sponsors
15 | - [x] Benefits
16 | - [x] Features
17 | - [x] Testimonials
18 | - [x] Team
19 | - [x] Community
20 | - [x] Contact
21 | - [x] Pricing
22 | - [x] Frequently Asked Questions(FAQ)
23 | - [x] Services
24 | - [x] Footer
25 |
26 | ## Features
27 |
28 | - [x] Fully Responsive Design
29 | - [x] User Friendly Navigation
30 | - [x] Dark Mode
31 |
32 | ## How to install
33 |
34 | 1. Clone this repositoy:
35 |
36 | ```bash
37 | git clone https://github.com/nobruf/shadcn-landing-page.git
38 | ```
39 |
40 | 2. Go into project
41 |
42 | ```bash
43 | cd shadcn-landing-page
44 | ```
45 |
46 | 3. Install dependencies
47 |
48 | ```bash
49 | npm install
50 | ```
51 |
52 | 4. Run project
53 |
54 | ```bash
55 | npm run dev
56 | ```
57 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobruf/shadcn-landing-page/ec8e18e8ed56ed6636023ced09948515c19258cc/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html {
6 | scroll-behavior: smooth;
7 | }
8 |
9 | /* *=========== Default theme =========== */
10 | /* @layer base {
11 | :root {
12 | --background: 0 0% 100%;
13 | --foreground: 240 10% 3.9%;
14 |
15 | --muted: 240 4.8% 95.9%;
16 | --muted-foreground: 240 3.8% 46.1%;
17 |
18 | --popover: 0 0% 100%;
19 | --popover-foreground: 240 10% 3.9%;
20 |
21 | --card: 0 0% 100%;
22 | --card-foreground: 240 10% 3.9%;
23 |
24 | --border: 240 5.9% 90%;
25 | --input: 240 5.9% 90%;
26 |
27 | --primary: 240 5.9% 10%;
28 | --primary-foreground: 0 0% 98%;
29 |
30 | --secondary: 240 4.8% 95.9%;
31 | --secondary-foreground: 240 5.9% 10%;
32 |
33 | --accent: 240 4.8% 95.9%;
34 | --accent-foreground: 240 5.9% 10%;
35 |
36 | --destructive: 0 84.2% 60.2%;
37 | --destructive-foreground: 0 0% 98%;
38 |
39 | --ring: 240 10% 3.9%;
40 |
41 | --radius: 0.5rem;
42 | }
43 |
44 | .dark {
45 | --background: 240 10% 3.9%;
46 | --foreground: 0 0% 98%;
47 |
48 | --muted: 240 3.7% 15.9%;
49 | --muted-foreground: 240 5% 64.9%;
50 |
51 | --popover: 240 10% 3.9%;
52 | --popover-foreground: 0 0% 98%;
53 |
54 | --card: 240 10% 3.9%;
55 | --card-foreground: 0 0% 98%;
56 |
57 | --border: 240 3.7% 15.9%;
58 | --input: 240 3.7% 15.9%;
59 |
60 | --primary: 0 0% 98%;
61 | --primary-foreground: 240 5.9% 10%;
62 |
63 | --secondary: 240 3.7% 15.9%;
64 | --secondary-foreground: 0 0% 98%;
65 |
66 | --accent: 240 3.7% 15.9%;
67 | --accent-foreground: 0 0% 98%;
68 |
69 | --destructive: 0 62.8% 30.6%;
70 | --destructive-foreground: 0 0% 98%;
71 |
72 | --ring: 240 4.9% 83.9%;
73 | }
74 | } */
75 |
76 | /* *=========== Orange theme =========== */
77 | @layer base {
78 | :root {
79 | --background: 0 0% 100%;
80 | --foreground: 20 14.3% 4.1%;
81 |
82 | --card: 0 0% 100%;
83 | --card-foreground: 20 14.3% 4.1%;
84 |
85 | --popover: 0 0% 100%;
86 | --popover-foreground: 20 14.3% 4.1%;
87 |
88 | --primary: 24.6 95% 53.1%;
89 | --primary-foreground: 60 9.1% 97.8%;
90 |
91 | --secondary: 60 4.8% 95.9%;
92 | --secondary-foreground: 24 9.8% 10%;
93 |
94 | --muted: 60 4.8% 95.9%;
95 | --muted-foreground: 25 5.3% 44.7%;
96 |
97 | --accent: 60 4.8% 95.9%;
98 | --accent-foreground: 24 9.8% 10%;
99 |
100 | --destructive: 0 84.2% 60.2%;
101 | --destructive-foreground: 60 9.1% 97.8%;
102 |
103 | --border: 20 5.9% 90%;
104 | --input: 20 5.9% 90%;
105 | --ring: 24.6 95% 53.1%;
106 | --radius: 0.5rem;
107 | }
108 |
109 | .dark {
110 | --background: 20 14.3% 4.1%;
111 | --foreground: 60 9.1% 97.8%;
112 |
113 | --card: 24 9.8% 8%;
114 | --card-foreground: 0 0% 95%;
115 |
116 | --popover: 20 14.3% 4.1%;
117 | --popover-foreground: 60 9.1% 97.8%;
118 |
119 | --primary: 20.5 90.2% 48.2%;
120 | --primary-foreground: 60 9.1% 97.8%;
121 |
122 | --secondary: 12 6.5% 15.1%;
123 | --secondary-foreground: 60 9.1% 97.8%;
124 |
125 | --muted: 12 6.5% 15.1%;
126 | --muted-foreground: 24 5.4% 63.9%;
127 |
128 | --accent: 12 6.5% 15.1%;
129 | --accent-foreground: 60 9.1% 97.8%;
130 |
131 | --destructive: 0 72.2% 50.6%;
132 | --destructive-foreground: 60 9.1% 97.8%;
133 |
134 | --border: 12 6.5% 15.1%;
135 | --input: 12 6.5% 15.1%;
136 | --ring: 20.5 90.2% 48.2%;
137 | }
138 | }
139 |
140 | .shadow-light {
141 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.085);
142 | }
143 |
144 | .shadow-dark {
145 | box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.141);
146 | }
147 |
148 | @layer base {
149 | * {
150 | @apply border-border;
151 | }
152 | body {
153 | @apply bg-background text-foreground;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { cn } from "@/lib/utils";
5 | import { Navbar } from "@/components/layout/navbar";
6 | import { ThemeProvider } from "@/components/layout/theme-provider";
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export const metadata: Metadata = {
10 | title: "Shadcn - Landing template",
11 | description: "Landing template from Shadcn",
12 | };
13 |
14 | export default function RootLayout({
15 | children,
16 | }: Readonly<{
17 | children: React.ReactNode;
18 | }>) {
19 | return (
20 |
21 |
22 |
28 |
29 |
30 | {children}
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { BenefitsSection } from "@/components/layout/sections/benefits";
2 | import { CommunitySection } from "@/components/layout/sections/community";
3 | import { ContactSection } from "@/components/layout/sections/contact";
4 | import { FAQSection } from "@/components/layout/sections/faq";
5 | import { FeaturesSection } from "@/components/layout/sections/features";
6 | import { FooterSection } from "@/components/layout/sections/footer";
7 | import { HeroSection } from "@/components/layout/sections/hero";
8 | import { PricingSection } from "@/components/layout/sections/pricing";
9 | import { ServicesSection } from "@/components/layout/sections/services";
10 | import { SponsorsSection } from "@/components/layout/sections/sponsors";
11 | import { TeamSection } from "@/components/layout/sections/team";
12 | import { TestimonialSection } from "@/components/layout/sections/testimonial";
13 |
14 | export const metadata = {
15 | title: "Shadcn - Landing template",
16 | description: "Free Shadcn landing page for developers",
17 | openGraph: {
18 | type: "website",
19 | url: "https://github.com/nobruf/shadcn-landing-page.git",
20 | title: "Shadcn - Landing template",
21 | description: "Free Shadcn landing page for developers",
22 | images: [
23 | {
24 | url: "https://res.cloudinary.com/dbzv9xfjp/image/upload/v1723499276/og-images/shadcn-vue.jpg",
25 | width: 1200,
26 | height: 630,
27 | alt: "Shadcn - Landing template",
28 | },
29 | ],
30 | },
31 | twitter: {
32 | card: "summary_large_image",
33 | site: "https://github.com/nobruf/shadcn-landing-page.git",
34 | title: "Shadcn - Landing template",
35 | description: "Free Shadcn landing page for developers",
36 | images: [
37 | "https://res.cloudinary.com/dbzv9xfjp/image/upload/v1723499276/og-images/shadcn-vue.jpg",
38 | ],
39 | },
40 | };
41 |
42 | export default function Home() {
43 | return (
44 | <>
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | >
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/components/icons/discord-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | function DiscordIcon(props: React.SVGProps | undefined) {
4 | return (
5 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default DiscordIcon;
21 |
--------------------------------------------------------------------------------
/components/icons/github-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | function GithubIcon(props: React.SVGProps | undefined) {
4 | return (
5 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default GithubIcon;
19 |
--------------------------------------------------------------------------------
/components/icons/linkedin-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | function LinkedInIcon(props: React.SVGProps | undefined) {
4 | return (
5 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default LinkedInIcon;
19 |
--------------------------------------------------------------------------------
/components/icons/x-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | function XIcon(props: React.SVGProps | undefined) {
4 | return (
5 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default XIcon;
19 |
--------------------------------------------------------------------------------
/components/layout/navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { ChevronsDown, Github, Menu } from "lucide-react";
3 | import React from "react";
4 | import {
5 | Sheet,
6 | SheetContent,
7 | SheetFooter,
8 | SheetHeader,
9 | SheetTitle,
10 | SheetTrigger,
11 | } from "../ui/sheet";
12 | import { Separator } from "../ui/separator";
13 | import {
14 | NavigationMenu,
15 | NavigationMenuContent,
16 | NavigationMenuItem,
17 | NavigationMenuLink,
18 | NavigationMenuList,
19 | NavigationMenuTrigger,
20 | } from "../ui/navigation-menu";
21 | import { Button } from "../ui/button";
22 | import Link from "next/link";
23 | import Image from "next/image";
24 | import { ToggleTheme } from "./toogle-theme";
25 |
26 | interface RouteProps {
27 | href: string;
28 | label: string;
29 | }
30 |
31 | interface FeatureProps {
32 | title: string;
33 | description: string;
34 | }
35 |
36 | const routeList: RouteProps[] = [
37 | {
38 | href: "#testimonials",
39 | label: "Testimonials",
40 | },
41 | {
42 | href: "#team",
43 | label: "Team",
44 | },
45 | {
46 | href: "#contact",
47 | label: "Contact",
48 | },
49 | {
50 | href: "#faq",
51 | label: "FAQ",
52 | },
53 | ];
54 |
55 | const featureList: FeatureProps[] = [
56 | {
57 | title: "Showcase Your Value ",
58 | description: "Highlight how your product solves user problems.",
59 | },
60 | {
61 | title: "Build Trust",
62 | description:
63 | "Leverages social proof elements to establish trust and credibility.",
64 | },
65 | {
66 | title: "Capture Leads",
67 | description:
68 | "Make your lead capture form visually appealing and strategically.",
69 | },
70 | ];
71 |
72 | export const Navbar = () => {
73 | const [isOpen, setIsOpen] = React.useState(false);
74 | return (
75 |
189 | );
190 | };
191 |
--------------------------------------------------------------------------------
/components/layout/sections/benefits.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2 | import { Icon } from "@/components/ui/icon";
3 | import { icons } from "lucide-react";
4 |
5 | interface BenefitsProps {
6 | icon: string;
7 | title: string;
8 | description: string;
9 | }
10 |
11 | const benefitList: BenefitsProps[] = [
12 | {
13 | icon: "Blocks",
14 | title: "Build Brand Trust",
15 | description:
16 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. A odio velit cum aliquam. Natus consectetur dolores.",
17 | },
18 | {
19 | icon: "LineChart",
20 | title: "More Leads",
21 | description:
22 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. A odio velit cum aliquam, natus consectetur.",
23 | },
24 | {
25 | icon: "Wallet",
26 | title: "Higher Conversions",
27 | description:
28 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Natus consectetur. A odio velit cum aliquam",
29 | },
30 | {
31 | icon: "Sparkle",
32 | title: "Test Marketing Ideas",
33 | description:
34 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. A odio velit cum aliquam. Natus consectetur dolores.",
35 | },
36 | ];
37 |
38 | export const BenefitsSection = () => {
39 | return (
40 |
41 |
42 |
43 |
Benefits
44 |
45 |
46 | Your Shortcut to Success
47 |
48 |
49 | Lorem ipsum dolor sit amet consectetur, adipisicing elit. Non
50 | ducimus reprehenderit architecto rerum similique facere odit
51 | deleniti necessitatibus quo quae.
52 |
53 |
54 |
55 |
56 | {benefitList.map(({ icon, title, description }, index) => (
57 |
61 |
62 |
63 |
69 |
70 | 0{index + 1}
71 |
72 |
73 |
74 | {title}
75 |
76 |
77 |
78 | {description}
79 |
80 |
81 | ))}
82 |
83 |
84 |
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/components/layout/sections/community.tsx:
--------------------------------------------------------------------------------
1 | import DiscordIcon from "@/components/icons/discord-icon";
2 | import { Button } from "@/components/ui/button";
3 | import {
4 | Card,
5 | CardContent,
6 | CardFooter,
7 | CardHeader,
8 | CardTitle,
9 | } from "@/components/ui/card";
10 |
11 | export const CommunitySection = () => {
12 | return (
13 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/components/layout/sections/contact.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | Card,
4 | CardContent,
5 | CardFooter,
6 | CardHeader,
7 | } from "@/components/ui/card";
8 | import { Building2, Clock, Mail, Phone } from "lucide-react";
9 | import { useForm } from "react-hook-form";
10 | import { z } from "zod";
11 | import { zodResolver } from "@hookform/resolvers/zod";
12 | import {
13 | Form,
14 | FormControl,
15 | FormField,
16 | FormItem,
17 | FormLabel,
18 | FormMessage,
19 | } from "@/components/ui/form";
20 | import { Input } from "@/components/ui/input";
21 | import { Button } from "@/components/ui/button";
22 | import {
23 | Select,
24 | SelectContent,
25 | SelectItem,
26 | SelectTrigger,
27 | SelectValue,
28 | } from "@/components/ui/select";
29 | import { Textarea } from "@/components/ui/textarea";
30 |
31 | const formSchema = z.object({
32 | firstName: z.string().min(2).max(255),
33 | lastName: z.string().min(2).max(255),
34 | email: z.string().email(),
35 | subject: z.string().min(2).max(255),
36 | message: z.string(),
37 | });
38 |
39 | export const ContactSection = () => {
40 | const form = useForm>({
41 | resolver: zodResolver(formSchema),
42 | defaultValues: {
43 | firstName: "",
44 | lastName: "",
45 | email: "",
46 | subject: "Web Development",
47 | message: "",
48 | },
49 | });
50 |
51 | function onSubmit(values: z.infer) {
52 | const { firstName, lastName, email, subject, message } = values;
53 | console.log(values);
54 |
55 | const mailToLink = `mailto:leomirandadev@gmail.com?subject=${subject}&body=Hello I am ${firstName} ${lastName}, my Email is ${email}. %0D%0A${message}`;
56 |
57 | window.location.href = mailToLink;
58 | }
59 |
60 | return (
61 |
244 | );
245 | };
246 |
--------------------------------------------------------------------------------
/components/layout/sections/faq.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Accordion,
3 | AccordionContent,
4 | AccordionItem,
5 | AccordionTrigger,
6 | } from "@/components/ui/accordion";
7 |
8 | interface FAQProps {
9 | question: string;
10 | answer: string;
11 | value: string;
12 | }
13 |
14 | const FAQList: FAQProps[] = [
15 | {
16 | question: "Is this template free?",
17 | answer: "Yes. It is a free NextJS Shadcn template.",
18 | value: "item-1",
19 | },
20 | {
21 | question: "Duis aute irure dolor in reprehenderit in voluptate velit?",
22 | answer:
23 | "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint labore quidem quam consectetur sapiente, iste rerum reiciendis animi nihil nostrum sit quo, modi quod.",
24 | value: "item-2",
25 | },
26 | {
27 | question:
28 | "Lorem ipsum dolor sit amet Consectetur natus dolor minus quibusdam?",
29 | answer:
30 | "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Labore qui nostrum reiciendis veritatis.",
31 | value: "item-3",
32 | },
33 | {
34 | question: "Excepteur sint occaecat cupidata non proident sunt?",
35 | answer: "Lorem ipsum dolor sit amet consectetur, adipisicing elit.",
36 | value: "item-4",
37 | },
38 | {
39 | question:
40 | "Enim ad minim veniam, quis nostrud exercitation ullamco laboris?",
41 | answer: "consectetur adipisicing elit. Sint labore.",
42 | value: "item-5",
43 | },
44 | ];
45 |
46 | export const FAQSection = () => {
47 | return (
48 |
49 |
50 |
51 | FAQS
52 |
53 |
54 |
55 | Common Questions
56 |
57 |
58 |
59 |
60 | {FAQList.map(({ question, answer, value }) => (
61 |
62 |
63 | {question}
64 |
65 |
66 | {answer}
67 |
68 | ))}
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/components/layout/sections/features.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2 | import { Icon } from "@/components/ui/icon";
3 | import { icons } from "lucide-react";
4 |
5 | interface FeaturesProps {
6 | icon: string;
7 | title: string;
8 | description: string;
9 | }
10 |
11 | const featureList: FeaturesProps[] = [
12 | {
13 | icon: "TabletSmartphone",
14 | title: "Mobile Friendly",
15 | description:
16 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. A odio velit cum aliquam, consectetur.",
17 | },
18 | {
19 | icon: "BadgeCheck",
20 | title: "Social Proof",
21 | description:
22 | "Lorem ipsum dolor sit amet consectetur. Natus consectetur, odio ea accusamus aperiam.",
23 | },
24 | {
25 | icon: "Goal",
26 | title: "Targeted Content",
27 | description:
28 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. odio ea accusamus aperiam.",
29 | },
30 | {
31 | icon: "PictureInPicture",
32 | title: "Strong Visuals",
33 | description:
34 | "Lorem elit. A odio velit cum aliquam. Natus consectetur dolores, odio ea accusamus aperiam.",
35 | },
36 | {
37 | icon: "MousePointerClick",
38 | title: "Clear CTA",
39 | description:
40 | "Lorem ipsum dolor sit amet consectetur adipisicing. odio ea accusamus consectetur.",
41 | },
42 | {
43 | icon: "Newspaper",
44 | title: "Clear Headline",
45 | description:
46 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. A odio velit cum aliquam. Natus consectetur.",
47 | },
48 | ];
49 |
50 | export const FeaturesSection = () => {
51 | return (
52 |
53 |
54 | Features
55 |
56 |
57 |
58 | What Makes Us Different
59 |
60 |
61 |
62 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptatem
63 | fugiat, odit similique quasi sint reiciendis quidem iure veritatis optio
64 | facere tenetur.
65 |
66 |
67 |
68 | {featureList.map(({ icon, title, description }) => (
69 |
70 |
71 |
72 |
73 |
79 |
80 |
81 | {title}
82 |
83 |
84 |
85 | {description}
86 |
87 |
88 |
89 | ))}
90 |
91 |
92 | );
93 | };
94 |
--------------------------------------------------------------------------------
/components/layout/sections/footer.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from "@/components/ui/separator";
2 | import { ChevronsDownIcon } from "lucide-react";
3 | import Link from "next/link";
4 |
5 | export const FooterSection = () => {
6 | return (
7 |
118 | );
119 | };
120 |
--------------------------------------------------------------------------------
/components/layout/sections/hero.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Badge } from "@/components/ui/badge";
3 | import { Button } from "@/components/ui/button";
4 | import { ArrowRight } from "lucide-react";
5 | import { useTheme } from "next-themes";
6 | import Image from "next/image";
7 | import Link from "next/link";
8 |
9 | export const HeroSection = () => {
10 | const { theme } = useTheme();
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | New
18 |
19 | Design is out now!
20 |
21 |
22 |
23 |
24 | Experience the
25 |
26 | Shadcn
27 |
28 | landing page
29 |
30 |
31 |
32 |
33 | {`We're more than just a tool, we're a community of passionate
34 | creators. Get access to exclusive resources, tutorials, and support.`}
35 |
36 |
37 |
38 |
39 | Get Started
40 |
41 |
42 |
43 |
48 |
52 | Github respository
53 |
54 |
55 |
56 |
57 |
58 |
74 |
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/components/layout/sections/pricing.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import {
3 | Card,
4 | CardContent,
5 | CardDescription,
6 | CardFooter,
7 | CardHeader,
8 | CardTitle,
9 | } from "@/components/ui/card";
10 | import { Check } from "lucide-react";
11 |
12 | enum PopularPlan {
13 | NO = 0,
14 | YES = 1,
15 | }
16 |
17 | interface PlanProps {
18 | title: string;
19 | popular: PopularPlan;
20 | price: number;
21 | description: string;
22 | buttonText: string;
23 | benefitList: string[];
24 | }
25 |
26 | const plans: PlanProps[] = [
27 | {
28 | title: "Free",
29 | popular: 0,
30 | price: 0,
31 | description:
32 | "Lorem ipsum dolor sit, amet ipsum consectetur adipisicing elit.",
33 | buttonText: "Start Free Trial",
34 | benefitList: [
35 | "1 team member",
36 | "1 GB storage",
37 | "Upto 2 pages",
38 | "Community support",
39 | "AI assistance",
40 | ],
41 | },
42 | {
43 | title: "Premium",
44 | popular: 1,
45 | price: 45,
46 | description:
47 | "Lorem ipsum dolor sit, amet ipsum consectetur adipisicing elit.",
48 | buttonText: "Get starterd",
49 | benefitList: [
50 | "4 team member",
51 | "8 GB storage",
52 | "Upto 6 pages",
53 | "Priority support",
54 | "AI assistance",
55 | ],
56 | },
57 | {
58 | title: "Enterprise",
59 | popular: 0,
60 | price: 120,
61 | description:
62 | "Lorem ipsum dolor sit, amet ipsum consectetur adipisicing elit.",
63 | buttonText: "Contact US",
64 | benefitList: [
65 | "10 team member",
66 | "20 GB storage",
67 | "Upto 10 pages",
68 | "Phone & email support",
69 | "AI assistance",
70 | ],
71 | },
72 | ];
73 |
74 | export const PricingSection = () => {
75 | return (
76 |
77 |
78 | Pricing
79 |
80 |
81 |
82 | Get unlimitted access
83 |
84 |
85 |
86 | Lorem ipsum dolor sit amet consectetur adipisicing reiciendis.
87 |
88 |
89 |
90 | {plans.map(
91 | ({ title, popular, price, description, buttonText, benefitList }) => (
92 |
100 |
101 | {title}
102 |
103 |
104 | {description}
105 |
106 |
107 |
108 | ${price}
109 | /month
110 |
111 |
112 |
113 |
114 |
115 | {benefitList.map((benefit) => (
116 |
117 |
118 | {benefit}
119 |
120 | ))}
121 |
122 |
123 |
124 |
125 |
131 | {buttonText}
132 |
133 |
134 |
135 | )
136 | )}
137 |
138 |
139 | );
140 | };
141 |
--------------------------------------------------------------------------------
/components/layout/sections/services.tsx:
--------------------------------------------------------------------------------
1 | import { Badge } from "@/components/ui/badge";
2 | import {
3 | Card,
4 | CardDescription,
5 | CardHeader,
6 | CardTitle,
7 | } from "@/components/ui/card";
8 |
9 | enum ProService {
10 | YES = 1,
11 | NO = 0,
12 | }
13 | interface ServiceProps {
14 | title: string;
15 | pro: ProService;
16 | description: string;
17 | }
18 | const serviceList: ServiceProps[] = [
19 | {
20 | title: "Custom Domain Integration",
21 | description:
22 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit adipisicing.",
23 | pro: 0,
24 | },
25 | {
26 | title: "Social Media Integrations",
27 | description:
28 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Molestiae, dicta.",
29 | pro: 0,
30 | },
31 | {
32 | title: "Email Marketing Integrations",
33 | description: "Lorem dolor sit amet adipisicing.",
34 | pro: 0,
35 | },
36 | {
37 | title: "SEO Optimization",
38 | description: "Lorem ipsum dolor sit amet consectetur.",
39 | pro: 1,
40 | },
41 | ];
42 |
43 | export const ServicesSection = () => {
44 | return (
45 |
46 |
47 | Services
48 |
49 |
50 |
51 | Grow Your Business
52 |
53 |
54 | From marketing and sales to operations and strategy, we have the
55 | expertise to help you achieve your goals.
56 |
57 |
58 |
59 |
60 | {serviceList.map(({ title, description, pro }) => (
61 |
65 |
66 | {title}
67 | {description}
68 |
69 |
74 | PRO
75 |
76 |
77 | ))}
78 |
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/components/layout/sections/sponsors.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Icon } from "@/components/ui/icon";
4 | import { Marquee } from "@devnomic/marquee";
5 | import "@devnomic/marquee/dist/index.css";
6 | import { icons } from "lucide-react";
7 | interface sponsorsProps {
8 | icon: string;
9 | name: string;
10 | }
11 |
12 | const sponsors: sponsorsProps[] = [
13 | {
14 | icon: "Crown",
15 | name: "Acmebrand",
16 | },
17 | {
18 | icon: "Vegan",
19 | name: "Acmelogo",
20 | },
21 | {
22 | icon: "Ghost",
23 | name: "Acmesponsor",
24 | },
25 | {
26 | icon: "Puzzle",
27 | name: "Acmeipsum",
28 | },
29 | {
30 | icon: "Squirrel",
31 | name: "Acme",
32 | },
33 | {
34 | icon: "Cookie",
35 | name: "Accmee",
36 | },
37 | {
38 | icon: "Drama",
39 | name: "Acmetech",
40 | },
41 | ];
42 |
43 | export const SponsorsSection = () => {
44 | return (
45 |
74 | );
75 | };
76 |
--------------------------------------------------------------------------------
/components/layout/sections/team.tsx:
--------------------------------------------------------------------------------
1 | import GithubIcon from "@/components/icons/github-icon";
2 | import LinkedInIcon from "@/components/icons/linkedin-icon";
3 | import XIcon from "@/components/icons/x-icon";
4 | import {
5 | Card,
6 | CardContent,
7 | CardHeader,
8 | CardTitle,
9 | CardFooter,
10 | } from "@/components/ui/card";
11 | import Image from "next/image";
12 | import Link from "next/link";
13 | interface TeamProps {
14 | imageUrl: string;
15 | firstName: string;
16 | lastName: string;
17 | positions: string[];
18 | socialNetworks: SocialNetworkProps[];
19 | }
20 | interface SocialNetworkProps {
21 | name: string;
22 | url: string;
23 | }
24 | export const TeamSection = () => {
25 | const teamList: TeamProps[] = [
26 | {
27 | imageUrl: "https://i.pravatar.cc/250?img=58",
28 | firstName: "Leo",
29 | lastName: "Miranda",
30 | positions: ["Vue Fronted Developer", "Creator Of This Website"],
31 | socialNetworks: [
32 | {
33 | name: "LinkedIn",
34 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
35 | },
36 | {
37 | name: "Github",
38 | url: "https://github.com/leoMirandaa",
39 | },
40 | {
41 | name: "X",
42 | url: "https://x.com/leo_mirand4",
43 | },
44 | ],
45 | },
46 | {
47 | imageUrl:
48 | "https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=1528&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
49 | firstName: "Elizabeth",
50 | lastName: "Moore",
51 | positions: ["UI/UX Designer"],
52 | socialNetworks: [
53 | {
54 | name: "LinkedIn",
55 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
56 | },
57 | {
58 | name: "X",
59 | url: "https://x.com/leo_mirand4",
60 | },
61 | ],
62 | },
63 | {
64 | imageUrl:
65 | "https://images.unsplash.com/photo-1527980965255-d3b416303d12?q=80&w=1760&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
66 | firstName: "David",
67 | lastName: "Diaz",
68 | positions: ["Machine Learning Engineer", "TensorFlow Tinkerer"],
69 | socialNetworks: [
70 | {
71 | name: "LinkedIn",
72 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
73 | },
74 | {
75 | name: "Github",
76 | url: "https://github.com/leoMirandaa",
77 | },
78 | ],
79 | },
80 | {
81 | imageUrl:
82 | "https://images.unsplash.com/photo-1573497161161-c3e73707e25c?q=80&w=1587&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
83 | firstName: "Sarah",
84 | lastName: "Robinson",
85 | positions: ["Cloud Native Developer", " Kubernetes Orchestrator"],
86 | socialNetworks: [
87 | {
88 | name: "LinkedIn",
89 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
90 | },
91 | {
92 | name: "Github",
93 | url: "https://github.com/leoMirandaa",
94 | },
95 | {
96 | name: "X",
97 | url: "https://x.com/leo_mirand4",
98 | },
99 | ],
100 | },
101 | {
102 | imageUrl:
103 | "https://images.unsplash.com/photo-1616805765352-beedbad46b2a?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
104 | firstName: "Michael",
105 | lastName: "Holland",
106 | positions: ["DevOps Engineer", "CI/CD Pipeline Mastermind"],
107 | socialNetworks: [
108 | {
109 | name: "LinkedIn",
110 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
111 | },
112 | ],
113 | },
114 | {
115 | imageUrl:
116 | "https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?q=80&w=1587&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
117 | firstName: "Zoe",
118 | lastName: "Garcia",
119 | positions: ["JavaScript Evangelist", "Deno Champion"],
120 | socialNetworks: [
121 | {
122 | name: "LinkedIn",
123 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
124 | },
125 | {
126 | name: "Github",
127 | url: "https://github.com/leoMirandaa",
128 | },
129 | ],
130 | },
131 | {
132 | imageUrl:
133 | "https://images.unsplash.com/photo-1633332755192-727a05c4013d?q=80&w=1480&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
134 | firstName: "Evan",
135 | lastName: "James",
136 | positions: ["Backend Developer"],
137 | socialNetworks: [
138 | {
139 | name: "LinkedIn",
140 | url: "https://www.linkedin.com/in/leopoldo-miranda/",
141 | },
142 | {
143 | name: "Github",
144 | url: "https://github.com/leoMirandaa",
145 | },
146 | {
147 | name: "X",
148 | url: "https://x.com/leo_mirand4",
149 | },
150 | ],
151 | },
152 | {
153 | imageUrl:
154 | "https://images.unsplash.com/photo-1573497019236-17f8177b81e8?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3Dhttps://images.unsplash.com/photo-1573497019236-17f8177b81e8?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
155 | firstName: "Pam",
156 | lastName: "Taylor",
157 | positions: ["Fullstack Developer", "UX Researcher"],
158 | socialNetworks: [
159 | {
160 | name: "X",
161 | url: "https://x.com/leo_mirand4",
162 | },
163 | ],
164 | },
165 | ];
166 | const socialIcon = (socialName: string) => {
167 | switch (socialName) {
168 | case "LinkedIn":
169 | return ;
170 | case "Github":
171 | return ;
172 | case "X":
173 | return ;
174 | }
175 | };
176 |
177 | return (
178 |
179 |
180 |
181 | Team
182 |
183 |
184 |
185 | The Company Dream Team
186 |
187 |
188 |
189 |
190 | {teamList.map(
191 | (
192 | { imageUrl, firstName, lastName, positions, socialNetworks },
193 | index
194 | ) => (
195 |
199 |
200 |
201 |
208 |
209 |
210 | {firstName}
211 | {lastName}
212 |
213 |
214 | {positions.map((position, index) => (
215 |
221 | {position}
222 | {index < positions.length - 1 && , }
223 |
224 | ))}
225 |
226 |
227 | {socialNetworks.map(({ name, url }, index) => (
228 |
234 | {socialIcon(name)}
235 |
236 | ))}
237 |
238 |
239 | )
240 | )}
241 |
242 |
243 | );
244 | };
245 |
--------------------------------------------------------------------------------
/components/layout/sections/testimonial.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
3 | import {
4 | Card,
5 | CardContent,
6 | CardDescription,
7 | CardHeader,
8 | CardTitle,
9 | } from "@/components/ui/card";
10 | import {
11 | Carousel,
12 | CarouselContent,
13 | CarouselItem,
14 | CarouselNext,
15 | CarouselPrevious,
16 | } from "@/components/ui/carousel";
17 | import { Star } from "lucide-react";
18 |
19 | interface ReviewProps {
20 | image: string;
21 | name: string;
22 | userName: string;
23 | comment: string;
24 | rating: number;
25 | }
26 |
27 | const reviewList: ReviewProps[] = [
28 | {
29 | image: "https://github.com/shadcn.png",
30 | name: "John Doe",
31 | userName: "Product Manager",
32 | comment:
33 | "Wow NextJs + Shadcn is awesome!. This template lets me change colors, fonts and images to match my brand identity. ",
34 | rating: 5.0,
35 | },
36 | {
37 | image: "https://github.com/shadcn.png",
38 | name: "Sophia Collins",
39 | userName: "Cybersecurity Analyst",
40 | comment:
41 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna. ",
42 | rating: 4.8,
43 | },
44 |
45 | {
46 | image: "https://github.com/shadcn.png",
47 | name: "Adam Johnson",
48 | userName: "Chief Technology Officer",
49 | comment:
50 | "Lorem ipsum dolor sit amet,exercitation. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
51 | rating: 4.9,
52 | },
53 | {
54 | image: "https://github.com/shadcn.png",
55 | name: "Ethan Parker",
56 | userName: "Data Scientist",
57 | comment:
58 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod labore et dolore magna aliqua. Ut enim ad minim veniam.",
59 | rating: 5.0,
60 | },
61 | {
62 | image: "https://github.com/shadcn.png",
63 | name: "Ava Mitchell",
64 | userName: "IT Project Manager",
65 | comment:
66 | "Lorem ipsum dolor sit amet, tempor incididunt aliqua. Ut enim ad minim veniam, quis nostrud incididunt consectetur adipiscing elit.",
67 | rating: 5.0,
68 | },
69 | {
70 | image: "https://github.com/shadcn.png",
71 | name: "Isabella Reed",
72 | userName: "DevOps Engineer",
73 | comment:
74 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
75 | rating: 4.9,
76 | },
77 | ];
78 |
79 | export const TestimonialSection = () => {
80 | return (
81 |
82 |
83 |
84 | Testimonials
85 |
86 |
87 |
88 | Hear What Our 1000+ Clients Say
89 |
90 |
91 |
92 |
98 |
99 | {reviewList.map((review) => (
100 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {`"${review.comment}"`}
114 |
115 |
116 |
117 |
118 |
119 |
123 | SV
124 |
125 |
126 |
127 | {review.name}
128 | {review.userName}
129 |
130 |
131 |
132 |
133 |
134 | ))}
135 |
136 |
137 |
138 |
139 |
140 | );
141 | };
142 |
--------------------------------------------------------------------------------
/components/layout/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children} ;
9 | }
10 |
--------------------------------------------------------------------------------
/components/layout/toogle-theme.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "next-themes";
2 | import { Button } from "../ui/button";
3 | import { Moon, Sun } from "lucide-react";
4 |
5 | export const ToggleTheme = () => {
6 | const { theme, setTheme } = useTheme();
7 | return (
8 | setTheme(theme === "light" ? "dark" : "light")}
10 | size="sm"
11 | variant="ghost"
12 | className="w-full justify-start"
13 | >
14 |
15 |
16 | Escuro
17 |
18 |
19 |
20 |
21 | Claro
22 |
23 |
24 | Trocar de tema
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion";
5 | import { ChevronDown, Plus } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Accordion = AccordionPrimitive.Root;
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ));
24 | AccordionItem.displayName = "AccordionItem";
25 |
26 | const AccordionTrigger = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, children, ...props }, ref) => (
30 |
31 | svg]:rotate-[135deg]",
35 | className
36 | )}
37 | {...props}
38 | >
39 | {children}
40 |
41 |
42 |
43 | ));
44 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
45 |
46 | const AccordionContent = React.forwardRef<
47 | React.ElementRef,
48 | React.ComponentPropsWithoutRef
49 | >(({ className, children, ...props }, ref) => (
50 |
55 |
58 | {children}
59 |
60 |
61 | ));
62 |
63 | AccordionContent.displayName = AccordionPrimitive.Content.displayName;
64 |
65 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
66 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border border-secondary 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",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | );
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | );
34 | }
35 |
36 | export { Badge, badgeVariants };
37 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ));
45 | CardTitle.displayName = "CardTitle";
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ));
57 | CardDescription.displayName = "CardDescription";
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ));
65 | CardContent.displayName = "CardContent";
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ));
77 | CardFooter.displayName = "CardFooter";
78 |
79 | export {
80 | Card,
81 | CardHeader,
82 | CardFooter,
83 | CardTitle,
84 | CardDescription,
85 | CardContent,
86 | };
87 |
--------------------------------------------------------------------------------
/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import useEmblaCarousel, {
5 | type UseEmblaCarouselType,
6 | } from "embla-carousel-react"
7 | import { ArrowLeft, ArrowRight } from "lucide-react"
8 |
9 | import { cn } from "@/lib/utils"
10 | import { Button } from "@/components/ui/button"
11 |
12 | type CarouselApi = UseEmblaCarouselType[1]
13 | type UseCarouselParameters = Parameters
14 | type CarouselOptions = UseCarouselParameters[0]
15 | type CarouselPlugin = UseCarouselParameters[1]
16 |
17 | type CarouselProps = {
18 | opts?: CarouselOptions
19 | plugins?: CarouselPlugin
20 | orientation?: "horizontal" | "vertical"
21 | setApi?: (api: CarouselApi) => void
22 | }
23 |
24 | type CarouselContextProps = {
25 | carouselRef: ReturnType[0]
26 | api: ReturnType[1]
27 | scrollPrev: () => void
28 | scrollNext: () => void
29 | canScrollPrev: boolean
30 | canScrollNext: boolean
31 | } & CarouselProps
32 |
33 | const CarouselContext = React.createContext(null)
34 |
35 | function useCarousel() {
36 | const context = React.useContext(CarouselContext)
37 |
38 | if (!context) {
39 | throw new Error("useCarousel must be used within a ")
40 | }
41 |
42 | return context
43 | }
44 |
45 | const Carousel = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes & CarouselProps
48 | >(
49 | (
50 | {
51 | orientation = "horizontal",
52 | opts,
53 | setApi,
54 | plugins,
55 | className,
56 | children,
57 | ...props
58 | },
59 | ref
60 | ) => {
61 | const [carouselRef, api] = useEmblaCarousel(
62 | {
63 | ...opts,
64 | axis: orientation === "horizontal" ? "x" : "y",
65 | },
66 | plugins
67 | )
68 | const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69 | const [canScrollNext, setCanScrollNext] = React.useState(false)
70 |
71 | const onSelect = React.useCallback((api: CarouselApi) => {
72 | if (!api) {
73 | return
74 | }
75 |
76 | setCanScrollPrev(api.canScrollPrev())
77 | setCanScrollNext(api.canScrollNext())
78 | }, [])
79 |
80 | const scrollPrev = React.useCallback(() => {
81 | api?.scrollPrev()
82 | }, [api])
83 |
84 | const scrollNext = React.useCallback(() => {
85 | api?.scrollNext()
86 | }, [api])
87 |
88 | const handleKeyDown = React.useCallback(
89 | (event: React.KeyboardEvent) => {
90 | if (event.key === "ArrowLeft") {
91 | event.preventDefault()
92 | scrollPrev()
93 | } else if (event.key === "ArrowRight") {
94 | event.preventDefault()
95 | scrollNext()
96 | }
97 | },
98 | [scrollPrev, scrollNext]
99 | )
100 |
101 | React.useEffect(() => {
102 | if (!api || !setApi) {
103 | return
104 | }
105 |
106 | setApi(api)
107 | }, [api, setApi])
108 |
109 | React.useEffect(() => {
110 | if (!api) {
111 | return
112 | }
113 |
114 | onSelect(api)
115 | api.on("reInit", onSelect)
116 | api.on("select", onSelect)
117 |
118 | return () => {
119 | api?.off("select", onSelect)
120 | }
121 | }, [api, onSelect])
122 |
123 | return (
124 |
137 |
145 | {children}
146 |
147 |
148 | )
149 | }
150 | )
151 | Carousel.displayName = "Carousel"
152 |
153 | const CarouselContent = React.forwardRef<
154 | HTMLDivElement,
155 | React.HTMLAttributes
156 | >(({ className, ...props }, ref) => {
157 | const { carouselRef, orientation } = useCarousel()
158 |
159 | return (
160 |
171 | )
172 | })
173 | CarouselContent.displayName = "CarouselContent"
174 |
175 | const CarouselItem = React.forwardRef<
176 | HTMLDivElement,
177 | React.HTMLAttributes
178 | >(({ className, ...props }, ref) => {
179 | const { orientation } = useCarousel()
180 |
181 | return (
182 |
193 | )
194 | })
195 | CarouselItem.displayName = "CarouselItem"
196 |
197 | const CarouselPrevious = React.forwardRef<
198 | HTMLButtonElement,
199 | React.ComponentProps
200 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201 | const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202 |
203 | return (
204 |
219 |
220 | Previous slide
221 |
222 | )
223 | })
224 | CarouselPrevious.displayName = "CarouselPrevious"
225 |
226 | const CarouselNext = React.forwardRef<
227 | HTMLButtonElement,
228 | React.ComponentProps
229 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230 | const { orientation, scrollNext, canScrollNext } = useCarousel()
231 |
232 | return (
233 |
248 |
249 | Next slide
250 |
251 | )
252 | })
253 | CarouselNext.displayName = "CarouselNext"
254 |
255 | export {
256 | type CarouselApi,
257 | Carousel,
258 | CarouselContent,
259 | CarouselItem,
260 | CarouselPrevious,
261 | CarouselNext,
262 | }
263 |
--------------------------------------------------------------------------------
/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { Slot } from "@radix-ui/react-slot"
6 | import {
7 | Controller,
8 | ControllerProps,
9 | FieldPath,
10 | FieldValues,
11 | FormProvider,
12 | useFormContext,
13 | } from "react-hook-form"
14 |
15 | import { cn } from "@/lib/utils"
16 | import { Label } from "@/components/ui/label"
17 |
18 | const Form = FormProvider
19 |
20 | type FormFieldContextValue<
21 | TFieldValues extends FieldValues = FieldValues,
22 | TName extends FieldPath = FieldPath
23 | > = {
24 | name: TName
25 | }
26 |
27 | const FormFieldContext = React.createContext(
28 | {} as FormFieldContextValue
29 | )
30 |
31 | const FormField = <
32 | TFieldValues extends FieldValues = FieldValues,
33 | TName extends FieldPath = FieldPath
34 | >({
35 | ...props
36 | }: ControllerProps) => {
37 | return (
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const useFormField = () => {
45 | const fieldContext = React.useContext(FormFieldContext)
46 | const itemContext = React.useContext(FormItemContext)
47 | const { getFieldState, formState } = useFormContext()
48 |
49 | const fieldState = getFieldState(fieldContext.name, formState)
50 |
51 | if (!fieldContext) {
52 | throw new Error("useFormField should be used within ")
53 | }
54 |
55 | const { id } = itemContext
56 |
57 | return {
58 | id,
59 | name: fieldContext.name,
60 | formItemId: `${id}-form-item`,
61 | formDescriptionId: `${id}-form-item-description`,
62 | formMessageId: `${id}-form-item-message`,
63 | ...fieldState,
64 | }
65 | }
66 |
67 | type FormItemContextValue = {
68 | id: string
69 | }
70 |
71 | const FormItemContext = React.createContext(
72 | {} as FormItemContextValue
73 | )
74 |
75 | const FormItem = React.forwardRef<
76 | HTMLDivElement,
77 | React.HTMLAttributes
78 | >(({ className, ...props }, ref) => {
79 | const id = React.useId()
80 |
81 | return (
82 |
83 |
84 |
85 | )
86 | })
87 | FormItem.displayName = "FormItem"
88 |
89 | const FormLabel = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => {
93 | const { error, formItemId } = useFormField()
94 |
95 | return (
96 |
102 | )
103 | })
104 | FormLabel.displayName = "FormLabel"
105 |
106 | const FormControl = React.forwardRef<
107 | React.ElementRef,
108 | React.ComponentPropsWithoutRef
109 | >(({ ...props }, ref) => {
110 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111 |
112 | return (
113 |
124 | )
125 | })
126 | FormControl.displayName = "FormControl"
127 |
128 | const FormDescription = React.forwardRef<
129 | HTMLParagraphElement,
130 | React.HTMLAttributes
131 | >(({ className, ...props }, ref) => {
132 | const { formDescriptionId } = useFormField()
133 |
134 | return (
135 |
141 | )
142 | })
143 | FormDescription.displayName = "FormDescription"
144 |
145 | const FormMessage = React.forwardRef<
146 | HTMLParagraphElement,
147 | React.HTMLAttributes
148 | >(({ className, children, ...props }, ref) => {
149 | const { error, formMessageId } = useFormField()
150 | const body = error ? String(error?.message) : children
151 |
152 | if (!body) {
153 | return null
154 | }
155 |
156 | return (
157 |
163 | {body}
164 |
165 | )
166 | })
167 | FormMessage.displayName = "FormMessage"
168 |
169 | export {
170 | useFormField,
171 | Form,
172 | FormItem,
173 | FormLabel,
174 | FormControl,
175 | FormDescription,
176 | FormMessage,
177 | FormField,
178 | }
179 |
--------------------------------------------------------------------------------
/components/ui/icon.tsx:
--------------------------------------------------------------------------------
1 | import { icons } from "lucide-react";
2 |
3 | export const Icon = ({
4 | name,
5 | color,
6 | size,
7 | className,
8 | }: {
9 | name: keyof typeof icons;
10 | color: string;
11 | size: number;
12 | className?: string;
13 | }) => {
14 | const LucideIcon = icons[name as keyof typeof icons];
15 |
16 | return ;
17 | };
18 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SelectPrimitive from "@radix-ui/react-select";
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Select = SelectPrimitive.Root;
10 |
11 | const SelectGroup = SelectPrimitive.Group;
12 |
13 | const SelectValue = SelectPrimitive.Value;
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ));
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ));
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ));
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName;
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ));
100 | SelectContent.displayName = SelectPrimitive.Content.displayName;
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ));
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | {children}
133 |
134 | ));
135 | SelectItem.displayName = SelectPrimitive.Item.displayName;
136 |
137 | const SelectSeparator = React.forwardRef<
138 | React.ElementRef,
139 | React.ComponentPropsWithoutRef
140 | >(({ className, ...props }, ref) => (
141 |
146 | ));
147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
148 |
149 | export {
150 | Select,
151 | SelectGroup,
152 | SelectValue,
153 | SelectTrigger,
154 | SelectContent,
155 | SelectLabel,
156 | SelectItem,
157 | SelectSeparator,
158 | SelectScrollUpButton,
159 | SelectScrollDownButton,
160 | };
161 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SheetPrimitive from "@radix-ui/react-dialog";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 | import { X } from "lucide-react";
7 |
8 | import { cn } from "@/lib/utils";
9 |
10 | const Sheet = SheetPrimitive.Root;
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger;
13 |
14 | const SheetClose = SheetPrimitive.Close;
15 |
16 | const SheetPortal = SheetPrimitive.Portal;
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ));
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | );
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 | {children}
68 |
69 |
70 | Close
71 |
72 |
73 |
74 | ));
75 | SheetContent.displayName = SheetPrimitive.Content.displayName;
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | );
89 | SheetHeader.displayName = "SheetHeader";
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | );
103 | SheetFooter.displayName = "SheetFooter";
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ));
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ));
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | };
141 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: "https",
7 | hostname: "i.pravatar.cc",
8 | },
9 | {
10 | protocol: "https",
11 | hostname: "images.unsplash.com",
12 | },
13 | {
14 | protocol: "https",
15 | hostname: "github.com",
16 | },
17 | ],
18 | },
19 | };
20 |
21 | export default nextConfig;
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shadcn-landing-page",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@devnomic/marquee": "^1.0.2",
13 | "@hookform/resolvers": "^3.9.0",
14 | "@radix-ui/react-accordion": "^1.1.2",
15 | "@radix-ui/react-avatar": "^1.0.4",
16 | "@radix-ui/react-collapsible": "^1.0.3",
17 | "@radix-ui/react-dialog": "^1.0.5",
18 | "@radix-ui/react-label": "^2.1.0",
19 | "@radix-ui/react-navigation-menu": "^1.1.4",
20 | "@radix-ui/react-scroll-area": "^1.0.5",
21 | "@radix-ui/react-select": "^2.1.1",
22 | "@radix-ui/react-separator": "^1.0.3",
23 | "@radix-ui/react-slot": "^1.1.0",
24 | "class-variance-authority": "^0.7.0",
25 | "clsx": "^2.1.1",
26 | "embla-carousel-react": "^8.1.3",
27 | "lucide-react": "^0.383.0",
28 | "next": "14.2.3",
29 | "next-themes": "^0.3.0",
30 | "react": "^18",
31 | "react-dom": "^18",
32 | "react-hook-form": "^7.52.2",
33 | "tailwind-merge": "^2.3.0",
34 | "tailwindcss-animate": "^1.0.7",
35 | "zod": "^3.23.8"
36 | },
37 | "devDependencies": {
38 | "@types/node": "^20",
39 | "@types/react": "^18",
40 | "@types/react-dom": "^18",
41 | "eslint": "^8",
42 | "eslint-config-next": "14.2.3",
43 | "postcss": "^8",
44 | "tailwindcss": "^3.4.1",
45 | "typescript": "^5"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/demo-img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobruf/shadcn-landing-page/ec8e18e8ed56ed6636023ced09948515c19258cc/public/demo-img.jpg
--------------------------------------------------------------------------------
/public/hero-image-dark.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobruf/shadcn-landing-page/ec8e18e8ed56ed6636023ced09948515c19258cc/public/hero-image-dark.jpeg
--------------------------------------------------------------------------------
/public/hero-image-light.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nobruf/shadcn-landing-page/ec8e18e8ed56ed6636023ced09948515c19258cc/public/hero-image-light.jpeg
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | const animate = require("tailwindcss-animate");
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | darkMode: ["class"],
6 | safelist: ["dark"],
7 | prefix: "",
8 |
9 | content: [
10 | "./components/**/*.{ts,tsx}",
11 | "./app/**/*.{ts,tsx}",
12 | "./src/**/*.{ts,tsx}",
13 | ],
14 |
15 | theme: {
16 | container: {
17 | center: true,
18 | padding: "1.5rem",
19 | screens: {
20 | "2xl": "1400px",
21 | },
22 | },
23 | extend: {
24 | colors: {
25 | border: "hsl(var(--border))",
26 | input: "hsl(var(--input))",
27 | ring: "hsl(var(--ring))",
28 | background: "hsl(var(--background))",
29 | foreground: "hsl(var(--foreground))",
30 | primary: {
31 | DEFAULT: "hsl(var(--primary))",
32 | foreground: "hsl(var(--primary-foreground))",
33 | },
34 | secondary: {
35 | DEFAULT: "hsl(var(--secondary))",
36 | foreground: "hsl(var(--secondary-foreground))",
37 | },
38 | destructive: {
39 | DEFAULT: "hsl(var(--destructive))",
40 | foreground: "hsl(var(--destructive-foreground))",
41 | },
42 | muted: {
43 | DEFAULT: "hsl(var(--muted))",
44 | foreground: "hsl(var(--muted-foreground))",
45 | },
46 | accent: {
47 | DEFAULT: "hsl(var(--accent))",
48 | foreground: "hsl(var(--accent-foreground))",
49 | },
50 | popover: {
51 | DEFAULT: "hsl(var(--popover))",
52 | foreground: "hsl(var(--popover-foreground))",
53 | },
54 | card: {
55 | DEFAULT: "hsl(var(--card))",
56 | foreground: "hsl(var(--card-foreground))",
57 | },
58 | },
59 | borderRadius: {
60 | xl: "calc(var(--radius) + 4px)",
61 | lg: "var(--radius)",
62 | md: "calc(var(--radius) - 2px)",
63 | sm: "calc(var(--radius) - 4px)",
64 | },
65 | keyframes: {
66 | "accordion-down": {
67 | from: { height: 0 },
68 | to: { height: "var(--radix-accordion-content-height)" },
69 | },
70 | "accordion-up": {
71 | from: { height: "var(--radix-accordion-content-height)" },
72 | to: { height: 0 },
73 | },
74 | "collapsible-down": {
75 | from: { height: 0 },
76 | to: { height: "var(--radix-collapsible-content-height)" },
77 | },
78 | "collapsible-up": {
79 | from: { height: "var(--radix-collapsible-content-height)" },
80 | to: { height: 0 },
81 | },
82 | },
83 | animation: {
84 | "accordion-down": "accordion-down 0.2s ease-out",
85 | "accordion-up": "accordion-up 0.2s ease-out",
86 | "collapsible-down": "collapsible-down 0.2s ease-in-out",
87 | "collapsible-up": "collapsible-up 0.2s ease-in-out",
88 | },
89 | },
90 | },
91 | plugins: [animate],
92 | };
93 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------