Skip to content

Instantly share code, notes, and snippets.

@ajayvignesh01
Created April 18, 2024 08:58
Show Gist options
  • Save ajayvignesh01/ba5a39f79561ffbb76bf941d6a45d068 to your computer and use it in GitHub Desktop.
Save ajayvignesh01/ba5a39f79561ffbb76bf941d6a45d068 to your computer and use it in GitHub Desktop.
Features Component 2 - shadcn/ui
'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