This commit is contained in:
2024-10-06 08:57:55 +05:30
parent 080ce9ee57
commit 990e7ee75b
48 changed files with 1929 additions and 185 deletions

View File

@ -15,7 +15,7 @@
}
.pillButton:hover {
@apply shadow-lg transform duration-300 translate-x-0 opacity-100 transition-transform ease-in-out;
@apply shadow-lg transform duration-300 transition-transform ease-in-out;
}

View File

@ -1,14 +1,28 @@
import React from 'react';
import styles from './PillButton.module.css';
type PillButtonProps = {
text: string; // text prop is now required
className?: string;
// Additional props if needed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
onClick?: () => void; // onClick is optional
};
export function PillButton({ text, onClick }: PillButtonProps) {
export function PillButton({
text,
className = '',
onClick,
...props
}: PillButtonProps) {
return (
<button onClick={onClick} className={styles.pillButton}>
<button
onClick={onClick}
className={`${styles.pillButton} ${className}`}
{...props}
>
{text}
<span className={styles.arrow}></span>
</button>

View File

@ -1,8 +1,9 @@
// src/components/AnimatedImage.tsx
'use client';
import React from 'react';
import useInView from '@/hooks/useInView';
import Image, { StaticImageData } from 'next/image';
import React from 'react';
import useInView from '@/hooks/useInView';
interface AnimatedImageProps {
src: string | StaticImageData;

View File

@ -1,6 +1,6 @@
'use client';
import React from 'react';
import { ReactNode } from 'react';
import React, { ReactNode } from 'react';
import useInView from '@/hooks/useInView';
interface AnimatedLiProps {

View File

@ -1,5 +1,6 @@
'use client';
import { ReactNode } from 'react';
import useInView from '@/hooks/useInView';
interface AnimatedTextProps {

View File

@ -0,0 +1,121 @@
'use client';
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
export function AuthPageComponent() {
const [activeTab, setActiveTab] = useState('login');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Handle form submission here
console.log('Form submitted:', activeTab);
};
return (
<div className='min-h-screen flex items-center justify-center bg-gray-100 px-4 py-12 sm:px-6 lg:px-8'>
<Card className='w-full max-w-md'>
<CardHeader className='space-y-1'>
<CardTitle className='text-2xl font-bold'>
Welcome to the Mozimo Store
</CardTitle>
<CardDescription>
Login or create an account to start shopping
</CardDescription>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className='grid w-full grid-cols-2'>
<TabsTrigger value='login'>Login</TabsTrigger>
<TabsTrigger value='register'>Register</TabsTrigger>
</TabsList>
<TabsContent value='login'>
<form onSubmit={handleSubmit} className='space-y-4'>
<div className='space-y-2'>
<Label htmlFor='login-email'>Email</Label>
<Input
id='login-email'
type='email'
placeholder='m@example.com'
required
/>
</div>
<div className='space-y-2'>
<Label htmlFor='login-password'>Password</Label>
<Input id='login-password' type='password' required />
</div>
<Button type='submit' className='w-full'>
Login
</Button>
</form>
</TabsContent>
<TabsContent value='register'>
<form onSubmit={handleSubmit} className='space-y-4'>
<div className='space-y-2'>
<Label htmlFor='register-name'>Name</Label>
<Input
id='register-name'
type='text'
placeholder='John Doe'
required
/>
</div>
<div className='space-y-2'>
<Label htmlFor='register-email'>Email</Label>
<Input
id='register-email'
type='email'
placeholder='m@example.com'
required
/>
</div>
<div className='space-y-2'>
<Label htmlFor='register-password'>Password</Label>
<Input id='register-password' type='password' required />
</div>
<Button type='submit' className='w-full'>
Register
</Button>
</form>
</TabsContent>
</Tabs>
</CardContent>
<CardFooter className='flex flex-wrap items-center justify-between gap-2'>
<div className='text-sm text-muted-foreground'>
<span className='mr-1 hidden sm:inline-block'>
{activeTab === 'login'
? "Don't have an account?"
: 'Already have an account?'}
</span>
<Button
variant='link'
className='p-0'
onClick={() =>
setActiveTab(activeTab === 'login' ? 'register' : 'login')
}
>
{activeTab === 'login' ? 'Sign up' : 'Log in'}
</Button>
</div>
{activeTab === 'login' && (
<Button variant='link' className='p-0'>
Forgot password?
</Button>
)}
</CardFooter>
</Card>
</div>
);
}

View File

@ -1,8 +1,9 @@
'use client';
import { useState } from 'react';
import Image from 'next/image';
import styles from './category-slider.module.css';
import { useState } from 'react';
import { AnimatedLi } from './animated-link';
import styles from './category-slider.module.css';
const categories = [
{ name: 'Bars', image: '/images/categories/bars.jpg' },
@ -29,7 +30,7 @@ export function ChocolateCategories() {
startClass={startClass}
finishClass={finishClass}
>
<img
<Image
className={styles.productImage}
alt={category.name}
src={category.image}

View File

@ -1,12 +1,13 @@
import { FaMapLocationDot } from 'react-icons/fa6';
import Image from 'next/image';
import {
FaPhoneAlt,
FaFacebook,
FaInstagram,
FaYoutube,
FaLinkedin,
FaPhoneAlt,
FaYoutube,
} from 'react-icons/fa';
import Image from 'next/image';
import { FaMapLocationDot } from 'react-icons/fa6';
import logoPic from '/public/images/logo-puce-red.svg';
export function Footer() {

View File

@ -1,7 +1,9 @@
'use client';
import { InstagramPost, fetchInstagramPosts } from '@/lib/instagram';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { fetchInstagramPosts, InstagramPost } from '@/lib/instagram';
export function InstagramFeed() {
const [posts, setPosts] = useState<InstagramPost[]>([]);
@ -19,7 +21,7 @@ export function InstagramFeed() {
{posts.map((post) => (
<div key={post.id} className='flex flex-col items-center'>
<a href={post.permalink} target='_blank' rel='noopener noreferrer'>
<img
<Image
src={post.media_url}
alt={post.caption}
className='rounded-lg shadow-lg'

View File

@ -1,7 +1,8 @@
// components/Layout.js
import { Footer } from './footer';
import { ReactNode } from 'react'; // Import ReactNode
import { Footer } from './footer';
interface LayoutProps {
children: ReactNode; // Define the type of children prop
}

View File

@ -11,8 +11,8 @@
z-index: 1000;
background: hsl(0 0% 100% / .2);
backdrop-filter: blur(1rem);
font-size: 2rem;
line-height: 2.8rem;
font-size: 1.25rem;
line-height: 1.75rem;
}
@ -28,9 +28,9 @@
.mobileNavToggle {
position: absolute;
display: block;
width: 3.2rem;
top: 3.2rem;
left: 3.2rem;
width: 2rem;
top: 2rem;
left: 2rem;
aspect-ratio: 1;
z-index: 9999;
background-image: url('/icons/menu.svg');
@ -50,14 +50,14 @@
.navBar {
right: 0;
flex-direction: row;
height: 8rem;
border-radius:0 0 0.8rem 0.8rem;
height: 5rem;
border-radius:0 0 0.5rem 0.5rem;
}
}
.navBar > div {
display: flex;
align-items: center;
gap: var(--gap, 3.2rem);
gap: var(--gap, 2rem);
}
/* .navBarInitial {
@ -73,10 +73,6 @@
backdrop-filter: blur(10px); /* Adds a blur effect */
}
.cls-1, .cls-2 {
fill: #ffffff;
}
/* Transparent / almost transparent to white
.navBarInitial {
@apply bg-gradient-to-b from-black/50 to-black/30 text-white absolute rounded-lg;

View File

@ -1,13 +1,15 @@
'use client';
import React, { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import React, { useEffect, useRef, useState } from 'react';
import { FiSearch, FiUser } from 'react-icons/fi';
import { HiShoppingBag } from 'react-icons/hi2';
import styles from './navbar.module.css';
import Image from 'next/image';
// import logoWhite from '/public/images/logo-white.svg';
import logoBlack from '/public/images/logo-black.svg';
import styles from './navbar.module.css';
// components/Navbar.tsx
export function Navbar() {
const prevScrollY = useRef(0);
@ -94,9 +96,9 @@ export function Navbar() {
<button aria-label='Profile'>
<FiUser size={20} />
</button>
<button aria-label='Shopping Cart'>
<a aria-label='Shopping Cart' href='/products'>
<HiShoppingBag size={20} />
</button>
</a>
</div>
</nav>
</div>

View File

@ -0,0 +1,7 @@
.ourCollection {
@apply w-full h-auto object-cover hover:scale-110;
transition-property: transform;
transition-duration: 1400ms;
transition-timing-function: linear;
}

View File

@ -1,5 +1,7 @@
import Image from 'next/image';
import styles from './our-collection.module.css';
const collections = [
{
title: 'Bestsellers',
@ -26,7 +28,7 @@ export function Collections() {
alt={collection.title}
width={500}
height={300}
className='w-full h-auto object-cover transition transform hover:scale-110 duration-[1400ms] linear'
className={styles.ourCollection}
/>
</div>
<div className='p-4'>

View File

@ -0,0 +1,162 @@
'use client';
import { SearchIcon, SlidersHorizontal } from 'lucide-react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Slider } from '@/components/ui/slider';
// Mock product data
const products = [
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999.99 },
{ id: 2, name: 'Smartphone', category: 'Electronics', price: 699.99 },
{ id: 3, name: 'Headphones', category: 'Electronics', price: 199.99 },
{ id: 4, name: 'T-shirt', category: 'Clothing', price: 24.99 },
{ id: 5, name: 'Jeans', category: 'Clothing', price: 49.99 },
{ id: 6, name: 'Sneakers', category: 'Footwear', price: 89.99 },
{ id: 7, name: 'Watch', category: 'Accessories', price: 149.99 },
{ id: 8, name: 'Backpack', category: 'Accessories', price: 79.99 },
];
const categories = ['Electronics', 'Clothing', 'Footwear', 'Accessories'];
export function ProductListComponent() {
const [priceRange, setPriceRange] = useState([0, 1000]);
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [sortBy, setSortBy] = useState('name');
const handleCategoryChange = (category: string) => {
setSelectedCategories((prev) =>
prev.includes(category)
? prev.filter((c) => c !== category)
: [...prev, category],
);
};
const filteredProducts = products
.filter(
(product) =>
(selectedCategories.length === 0 ||
selectedCategories.includes(product.category)) &&
product.price >= priceRange[0] &&
product.price <= priceRange[1] &&
product.name.toLowerCase().includes(searchQuery.toLowerCase()),
)
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
return (
<div className='container mx-auto px-4 py-8'>
<div className='flex flex-col md:flex-row gap-8'>
{/* Sidebar with filters */}
<aside className='w-full md:w-1/4 space-y-6'>
<Card>
<CardHeader>
<CardTitle>Filters</CardTitle>
</CardHeader>
<CardContent className='space-y-6'>
<div>
<h3 className='text-lg font-semibold mb-2'>Categories</h3>
{categories.map((category) => (
<div key={category} className='flex items-center space-x-2'>
<Checkbox
id={category}
checked={selectedCategories.includes(category)}
onCheckedChange={() => handleCategoryChange(category)}
/>
<label
htmlFor={category}
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
>
{category}
</label>
</div>
))}
</div>
<div>
<h3 className='text-lg font-semibold mb-2'>Price Range</h3>
<Slider
min={0}
max={1000}
step={10}
value={priceRange}
onValueChange={setPriceRange}
/>
<div className='flex justify-between mt-2'>
<span>${priceRange[0]}</span>
<span>${priceRange[1]}</span>
</div>
</div>
</CardContent>
</Card>
</aside>
{/* Main content */}
<main className='w-full md:w-3/4'>
<div className='flex flex-col sm:flex-row justify-between items-center mb-6 gap-4'>
<div className='relative w-full sm:w-auto'>
<SearchIcon className='absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400' />
<Input
type='search'
placeholder='Search products...'
className='pl-8'
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<div className='flex items-center space-x-2 w-full sm:w-auto'>
<Select value={sortBy} onValueChange={setSortBy}>
<SelectTrigger className='w-full sm:w-[180px]'>
<SelectValue placeholder='Sort by' />
</SelectTrigger>
<SelectContent>
<SelectItem value='name'>Name</SelectItem>
<SelectItem value='price'>Price</SelectItem>
</SelectContent>
</Select>
<Button variant='outline' size='icon'>
<SlidersHorizontal className='h-4 w-4' />
</Button>
</div>
</div>
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'>
{filteredProducts.map((product) => (
<Card key={product.id}>
<CardHeader>
<CardTitle>{product.name}</CardTitle>
</CardHeader>
<CardContent>
<p>Category: {product.category}</p>
<p className='font-bold mt-2'>${product.price.toFixed(2)}</p>
</CardContent>
<CardFooter>
<Button className='w-full'>Add to Cart</Button>
</CardFooter>
</Card>
))}
</div>
</main>
</div>
</div>
);
}

View File

@ -0,0 +1,216 @@
'use client';
import { ChevronLeft, ChevronRight, ShoppingCart, Star } from 'lucide-react';
import Image from 'next/image';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
interface Variant {
id: string;
name: string;
price: number;
}
interface Product {
name: string;
description: string;
variants: Variant[];
images: string[];
}
export function ProductPageComponent() {
const product: Product = {
name: 'Luxury Dark Chocolate Bar',
description:
'Indulge in the rich, intense flavor of our premium dark chocolate bar. Made with carefully selected cocoa beans, this bar offers a perfect balance of bitterness and sweetness that will satisfy even the most discerning chocolate connoisseurs.',
variants: [
{ id: '70', name: '70% Cocoa', price: 12.99 },
{ id: '85', name: '85% Cocoa', price: 14.99 },
{ id: '100', name: '100% Cocoa', price: 16.99 },
],
images: [
'/placeholder.svg?height=400&width=400',
'/placeholder.svg?height=400&width=400',
'/placeholder.svg?height=400&width=400',
],
};
const [quantity, setQuantity] = useState(1);
const [currentImage, setCurrentImage] = useState(0);
const [selectedVariant, setSelectedVariant] = useState<Variant>(
product.variants[0],
);
const nextImage = () => {
setCurrentImage((currentImage + 1) % product.images.length);
};
const prevImage = () => {
setCurrentImage(
(currentImage - 1 + product.images.length) % product.images.length,
);
};
return (
<div className='container mx-auto px-4 py-8'>
<div className='grid md:grid-cols-2 gap-8'>
<div className='relative'>
<Image
src={product.images[currentImage]}
alt={`${product.name} image ${currentImage + 1}`}
className='w-full h-auto rounded-lg shadow-lg'
/>
<Button
variant='outline'
size='icon'
className='absolute left-2 top-1/2 transform -translate-y-1/2'
onClick={prevImage}
>
<ChevronLeft className='h-4 w-4' />
<span className='sr-only'>Previous image</span>
</Button>
<Button
variant='outline'
size='icon'
className='absolute right-2 top-1/2 transform -translate-y-1/2'
onClick={nextImage}
>
<ChevronRight className='h-4 w-4' />
<span className='sr-only'>Next image</span>
</Button>
<div className='flex justify-center mt-4 space-x-2'>
{product.images.map((_, index) => (
<div
key={index}
className={`w-3 h-3 rounded-full ${
index === currentImage ? 'bg-primary' : 'bg-gray-300'
}`}
/>
))}
</div>
</div>
<div>
<h1 className='text-3xl font-bold mb-4'>{product.name}</h1>
<div className='flex items-center mb-4'>
<div className='flex text-yellow-400'>
{[...Array(5)].map((_, i) => (
<Star key={i} className='w-5 h-5 fill-current' />
))}
</div>
<span className='ml-2 text-sm text-gray-600'>(128 reviews)</span>
</div>
<p className='text-2xl font-bold mb-4'>
${selectedVariant.price.toFixed(2)}
</p>
<p className='text-gray-600 mb-6'>{product.description}</p>
<div className='mb-6'>
<h3 className='text-lg font-semibold mb-2'>Choose Variant:</h3>
<RadioGroup
defaultValue={selectedVariant.id}
onValueChange={(value) =>
setSelectedVariant(
product.variants.find((v) => v.id === value) ||
product.variants[0],
)
}
>
{product.variants.map((variant) => (
<div key={variant.id} className='flex items-center space-x-2'>
<RadioGroupItem value={variant.id} id={variant.id} />
<Label htmlFor={variant.id}>
{variant.name} - ${variant.price.toFixed(2)}
</Label>
</div>
))}
</RadioGroup>
</div>
<div className='flex items-center mb-6'>
<Button
variant='outline'
size='sm'
onClick={() => setQuantity(Math.max(1, quantity - 1))}
>
-
</Button>
<span className='mx-4'>{quantity}</span>
<Button
variant='outline'
size='sm'
onClick={() => setQuantity(quantity + 1)}
>
+
</Button>
</div>
<Button className='w-full md:w-auto'>
<ShoppingCart className='mr-2 h-4 w-4' /> Add to Cart
</Button>
</div>
</div>
<div className='mt-12'>
<h2 className='text-2xl font-bold mb-4'>Product Details</h2>
<ul className='list-disc pl-5 space-y-2 text-gray-600'>
<li>Single-origin cocoa beans from Ecuador</li>
<li>Fair trade certified</li>
<li>No artificial flavors or preservatives</li>
<li>100g (3.5oz) bar</li>
<li>Available in different cocoa percentages</li>
</ul>
</div>
<div className='mt-12'>
<h2 className='text-2xl font-bold mb-4'>Customer Reviews</h2>
<div className='space-y-4'>
{[...Array(3)].map((_, i) => (
<Card key={i}>
<CardContent className='p-4'>
<div className='flex items-center mb-2'>
<div className='flex text-yellow-400'>
{[...Array(5)].map((_, j) => (
<Star key={j} className='w-4 h-4 fill-current' />
))}
</div>
<span className='ml-2 text-sm font-medium'>John Doe</span>
</div>
<p className='text-gray-600'>
This chocolate is absolutely divine! The rich flavor and
smooth texture make it a perfect treat for any chocolate
lover. I especially love the 85% cocoa variant for its intense
flavor.
</p>
</CardContent>
</Card>
))}
</div>
</div>
<div className='mt-12'>
<h2 className='text-2xl font-bold mb-4'>Related Products</h2>
<div className='grid grid-cols-1 md:grid-cols-3 gap-6'>
{[...Array(3)].map((_, i) => (
<Card key={i}>
<CardContent className='p-4'>
<Image
src='/placeholder.svg?height=200&width=200'
alt={`Related product ${i + 1}`}
className='w-full h-48 object-cover mb-4 rounded'
/>
<h3 className='font-bold mb-2'>Milk Chocolate Truffles</h3>
<p className='text-gray-600 mb-2'>
Smooth, creamy milk chocolate truffles
</p>
<p className='font-bold'>$9.99</p>
</CardContent>
</Card>
))}
</div>
</div>
</div>
);
}

View File

@ -1,16 +1,15 @@
'use client';
// import Swiper core and required modules
import { Autoplay, Pagination, EffectFade } from 'swiper/modules';
import Image from 'next/image';
import { Swiper, SwiperSlide } from 'swiper/react';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/effect-fade';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import Image from 'next/image';
import { Autoplay, EffectFade, Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
const slides = [
{ name: 'Bars', image: '/images/slider/slider-01.jpg' },
{ name: 'Barks', image: '/images/slider/slider-02.jpg' },

View File

@ -0,0 +1,57 @@
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap 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',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = 'Button';
export { Button, buttonVariants };

View File

@ -0,0 +1,83 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-xl border bg-card text-card-foreground shadow',
className,
)}
{...props}
/>
));
Card.displayName = 'Card';
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
));
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
));
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';
export {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
};

View File

@ -0,0 +1,30 @@
'use client';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<CheckIcon className='h-4 w-4' />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = 'Input';
export { Input };

View File

@ -0,0 +1,26 @@
'use client';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@ -0,0 +1,44 @@
'use client';
import { CheckIcon } from '@radix-ui/react-icons';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import * as React from 'react';
import { cn } from '@/lib/utils';
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn('grid gap-2', className)}
{...props}
ref={ref}
/>
);
});
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className='flex items-center justify-center'>
<CheckIcon className='h-3.5 w-3.5 fill-primary' />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
});
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem };

View File

@ -0,0 +1,164 @@
'use client';
import {
CaretSortIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from '@radix-ui/react-icons';
import * as SelectPrimitive from '@radix-ui/react-select';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretSortIcon className='h-4 w-4 opacity-50' />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className='absolute right-2 flex h-3.5 w-3.5 items-center justify-center'>
<SelectPrimitive.ItemIndicator>
<CheckIcon className='h-4 w-4' />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View File

@ -0,0 +1,28 @@
'use client';
import * as SliderPrimitive from '@radix-ui/react-slider';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
'relative flex w-full touch-none select-none items-center',
className,
)}
{...props}
>
<SliderPrimitive.Track className='relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20'>
<SliderPrimitive.Range className='absolute h-full bg-primary' />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className='block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50' />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider };

View File

@ -0,0 +1,55 @@
'use client';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsContent, TabsList, TabsTrigger };