Created
April 18, 2024 08:58
-
-
Save ajayvignesh01/ba5a39f79561ffbb76bf941d6a45d068 to your computer and use it in GitHub Desktop.
Features Component 2 - shadcn/ui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use client' | |
// https://ui.shadcn.com/docs/components/accordion | |
import { Accordion, AccordionContent, AccordionItem } from '@/components/ui/accordion' | |
// https://ui.shadcn.com/docs/components/card | |
import { Card, CardContent } from '@/components/ui/card' | |
// https://ui.shadcn.com/docs/components/carousel | |
import { Carousel, CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel' | |
// https://ui.shadcn.com/docs/components/tabs | |
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' | |
// https://github.com/shadcn-ui/ui/blob/bf0c8b596bd7fb32daed989cab318430fd4c8919/apps/www/hooks/use-media-query.tsx#L4 | |
import { useMediaQuery } from '@/lib/hooks/use-media-query' | |
// https://github.com/shadcn-ui/ui/blob/bf0c8b596bd7fb32daed989cab318430fd4c8919/apps/www/lib/utils.ts | |
import { cn } from '@/lib/utils' | |
// https://lucide.dev/icons/ | |
import { BotIcon, BrainCircuitIcon, ScanSearchIcon } from 'lucide-react' | |
import React from 'react' | |
const tabs = [ | |
{ | |
id: '0', | |
icon: BotIcon, | |
name: 'Feature 1', | |
title: 'Feature title', | |
description: 'Feature description.' | |
}, | |
{ | |
id: '1', | |
icon: ScanSearchIcon, | |
name: 'Feature 2', | |
title: 'Feature 2 title', | |
description: 'Feature 2 description.' | |
}, | |
{ | |
id: '2', | |
icon: BrainCircuitIcon, | |
name: 'Feature 3', | |
title: 'Feature 3 title', | |
description: 'Feature 3 description.' | |
} | |
] | |
export function Features({ className }: { className?: string }) { | |
const isDesktop = useMediaQuery('(min-width: 640px)') | |
const [api, setApi] = React.useState<CarouselApi>() | |
const [tab, setTab] = React.useState<string>('0') | |
React.useEffect(() => { | |
if (!api) return | |
const sync = () => { | |
const currentCarousel = api.selectedScrollSnap() | |
setTab(currentCarousel.toString()) | |
} | |
api.on('select', sync) | |
return () => { | |
api.off('select', sync) | |
} | |
}, [api]) | |
return ( | |
<section | |
id='features' | |
className='rounded-3xl border-b border-t bg-[#f9f9f9] py-20 dark:bg-[#0f0f0f]' | |
> | |
<div className={cn('container flex flex-col items-center justify-center', className)}> | |
<div className='pb-20 md:mx-auto md:text-center'> | |
<h2 className='font-cal text-3xl sm:text-4xl md:text-5xl'>{'Product Features'}</h2> | |
<p className='pt-6 text-lg tracking-tight text-muted-foreground'> | |
What does this website even do? Can it cook for me, how about surf? | |
</p> | |
</div> | |
<Tabs | |
onValueChange={setTab} | |
value={tab} | |
defaultValue='0' | |
orientation={isDesktop ? 'horizontal' : 'vertical'} | |
className='w-full' | |
> | |
<TabsList className='grid h-fit w-full items-start justify-normal rounded-b-none sm:grid-cols-3'> | |
{tabs.map((TabElement, index) => ( | |
<TabsTrigger | |
key={TabElement.id} | |
onFocus={() => { | |
api?.scrollTo(index) | |
}} | |
className={cn( | |
'flex h-full basis-1/3 flex-col items-start justify-normal whitespace-normal p-3', // base | |
'data-[state=inactive]:hover:bg-black/5 dark:data-[state=inactive]:hover:bg-white/5' // hover | |
)} | |
value={TabElement.id} | |
> | |
<div className='flex flex-row items-center space-x-2'> | |
<TabElement.icon className='size-6.5 rounded-md' /> | |
<p className='text-sm lg:text-base'>{TabElement.name}</p> | |
</div> | |
{/* sm:flex */} | |
<div className='hidden flex-col space-y-4 pt-2 text-left sm:flex'> | |
<p className='font-cal text-base lg:text-lg'>{TabElement.title}</p> | |
<p>{TabElement.description}</p> | |
</div> | |
{/* sm:hidden */} | |
<Accordion value={tab} type='single' collapsible className='sm:hidden'> | |
<AccordionItem value={TabElement.id} className='border-none'> | |
<AccordionContent className='flex flex-col space-y-4 pb-0 pt-2 text-left'> | |
<p className='font-cal text-base lg:text-lg'>{TabElement.title}</p> | |
<p>{TabElement.description}</p> | |
</AccordionContent> | |
</AccordionItem> | |
</Accordion> | |
</TabsTrigger> | |
))} | |
</TabsList> | |
</Tabs> | |
<div className='w-full rounded-md rounded-t-none bg-secondary pb-1 md:py-6 lg:py-12'> | |
<Carousel setApi={setApi} className='w-full'> | |
<CarouselContent className='ml-0'> | |
{Array.from({ length: 3 }).map((_, index) => ( | |
<CarouselItem | |
key={index} | |
className={cn( | |
'h-[32rem] px-1', // base | |
'sm:basis-11/12', // sm | |
'md:ml-4 md:basis-10/12 md:px-2', // md | |
'lg:ml-8 lg:basis-9/12 lg:px-4' // lg | |
)} | |
> | |
<Card className='flex h-full flex-col rounded-md'> | |
<CardContent className='flex h-full items-center justify-center p-6'> | |
<span className='text-4xl font-semibold'>{index + 1}</span> | |
</CardContent> | |
</Card> | |
</CarouselItem> | |
))} | |
</CarouselContent> | |
</Carousel> | |
</div> | |
</div> | |
</section> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment