├── .eslintrc.json ├── app ├── favicon.ico ├── layout.tsx ├── loading.tsx ├── globals.css └── page.tsx ├── public ├── img │ ├── shadcn.jpg │ ├── products │ │ ├── 7C.png │ │ ├── 6700.png │ │ ├── 7IV.png │ │ ├── 7RIII.png │ │ ├── 7RV.png │ │ ├── 7SIII.png │ │ ├── FX3.png │ │ └── ZVE10.png │ └── hero-1920x1080.jpg ├── vercel.svg └── next.svg ├── next.config.js ├── postcss.config.js ├── types.ts ├── lib └── utils.ts ├── components ├── ui │ ├── container.tsx │ ├── skeleton.tsx │ ├── ProfileButton.tsx │ ├── ProductCard.tsx │ ├── avatar.tsx │ ├── button.tsx │ ├── card.tsx │ ├── sheet.tsx │ └── dropdown-menu.tsx ├── ThemeProvider.tsx ├── ProductList.tsx └── Header.tsx ├── components.json ├── .gitignore ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── tailwind.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/img/shadcn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/shadcn.jpg -------------------------------------------------------------------------------- /public/img/products/7C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/7C.png -------------------------------------------------------------------------------- /public/img/hero-1920x1080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/hero-1920x1080.jpg -------------------------------------------------------------------------------- /public/img/products/6700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/6700.png -------------------------------------------------------------------------------- /public/img/products/7IV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/7IV.png -------------------------------------------------------------------------------- /public/img/products/7RIII.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/7RIII.png -------------------------------------------------------------------------------- /public/img/products/7RV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/7RV.png -------------------------------------------------------------------------------- /public/img/products/7SIII.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/7SIII.png -------------------------------------------------------------------------------- /public/img/products/FX3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/FX3.png -------------------------------------------------------------------------------- /public/img/products/ZVE10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeSTACKr/shadcn-nextjs/HEAD/public/img/products/ZVE10.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: string; 3 | category: string; 4 | name: string; 5 | price: string; 6 | images: string[]; 7 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /components/ui/container.tsx: -------------------------------------------------------------------------------- 1 | interface ContainerProps { 2 | children: React.ReactNode; 3 | } 4 | 5 | const Container: React.FC = ({ 6 | children 7 | }) => { 8 | return ( 9 |
10 | {children} 11 |
12 | ); 13 | }; 14 | 15 | export default Container; 16 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components/ThemeProvider.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.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /components/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import ProductCard from "@/components/ui/ProductCard"; 2 | import { Product } from "@/types"; 3 | 4 | interface ProductListProps { 5 | items: Product[]; 6 | } 7 | 8 | const ProductList: React.FC = ({ items }) => { 9 | return ( 10 |
11 |
12 | {items.map((item) => ( 13 | 14 | ))} 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default ProductList; 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import "./globals.css"; 4 | import { ThemeProvider } from "next-themes"; 5 | import Header from "@/components/Header"; 6 | import type { Metadata } from "next"; 7 | import { Inter } from "next/font/google"; 8 | 9 | const inter = Inter({ subsets: ["latin"] }); 10 | 11 | export const metadata: Metadata = { 12 | title: "Create Next App", 13 | description: "Generated by create next app", 14 | }; 15 | 16 | export default function RootLayout({ 17 | children, 18 | }: { 19 | children: React.ReactNode; 20 | }) { 21 | return ( 22 | 23 | 24 | 25 |
26 | {children} 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /app/loading.tsx: -------------------------------------------------------------------------------- 1 | import Container from "@/components/ui/container"; 2 | import {Skeleton} from "@/components/ui/skeleton"; 3 | 4 | const Loading = () => { 5 | return ( 6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | ); 20 | } 21 | 22 | export default Loading; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jesse Hall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /components/ui/ProfileButton.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DropdownMenu, 3 | DropdownMenuContent, 4 | DropdownMenuItem, 5 | DropdownMenuLabel, 6 | DropdownMenuSeparator, 7 | DropdownMenuTrigger, 8 | } from "@/components/ui/dropdown-menu"; 9 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 10 | 11 | const ProfileButton = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | CN 18 | 19 | 20 | 21 | My Account 22 | 23 | Profile 24 | Billing 25 | Subscription 26 | 27 | Log Out 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default ProfileButton; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadcn-nextjs-ecommerce", 3 | "author": { 4 | "name": "Jesse Hall", 5 | "url": "https://youtube.com/codeSTACKr", 6 | }, 7 | "version": "0.1.0", 8 | "private": true, 9 | "scripts": { 10 | "dev": "next dev", 11 | "build": "next build", 12 | "start": "next start", 13 | "lint": "next lint" 14 | }, 15 | "dependencies": { 16 | "@radix-ui/react-avatar": "^1.0.3", 17 | "@radix-ui/react-dialog": "^1.0.4", 18 | "@radix-ui/react-dropdown-menu": "^2.0.5", 19 | "@radix-ui/react-icons": "^1.3.0", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@types/node": "20.5.0", 22 | "@types/react": "18.2.20", 23 | "@types/react-dom": "18.2.7", 24 | "add": "^2.0.6", 25 | "autoprefixer": "10.4.15", 26 | "avatar": "^0.1.0", 27 | "class-variance-authority": "^0.7.0", 28 | "clsx": "^2.0.0", 29 | "dropdown-menu": "^0.1.1", 30 | "eslint": "8.47.0", 31 | "eslint-config-next": "13.4.16", 32 | "lucide-react": "^0.263.1", 33 | "next": "13.4.16", 34 | "next-themes": "^0.2.1", 35 | "postcss": "8.4.27", 36 | "react": "18.2.0", 37 | "react-dom": "18.2.0", 38 | "shadcn-ui": "^0.3.0", 39 | "tailwind-merge": "^1.14.0", 40 | "tailwindcss": "3.3.3", 41 | "tailwindcss-animate": "^1.0.6", 42 | "typescript": "5.1.6" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | import { Card, CardContent, CardFooter } from "@/components/ui/card"; 5 | 6 | import { Product } from "@/types"; 7 | 8 | interface ProductCard { 9 | data: Product; 10 | } 11 | 12 | const ProductCard: React.FC = ({ data }) => { 13 | return ( 14 | 15 | 16 | 17 |
18 | 24 |
25 |
26 | 27 |
28 |

{data.name}

29 |

{data.category}

30 |
31 |
{data?.price}
32 |
33 |
34 | 35 | ); 36 | }; 37 | 38 | export default ProductCard; 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | @layer base { 7 | :root { 8 | --background: 0 0% 100%; 9 | --foreground: 222.2 84% 4.9%; 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | --popover: 0 0% 100%; 13 | --popover-foreground: 222.2 84% 4.9%; 14 | --primary: 222.2 47.4% 11.2%; 15 | --primary-foreground: 210 40% 98%; 16 | --secondary: 210 40% 96.1%; 17 | --secondary-foreground: 222.2 47.4% 11.2%; 18 | --muted: 210 40% 96.1%; 19 | --muted-foreground: 215.4 16.3% 46.9%; 20 | --accent: 210 40% 96.1%; 21 | --accent-foreground: 222.2 47.4% 11.2%; 22 | --destructive: 0 84.2% 60.2%; 23 | --destructive-foreground: 210 40% 98%; 24 | --border: 214.3 31.8% 91.4%; 25 | --input: 214.3 31.8% 91.4%; 26 | --ring: 222.2 84% 4.9%; 27 | --radius: 0.3rem; 28 | } 29 | 30 | .dark { 31 | --background: 222.2 84% 4.9%; 32 | --foreground: 210 40% 98%; 33 | --card: 222.2 84% 4.9%; 34 | --card-foreground: 210 40% 98%; 35 | --popover: 222.2 84% 4.9%; 36 | --popover-foreground: 210 40% 98%; 37 | --primary: 210 40% 98%; 38 | --primary-foreground: 222.2 47.4% 11.2%; 39 | --secondary: 217.2 32.6% 17.5%; 40 | --secondary-foreground: 210 40% 98%; 41 | --muted: 217.2 32.6% 17.5%; 42 | --muted-foreground: 215 20.2% 65.1%; 43 | --accent: 217.2 32.6% 17.5%; 44 | --accent-foreground: 210 40% 98%; 45 | --destructive: 0 62.8% 30.6%; 46 | --destructive-foreground: 210 40% 98%; 47 | --border: 217.2 32.6% 17.5%; 48 | --input: 217.2 32.6% 17.5%; 49 | --ring: 212.7 26.8% 83.9; 50 | } 51 | } 52 | 53 | @layer base { 54 | * { 55 | @apply border-border; 56 | } 57 | body { 58 | @apply bg-background text-foreground; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shadcn/ui Crash Course 2 | 3 | This repo was used in the shadcn/ui crash course video on YouTube where we build out an ecommerce landing page. 4 | 5 | 6 | [![shadcn/ui Crash Course](https://img.youtube.com/vi/DTGRIaAJYIo/0.jpg)](https://www.youtube.com/watch?v=DTGRIaAJYIo) 7 | 8 | Discover the power of shadcn/ui, a collection of reusable React components that revolutionize CSS styling in this comprehensive tutorial. Learn how to build an amazing UI in just minutes. Explore the combination of Radix UI and Tailwind CSS, and how shadcn/ui supports server-side rendering, dark mode, and themes. 9 | 10 | This video covers: 11 | 12 | - Ease of Use: Copy and paste components or use the CLI for setup. 13 | - Integration with Radix & Tailwind: Unstyled, accessible components with utility-first CSS. 14 | - Installation Guides: Step-by-step setup in Astro, Vite, Gatsby, Remix, and Next.js 13.4. 15 | - Application Build: Build an e-commerce storefront with shadcn, inspired by @codewithantonio . 16 | - Accessibility & Customization: Full keyboard navigation, assistive technology support, and more. 17 | - Themes & Dark Mode: Choose from predefined themes and enjoy TypeScript support. 18 | 19 | Shadcn/ui is not just a trend; it's a game-changer in web development. Whether you're a beginner or an expert, this video will guide you through the process of creating a visually appealing and fully functional application. Try shadcn in your next project and experience the ease and flexibility it offers. Don't forget to like and subscribe for more content like this! 20 | 21 | ## Getting Started 22 | 23 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 24 | 25 | First, run the development server: 26 | 27 | ```bash 28 | npm run dev 29 | # or 30 | yarn dev 31 | # or 32 | pnpm dev 33 | ``` 34 | 35 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 36 | -------------------------------------------------------------------------------- /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 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /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 |

41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | colors: { 20 | border: "hsl(var(--border))", 21 | input: "hsl(var(--input))", 22 | ring: "hsl(var(--ring))", 23 | background: "hsl(var(--background))", 24 | foreground: "hsl(var(--foreground))", 25 | primary: { 26 | DEFAULT: "hsl(var(--primary))", 27 | foreground: "hsl(var(--primary-foreground))", 28 | }, 29 | secondary: { 30 | DEFAULT: "hsl(var(--secondary))", 31 | foreground: "hsl(var(--secondary-foreground))", 32 | }, 33 | destructive: { 34 | DEFAULT: "hsl(var(--destructive))", 35 | foreground: "hsl(var(--destructive-foreground))", 36 | }, 37 | muted: { 38 | DEFAULT: "hsl(var(--muted))", 39 | foreground: "hsl(var(--muted-foreground))", 40 | }, 41 | accent: { 42 | DEFAULT: "hsl(var(--accent))", 43 | foreground: "hsl(var(--accent-foreground))", 44 | }, 45 | popover: { 46 | DEFAULT: "hsl(var(--popover))", 47 | foreground: "hsl(var(--popover-foreground))", 48 | }, 49 | card: { 50 | DEFAULT: "hsl(var(--card))", 51 | foreground: "hsl(var(--card-foreground))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | keyframes: { 60 | "accordion-down": { 61 | from: { height: 0 }, 62 | to: { height: "var(--radix-accordion-content-height)" }, 63 | }, 64 | "accordion-up": { 65 | from: { height: "var(--radix-accordion-content-height)" }, 66 | to: { height: 0 }, 67 | }, 68 | }, 69 | animation: { 70 | "accordion-down": "accordion-down 0.2s ease-out", 71 | "accordion-up": "accordion-up 0.2s ease-out", 72 | }, 73 | }, 74 | }, 75 | plugins: [require("tailwindcss-animate")], 76 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Container from "@/components/ui/container"; 2 | import { Button } from "@/components/ui/button"; 3 | import { ShoppingBag } from "lucide-react"; 4 | import ProductList from "@/components/ProductList"; 5 | 6 | const products = [ 7 | { 8 | id: "1", 9 | category: "Camera", 10 | name: "Sony FX3", 11 | price: "$3,999.00", 12 | images: ["/img/products/FX3.png"], 13 | }, 14 | { 15 | id: "2", 16 | category: "Camera", 17 | name: "Sony A7S III", 18 | price: "$3,499.00", 19 | images: ["/img/products/7SIII.png"], 20 | }, 21 | { 22 | id: "3", 23 | category: "Camera", 24 | name: "Sony A7C", 25 | price: "$1,599.00", 26 | images: ["/img/products/7C.png"], 27 | }, 28 | { 29 | id: "4", 30 | category: "Camera", 31 | name: "Sony A7 IV", 32 | price: "$2,399.00", 33 | images: ["/img/products/7IV.png"], 34 | }, 35 | { 36 | id: "5", 37 | category: "Camera", 38 | name: "Sony A7R III", 39 | price: "$2,499.00", 40 | images: ["/img/products/7RIII.png"], 41 | }, 42 | { 43 | id: "6", 44 | category: "Camera", 45 | name: "Sony A7R V", 46 | price: "$3,899.00", 47 | images: ["/img/products/7RV.png"], 48 | }, 49 | { 50 | id: "7", 51 | category: "Camera", 52 | name: "Sony A6700", 53 | price: "$1,799.00", 54 | images: ["/img/products/6700.png"], 55 | }, 56 | { 57 | id: "8", 58 | category: "Camera", 59 | name: "Sony AZV-E10", 60 | price: "$699.00", 61 | images: ["/img/products/ZVE10.png"], 62 | }, 63 | ]; 64 | 65 | export default function Home() { 66 | return ( 67 | 68 |
69 |
70 |
74 |
75 |
76 | Featured Products 77 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { useTheme } from "next-themes"; 5 | import Container from "./ui/container"; 6 | import { Button } from "./ui/button"; 7 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; 8 | import { Menu, Moon, ShoppingCart, Sun } from "lucide-react"; 9 | import ProfileButton from "./ui/ProfileButton"; 10 | 11 | const Header = () => { 12 | const { theme, setTheme } = useTheme(); 13 | const routes = [ 14 | { 15 | href: "/", 16 | label: "Products", 17 | }, 18 | { 19 | href: "/", 20 | label: "Categories", 21 | }, 22 | { 23 | href: "/", 24 | label: "On Sale", 25 | }, 26 | ]; 27 | 28 | return ( 29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 49 | 50 | 51 | 52 |

STORE NAME

53 | 54 |
55 | 68 |
69 | 78 | 89 | 90 |
91 |
92 |
93 |
94 | ); 95 | }; 96 | 97 | export default Header; 98 | -------------------------------------------------------------------------------- /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 { Cross2Icon } from "@radix-ui/react-icons" 6 | import { cva, type VariantProps } from "class-variance-authority" 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 = ({ 17 | className, 18 | ...props 19 | }: SheetPrimitive.DialogPortalProps) => ( 20 | 21 | ) 22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName 23 | 24 | const SheetOverlay = React.forwardRef< 25 | React.ElementRef, 26 | React.ComponentPropsWithoutRef 27 | >(({ className, ...props }, ref) => ( 28 | 36 | )) 37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName 38 | 39 | const sheetVariants = cva( 40 | "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", 41 | { 42 | variants: { 43 | side: { 44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", 45 | bottom: 46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 47 | 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", 48 | right: 49 | "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", 50 | }, 51 | }, 52 | defaultVariants: { 53 | side: "right", 54 | }, 55 | } 56 | ) 57 | 58 | interface SheetContentProps 59 | extends React.ComponentPropsWithoutRef, 60 | VariantProps {} 61 | 62 | const SheetContent = React.forwardRef< 63 | React.ElementRef, 64 | SheetContentProps 65 | >(({ side = "right", className, children, ...props }, ref) => ( 66 | 67 | 68 | 73 | {children} 74 | 75 | 76 | Close 77 | 78 | 79 | 80 | )) 81 | SheetContent.displayName = SheetPrimitive.Content.displayName 82 | 83 | const SheetHeader = ({ 84 | className, 85 | ...props 86 | }: React.HTMLAttributes) => ( 87 |
94 | ) 95 | SheetHeader.displayName = "SheetHeader" 96 | 97 | const SheetFooter = ({ 98 | className, 99 | ...props 100 | }: React.HTMLAttributes) => ( 101 |
108 | ) 109 | SheetFooter.displayName = "SheetFooter" 110 | 111 | const SheetTitle = React.forwardRef< 112 | React.ElementRef, 113 | React.ComponentPropsWithoutRef 114 | >(({ className, ...props }, ref) => ( 115 | 120 | )) 121 | SheetTitle.displayName = SheetPrimitive.Title.displayName 122 | 123 | const SheetDescription = React.forwardRef< 124 | React.ElementRef, 125 | React.ComponentPropsWithoutRef 126 | >(({ className, ...props }, ref) => ( 127 | 132 | )) 133 | SheetDescription.displayName = SheetPrimitive.Description.displayName 134 | 135 | export { 136 | Sheet, 137 | SheetTrigger, 138 | SheetClose, 139 | SheetContent, 140 | SheetHeader, 141 | SheetFooter, 142 | SheetTitle, 143 | SheetDescription, 144 | } 145 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { 6 | CheckIcon, 7 | ChevronRightIcon, 8 | DotFilledIcon, 9 | } from "@radix-ui/react-icons" 10 | 11 | import { cn } from "@/lib/utils" 12 | 13 | const DropdownMenu = DropdownMenuPrimitive.Root 14 | 15 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 16 | 17 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 18 | 19 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 20 | 21 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 22 | 23 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 24 | 25 | const DropdownMenuSubTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef & { 28 | inset?: boolean 29 | } 30 | >(({ className, inset, children, ...props }, ref) => ( 31 | 40 | {children} 41 | 42 | 43 | )) 44 | DropdownMenuSubTrigger.displayName = 45 | DropdownMenuPrimitive.SubTrigger.displayName 46 | 47 | const DropdownMenuSubContent = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, ...props }, ref) => ( 51 | 59 | )) 60 | DropdownMenuSubContent.displayName = 61 | DropdownMenuPrimitive.SubContent.displayName 62 | 63 | const DropdownMenuContent = React.forwardRef< 64 | React.ElementRef, 65 | React.ComponentPropsWithoutRef 66 | >(({ className, sideOffset = 4, ...props }, ref) => ( 67 | 68 | 78 | 79 | )) 80 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 81 | 82 | const DropdownMenuItem = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef & { 85 | inset?: boolean 86 | } 87 | >(({ className, inset, ...props }, ref) => ( 88 | 97 | )) 98 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 99 | 100 | const DropdownMenuCheckboxItem = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, children, checked, ...props }, ref) => ( 104 | 113 | 114 | 115 | 116 | 117 | 118 | {children} 119 | 120 | )) 121 | DropdownMenuCheckboxItem.displayName = 122 | DropdownMenuPrimitive.CheckboxItem.displayName 123 | 124 | const DropdownMenuRadioItem = React.forwardRef< 125 | React.ElementRef, 126 | React.ComponentPropsWithoutRef 127 | >(({ className, children, ...props }, ref) => ( 128 | 136 | 137 | 138 | 139 | 140 | 141 | {children} 142 | 143 | )) 144 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 145 | 146 | const DropdownMenuLabel = React.forwardRef< 147 | React.ElementRef, 148 | React.ComponentPropsWithoutRef & { 149 | inset?: boolean 150 | } 151 | >(({ className, inset, ...props }, ref) => ( 152 | 161 | )) 162 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 163 | 164 | const DropdownMenuSeparator = React.forwardRef< 165 | React.ElementRef, 166 | React.ComponentPropsWithoutRef 167 | >(({ className, ...props }, ref) => ( 168 | 173 | )) 174 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 175 | 176 | const DropdownMenuShortcut = ({ 177 | className, 178 | ...props 179 | }: React.HTMLAttributes) => { 180 | return ( 181 | 185 | ) 186 | } 187 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 188 | 189 | export { 190 | DropdownMenu, 191 | DropdownMenuTrigger, 192 | DropdownMenuContent, 193 | DropdownMenuItem, 194 | DropdownMenuCheckboxItem, 195 | DropdownMenuRadioItem, 196 | DropdownMenuLabel, 197 | DropdownMenuSeparator, 198 | DropdownMenuShortcut, 199 | DropdownMenuGroup, 200 | DropdownMenuPortal, 201 | DropdownMenuSub, 202 | DropdownMenuSubContent, 203 | DropdownMenuSubTrigger, 204 | DropdownMenuRadioGroup, 205 | } 206 | --------------------------------------------------------------------------------