feat(multi): added new component variants

Added Pagination, Sheet, Sonner and Tabs component variants
This commit is contained in:
OmkarOza 2025-07-04 17:37:58 +05:30
parent 20c634e8b5
commit 71f477b1db
86 changed files with 6211 additions and 52 deletions

View file

@ -52,6 +52,7 @@
"Dribbble",
"Duarte",
"Dulce",
"Ecommerce",
"emblor",
"Español",
"firstname",

View file

@ -4,6 +4,13 @@
All notable changes to this project will be documented in this file.
## v1.0.0-beta.3 (2025-07-04)
### Added
- Added Pagination, Sheet, Sonner and Tabs component variants
- Added Animated Tabs component variants
## v1.0.0-beta.2 (2025-06-27)
### Added

View file

@ -1,6 +1,6 @@
{
"name": "shadcn-studio",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"license": "MIT",
"publishConfig": {
"access": "public"

View file

@ -2997,8 +2997,7 @@ export const components: ComponentProps[] = [
path: 'src/components/shadcn-studio/form/form-10.tsx',
target: 'components/shadcn-studio/form/form-10.tsx'
}
],
badge: 'New'
]
},
{
name: 'input-01',
@ -3558,6 +3557,142 @@ export const components: ComponentProps[] = [
}
]
},
{
name: 'pagination-01',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-01.tsx',
target: 'components/shadcn-studio/pagination/pagination-01.tsx'
}
]
},
{
name: 'pagination-02',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-02.tsx',
target: 'components/shadcn-studio/pagination/pagination-02.tsx'
}
]
},
{
name: 'pagination-03',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-03.tsx',
target: 'components/shadcn-studio/pagination/pagination-03.tsx'
}
]
},
{
name: 'pagination-04',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-04.tsx',
target: 'components/shadcn-studio/pagination/pagination-04.tsx'
}
]
},
{
name: 'pagination-05',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-05.tsx',
target: 'components/shadcn-studio/pagination/pagination-05.tsx'
}
]
},
{
name: 'pagination-06',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-06.tsx',
target: 'components/shadcn-studio/pagination/pagination-06.tsx'
}
]
},
{
name: 'pagination-07',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-07.tsx',
target: 'components/shadcn-studio/pagination/pagination-07.tsx'
}
]
},
{
name: 'pagination-08',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-08.tsx',
target: 'components/shadcn-studio/pagination/pagination-08.tsx'
}
]
},
{
name: 'pagination-09',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-09.tsx',
target: 'components/shadcn-studio/pagination/pagination-09.tsx'
}
]
},
{
name: 'pagination-10',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-10.tsx',
target: 'components/shadcn-studio/pagination/pagination-10.tsx'
}
]
},
{
name: 'pagination-11',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-11.tsx',
target: 'components/shadcn-studio/pagination/pagination-11.tsx'
}
]
},
{
name: 'pagination-12',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-12.tsx',
target: 'components/shadcn-studio/pagination/pagination-12.tsx'
}
]
},
{
name: 'pagination-13',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-13.tsx',
target: 'components/shadcn-studio/pagination/pagination-13.tsx'
}
]
},
{
name: 'pagination-14',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-14.tsx',
target: 'components/shadcn-studio/pagination/pagination-14.tsx'
}
]
},
{
name: 'pagination-15',
files: [
{
path: 'src/components/shadcn-studio/pagination/pagination-15.tsx',
target: 'components/shadcn-studio/pagination/pagination-15.tsx'
}
],
className: 'col-span-full border-e-0'
},
{
name: 'popover-01',
files: [
@ -4216,6 +4351,250 @@ export const components: ComponentProps[] = [
],
isAnimated: true
},
{
name: 'sheet-01',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-01.tsx',
target: 'components/shadcn-studio/sheet/sheet-01.tsx'
}
]
},
{
name: 'sheet-02',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-02.tsx',
target: 'components/shadcn-studio/sheet/sheet-02.tsx'
}
]
},
{
name: 'sheet-03',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-03.tsx',
target: 'components/shadcn-studio/sheet/sheet-03.tsx'
}
]
},
{
name: 'sheet-04',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-04.tsx',
target: 'components/shadcn-studio/sheet/sheet-04.tsx'
}
]
},
{
name: 'sheet-05',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-05.tsx',
target: 'components/shadcn-studio/sheet/sheet-05.tsx'
}
]
},
{
name: 'sheet-06',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-06.tsx',
target: 'components/shadcn-studio/sheet/sheet-06.tsx'
}
]
},
{
name: 'sheet-07',
files: [
{
path: 'src/components/shadcn-studio/sheet/sheet-07.tsx',
target: 'components/shadcn-studio/sheet/sheet-07.tsx'
}
],
className: 'col-span-full border-e-0'
},
{
name: 'sonner-01',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-01.tsx',
target: 'components/shadcn-studio/sonner/sonner-01.tsx'
}
]
},
{
name: 'sonner-02',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-02.tsx',
target: 'components/shadcn-studio/sonner/sonner-02.tsx'
}
]
},
{
name: 'sonner-03',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-03.tsx',
target: 'components/shadcn-studio/sonner/sonner-03.tsx'
}
]
},
{
name: 'sonner-04',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-04.tsx',
target: 'components/shadcn-studio/sonner/sonner-04.tsx'
}
]
},
{
name: 'sonner-05',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-05.tsx',
target: 'components/shadcn-studio/sonner/sonner-05.tsx'
}
]
},
{
name: 'sonner-06',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-06.tsx',
target: 'components/shadcn-studio/sonner/sonner-06.tsx'
}
]
},
{
name: 'sonner-07',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-07.tsx',
target: 'components/shadcn-studio/sonner/sonner-07.tsx'
}
]
},
{
name: 'sonner-08',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-08.tsx',
target: 'components/shadcn-studio/sonner/sonner-08.tsx'
}
]
},
{
name: 'sonner-09',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-09.tsx',
target: 'components/shadcn-studio/sonner/sonner-09.tsx'
}
]
},
{
name: 'sonner-10',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-10.tsx',
target: 'components/shadcn-studio/sonner/sonner-10.tsx'
}
]
},
{
name: 'sonner-11',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-11.tsx',
target: 'components/shadcn-studio/sonner/sonner-11.tsx'
}
]
},
{
name: 'sonner-12',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-12.tsx',
target: 'components/shadcn-studio/sonner/sonner-12.tsx'
}
]
},
{
name: 'sonner-13',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-13.tsx',
target: 'components/shadcn-studio/sonner/sonner-13.tsx'
}
]
},
{
name: 'sonner-14',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-14.tsx',
target: 'components/shadcn-studio/sonner/sonner-14.tsx'
}
]
},
{
name: 'sonner-15',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-15.tsx',
target: 'components/shadcn-studio/sonner/sonner-15.tsx'
}
]
},
{
name: 'sonner-16',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-16.tsx',
target: 'components/shadcn-studio/sonner/sonner-16.tsx'
}
]
},
{
name: 'sonner-17',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-17.tsx',
target: 'components/shadcn-studio/sonner/sonner-17.tsx'
}
]
},
{
name: 'sonner-18',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-18.tsx',
target: 'components/shadcn-studio/sonner/sonner-18.tsx'
}
]
},
{
name: 'sonner-19',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-19.tsx',
target: 'components/shadcn-studio/sonner/sonner-19.tsx'
}
]
},
{
name: 'sonner-20',
files: [
{
path: 'src/components/shadcn-studio/sonner/sonner-20.tsx',
target: 'components/shadcn-studio/sonner/sonner-20.tsx'
}
]
},
{
name: 'switch-01',
files: [
@ -4548,6 +4927,276 @@ export const components: ComponentProps[] = [
}
]
},
{
name: 'tabs-01',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-01.tsx',
target: 'components/shadcn-studio/tabs/tabs-01.tsx'
}
]
},
{
name: 'tabs-02',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-02.tsx',
target: 'components/shadcn-studio/tabs/tabs-02.tsx'
}
]
},
{
name: 'tabs-03',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-03.tsx',
target: 'components/shadcn-studio/tabs/tabs-03.tsx'
}
]
},
{
name: 'tabs-04',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-04.tsx',
target: 'components/shadcn-studio/tabs/tabs-04.tsx'
}
]
},
{
name: 'tabs-05',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-05.tsx',
target: 'components/shadcn-studio/tabs/tabs-05.tsx'
}
]
},
{
name: 'tabs-06',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-06.tsx',
target: 'components/shadcn-studio/tabs/tabs-06.tsx'
}
]
},
{
name: 'tabs-07',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-07.tsx',
target: 'components/shadcn-studio/tabs/tabs-07.tsx'
}
]
},
{
name: 'tabs-08',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-08.tsx',
target: 'components/shadcn-studio/tabs/tabs-08.tsx'
}
]
},
{
name: 'tabs-09',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-09.tsx',
target: 'components/shadcn-studio/tabs/tabs-09.tsx'
}
]
},
{
name: 'tabs-10',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-10.tsx',
target: 'components/shadcn-studio/tabs/tabs-10.tsx'
}
]
},
{
name: 'tabs-11',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-11.tsx',
target: 'components/shadcn-studio/tabs/tabs-11.tsx'
}
]
},
{
name: 'tabs-12',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-12.tsx',
target: 'components/shadcn-studio/tabs/tabs-12.tsx'
}
]
},
{
name: 'tabs-13',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-13.tsx',
target: 'components/shadcn-studio/tabs/tabs-13.tsx'
}
]
},
{
name: 'tabs-14',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-14.tsx',
target: 'components/shadcn-studio/tabs/tabs-14.tsx'
}
]
},
{
name: 'tabs-15',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-15.tsx',
target: 'components/shadcn-studio/tabs/tabs-15.tsx'
}
]
},
{
name: 'tabs-16',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-16.tsx',
target: 'components/shadcn-studio/tabs/tabs-16.tsx'
}
]
},
{
name: 'tabs-17',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-17.tsx',
target: 'components/shadcn-studio/tabs/tabs-17.tsx'
}
]
},
{
name: 'tabs-18',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-18.tsx',
target: 'components/shadcn-studio/tabs/tabs-18.tsx'
}
]
},
{
name: 'tabs-19',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-19.tsx',
target: 'components/shadcn-studio/tabs/tabs-19.tsx'
}
]
},
{
name: 'tabs-20',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-20.tsx',
target: 'components/shadcn-studio/tabs/tabs-20.tsx'
}
]
},
{
name: 'tabs-21',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-21.tsx',
target: 'components/shadcn-studio/tabs/tabs-21.tsx'
}
]
},
{
name: 'tabs-22',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-22.tsx',
target: 'components/shadcn-studio/tabs/tabs-22.tsx'
}
]
},
{
name: 'tabs-23',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-23.tsx',
target: 'components/shadcn-studio/tabs/tabs-23.tsx'
}
]
},
{
name: 'tabs-24',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-24.tsx',
target: 'components/shadcn-studio/tabs/tabs-24.tsx'
}
]
},
{
name: 'tabs-25',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-25.tsx',
target: 'components/shadcn-studio/tabs/tabs-25.tsx'
}
]
},
{
name: 'tabs-26',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-26.tsx',
target: 'components/shadcn-studio/tabs/tabs-26.tsx'
}
]
},
{
name: 'tabs-27',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-27.tsx',
target: 'components/shadcn-studio/tabs/tabs-27.tsx'
},
{
path: 'src/components/ui/motion-highlight.tsx'
},
{
path: 'src/components/ui/motion-tabs.tsx'
}
],
isAnimated: true
},
{
name: 'tabs-28',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-28.tsx',
target: 'components/shadcn-studio/tabs/tabs-28.tsx'
}
],
isAnimated: true
},
{
name: 'tabs-29',
files: [
{
path: 'src/components/shadcn-studio/tabs/tabs-29.tsx',
target: 'components/shadcn-studio/tabs/tabs-29.tsx'
}
],
isAnimated: true
},
{
name: 'textarea-01',
files: [

View file

@ -19,7 +19,7 @@ export const roadmap = [
title: 'Shadcn Figma UI Kit',
description:
'Streamline your design-to-development process with a shadcn UI kit for Figma, plus a dedicated Figma plugin.',
status: 'In Progress'
status: 'Coming Soon'
},
{
icon: Component,
@ -32,7 +32,7 @@ export const roadmap = [
title: 'Shadcn Animated Variants',
description:
'Build dynamic and captivating UI with an open-source and premium set of animated components, blocks, templates, and effects.',
status: 'Coming Soon'
status: 'In Progress'
},
{
icon: FileCode2,

View file

@ -50,10 +50,10 @@ const Form = (props: SVGAttributes<SVGElement>) => {
y2='152'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--mute-foreground)' />
<stop offset='0.75' stopColor='var(--mute-foreground)' />
<stop offset='1' stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint1_linear_5593_504'
@ -63,10 +63,10 @@ const Form = (props: SVGAttributes<SVGElement>) => {
y2='152'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--mute-foreground)' />
<stop offset='0.75' stopColor='var(--mute-foreground)' />
<stop offset='1' stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint2_linear_5593_504'
@ -76,10 +76,10 @@ const Form = (props: SVGAttributes<SVGElement>) => {
y2='14.7498'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--mute-foreground)' />
<stop offset='0.75' stopColor='var(--mute-foreground)' />
<stop offset='1' stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint3_linear_5593_504'
@ -89,10 +89,10 @@ const Form = (props: SVGAttributes<SVGElement>) => {
y2='136.35'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--mute-foreground)' />
<stop offset='0.75' stopColor='var(--mute-foreground)' />
<stop offset='1' stopColor='var(--mute-foreground)' stopOpacity='0' />
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<clipPath id='clip0_5593_504'>
<rect width='206' height='152' fill='white' />

View file

@ -0,0 +1,95 @@
// React Imports
import type { SVGAttributes } from 'react'
const Tabs = (props: SVGAttributes<SVGElement>) => {
return (
<svg width='214' height='62' viewBox='0 0 214 62' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
<g clipPath='url(#clip0_5357_51879)'>
<path d='M24 62L24 1.75834e-06' stroke='url(#paint0_linear_5357_51879)' strokeOpacity='0.4' />
<path d='M190 62L190 1.75834e-06' stroke='url(#paint1_linear_5357_51879)' strokeOpacity='0.4' />
<path d='M214 16L5.78165e-06 16' stroke='url(#paint2_linear_5357_51879)' strokeOpacity='0.4' />
<path d='M214 46L5.78165e-06 46' stroke='url(#paint3_linear_5357_51879)' strokeOpacity='0.4' />
<ellipse cx='39' cy='31' rx='15' ry='15' fill='var(--card)' />
<ellipse cx='73' cy='31' rx='15' ry='15' fill='var(--primary)' />
<ellipse cx='141' cy='31' rx='15' ry='15' fill='var(--card)' />
<ellipse cx='107' cy='31' rx='15' ry='15' fill='var(--card)' />
<ellipse cx='175' cy='31' rx='15' ry='15' fill='var(--card)' />
<path
d='M41.25 35.5L36.75 31L41.25 26.5'
stroke='var(--card-foreground)'
strokeOpacity='0.6'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M172.75 35.5L177.25 31L172.75 26.5'
stroke='var(--card-foreground)'
strokeOpacity='0.6'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
</g>
<defs>
<linearGradient
id='paint0_linear_5357_51879'
x1='24.5'
y1='3.33621e-06'
x2='24.4955'
y2='62'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint1_linear_5357_51879'
x1='190.5'
y1='3.33621e-06'
x2='190.495'
y2='62'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint2_linear_5357_51879'
x1='1.14399e-05'
y1='15.5'
x2='214'
y2='15.554'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint3_linear_5357_51879'
x1='1.14399e-05'
y1='45.5'
x2='214'
y2='45.554'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<clipPath id='clip0_5357_51879'>
<rect width='214' height='62' fill='white' />
</clipPath>
</defs>
</svg>
)
}
export default Tabs

79
src/assets/svg/Sheet.tsx Normal file
View file

@ -0,0 +1,79 @@
// React Imports
import type { SVGAttributes } from 'react'
const Sheet = (props: SVGAttributes<SVGElement>) => {
return (
<svg width='278' height='191' viewBox='0 0 278 191' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
<path d='M21 191L21 4.41074e-06' stroke='url(#paint0_linear_16511_23622)' strokeOpacity='0.4' />
<path d='M254 191L254 4.41074e-06' stroke='url(#paint1_linear_16511_23622)' strokeOpacity='0.4' />
<path d='M276 23L-1.37091e-06 23' stroke='url(#paint2_linear_16511_23622)' strokeOpacity='0.4' />
<path d='M277 167L0.999999 167' stroke='url(#paint3_linear_16511_23622)' strokeOpacity='0.4' />
<rect x='165.688' y='23' width='88.5502' height='144' rx='8' fill='var(--card)' />
<rect x='175.209' y='36.5' width='47.4371' height='8' rx='4' fill='var(--card-foreground)' fillOpacity='0.2' />
<rect x='175.209' y='54.5' width='65.5084' height='8' rx='4' fill='var(--primary)' />
<rect x='175.209' y='132.5' width='65.5084' height='22' rx='6' fill='var(--primary)' />
<rect x='31.3798' y='36.5' width='57.8087' height='44' rx='6' fill='var(--card)' />
<rect x='31.3798' y='87.9012' width='57.8087' height='66.5988' rx='6' fill='var(--card)' />
<rect x='96.2207' y='36.5' width='57.8087' height='44' rx='6' fill='var(--card)' />
<rect x='96.2207' y='87.9012' width='57.8087' height='66.5988' rx='6' fill='var(--card)' />
<rect x='175.209' y='72.5' width='24.095' height='8' rx='4' fill='var(--card-foreground)' fillOpacity='0.2' />
<rect x='175.209' y='90.5' width='43.6722' height='8' rx='4' fill='var(--card-foreground)' fillOpacity='0.2' />
<defs>
<linearGradient
id='paint0_linear_16511_23622'
x1='21.5'
y1='1.02489e-05'
x2='21.457'
y2='191'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint1_linear_16511_23622'
x1='254.5'
y1='1.02489e-05'
x2='254.457'
y2='191'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint2_linear_16511_23622'
x1='1.47542e-05'
y1='22.5'
x2='276'
y2='22.5899'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint3_linear_16511_23622'
x1='1.00001'
y1='166.5'
x2='277'
y2='166.59'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
</defs>
</svg>
)
}
export default Sheet

92
src/assets/svg/Sonner.tsx Normal file
View file

@ -0,0 +1,92 @@
// React Imports
import type { SVGAttributes } from 'react'
const Sonner = (props: SVGAttributes<SVGElement>) => {
return (
<svg width='198' height='74' viewBox='0 0 198 74' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
<g clipPath='url(#clip0_5593_579)'>
<rect x='18' y='16.0002' width='161' height='42' rx='8' fill='var(--card)' />
<path d='M18 74L18 -1.20699e-06' stroke='url(#paint0_linear_5593_579)' strokeOpacity='0.4' />
<path d='M179 74L179 -1.20699e-06' stroke='url(#paint1_linear_5593_579)' strokeOpacity='0.4' />
<rect x='30.5' y='33.0002' width='90' height='8' rx='4' fill='var(--primary)' />
<path
d='M164 33L156 41'
stroke='var(--card-foreground)'
strokeOpacity='0.6'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M156 33L164 41'
stroke='var(--card-foreground)'
strokeOpacity='0.6'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path d='M198 16L-2.14577e-06 16' stroke='url(#paint2_linear_5593_579)' strokeOpacity='0.4' />
<path d='M198 58L-2.14577e-06 58' stroke='url(#paint3_linear_5593_579)' strokeOpacity='0.4' />
</g>
<defs>
<linearGradient
id='paint0_linear_5593_579'
x1='18.5'
y1='3.96895e-06'
x2='18.4935'
y2='74'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint1_linear_5593_579'
x1='179.5'
y1='3.96895e-06'
x2='179.494'
y2='74'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint2_linear_5593_579'
x1='1.05845e-05'
y1='15.5'
x2='198'
y2='15.5463'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint3_linear_5593_579'
x1='1.05845e-05'
y1='57.5'
x2='198'
y2='57.5463'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<clipPath id='clip0_5593_579'>
<rect width='198' height='74' fill='white' />
</clipPath>
</defs>
</svg>
)
}
export default Sonner

80
src/assets/svg/Tabs.tsx Normal file
View file

@ -0,0 +1,80 @@
// React Imports
import type { SVGAttributes } from 'react'
const Tabs = (props: SVGAttributes<SVGElement>) => {
return (
<svg width='220' height='57' viewBox='0 0 220 57' fill='none' xmlns='http://www.w3.org/2000/svg' {...props}>
<g clipPath='url(#clip0_5372_59611)'>
<path d='M22 57L22 -1.83284e-06' stroke='url(#paint0_linear_5372_59611)' strokeOpacity='0.4' />
<path d='M198 57L198 -1.83284e-06' stroke='url(#paint1_linear_5372_59611)' strokeOpacity='0.4' />
<path d='M220 14L-2.38419e-06 14' stroke='url(#paint2_linear_5372_59611)' strokeOpacity='0.4' />
<path d='M220 42L-2.38419e-06 42' stroke='url(#paint3_linear_5372_59611)' strokeOpacity='0.4' />
<rect x='142' y='14' width='56' height='28' rx='6' fill='var(--card)' />
<rect x='151' y='25' width='38' height='6' rx='3' fill='var(--card-foreground)' fillOpacity='0.6' />
<rect x='22' y='14' width='56' height='28' rx='6' fill='var(--card)' />
<rect x='82' y='14' width='56' height='28' rx='6' fill='var(--primary)' />
<rect x='91' y='25' width='38' height='6' rx='3' fill='var(--primary-foreground)' />
<rect x='31' y='25' width='38' height='6' rx='3' fill='var(--card-foreground)' fillOpacity='0.6' />
</g>
<defs>
<linearGradient
id='paint0_linear_5372_59611'
x1='22.5'
y1='3.08924e-06'
x2='22.4962'
y2='57'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint1_linear_5372_59611'
x1='198.5'
y1='3.08924e-06'
x2='198.496'
y2='57'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint2_linear_5372_59611'
x1='1.1726e-05'
y1='13.5'
x2='220'
y2='13.5571'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<linearGradient
id='paint3_linear_5372_59611'
x1='1.1726e-05'
y1='41.5'
x2='220'
y2='41.5571'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='var(--muted-foreground)' stopOpacity='0' />
<stop offset='0.245' stopColor='var(--muted-foreground)' />
<stop offset='0.75' stopColor='var(--muted-foreground)' />
<stop offset='1' stopColor='var(--muted-foreground)' stopOpacity='0' />
</linearGradient>
<clipPath id='clip0_5372_59611'>
<rect width='220' height='57' fill='white' />
</clipPath>
</defs>
</svg>
)
}
export default Tabs

View file

@ -19,7 +19,7 @@ const ComponentCard = ({
return (
<div
className={cn(
'group/item relative flex min-h-[210px] items-center justify-center px-8 py-12 group-first/grid:border-t-0',
'group/item relative flex min-h-[210px] items-center justify-center px-8 py-12 group-first/grid:border-t-0 max-sm:px-4',
className
)}
data-slot={componentName}

View file

@ -36,7 +36,7 @@ const Header = ({ toggle }: { toggle: ReactNode }) => {
</Link>
<DropdownMenu>
<DropdownMenuTrigger className='text-muted-foreground hover:text-foreground cursor-pointer text-sm font-medium max-sm:hidden'>
v1.0.0-beta.2
v1.0.0-beta.3
</DropdownMenuTrigger>
<DropdownMenuContent align='start'>
<DropdownMenuGroup>

View file

@ -0,0 +1,36 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const PaginationDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' />
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationDemo

View file

@ -0,0 +1,35 @@
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, PaginationContent, PaginationItem, PaginationLink } from '@/components/ui/pagination'
const PaginationWithIconDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to previous page' size='icon'>
<ChevronLeftIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to next page' size='icon'>
<ChevronRightIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithIconDemo

View file

@ -0,0 +1,49 @@
import { buttonVariants } from '@/components/ui/button'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
import { cn } from '@/lib/utils'
const PaginationWithPrimaryButtonDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' />
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink
href='#'
isActive
className={cn(
buttonVariants({
variant: 'default',
size: 'icon'
}),
'hover:!text-primary-foreground dark:bg-primary dark:text-primary-foreground dark:hover:text-primary-foreground dark:hover:bg-primary/90 !shadow-none dark:border-transparent'
)}
>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithPrimaryButtonDemo

View file

@ -0,0 +1,49 @@
import { buttonVariants } from '@/components/ui/button'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
import { cn } from '@/lib/utils'
const PaginationWithSecondaryButtonDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' />
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink
href='#'
isActive
className={cn(
'hover:!text-secondary-foreground !border-none !shadow-none',
buttonVariants({
variant: 'secondary',
size: 'icon'
})
)}
>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithSecondaryButtonDemo

View file

@ -0,0 +1,54 @@
import { buttonVariants } from '@/components/ui/button'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
import { cn } from '@/lib/utils'
const pages = [1, 2, 3]
const BorderedPaginationDemo = () => {
return (
<Pagination>
<PaginationContent className='gap-0 divide-x overflow-hidden rounded-lg border'>
<PaginationItem>
<PaginationPrevious href='#' className='rounded-none' />
</PaginationItem>
{pages.map(page => {
const isActive = page === 2
return (
<PaginationItem key={page}>
<PaginationLink
href={`#${page}`}
className={cn(
{
[buttonVariants({
variant: 'default',
className:
'hover:!text-primary-foreground dark:bg-primary dark:text-primary-foreground dark:hover:text-primary-foreground dark:hover:bg-primary/90 dark:border-transparent'
})]: isActive
},
'rounded-none border-none'
)}
isActive={isActive}
>
{page}
</PaginationLink>
</PaginationItem>
)
})}
<PaginationItem>
<PaginationNext href='#' className='rounded-none' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default BorderedPaginationDemo

View file

@ -0,0 +1,34 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const pages = [1, 2, 3]
const PaginationWithRoundedButton = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' className='rounded-full' />
</PaginationItem>
{pages.map(page => (
<PaginationItem key={page}>
<PaginationLink href={`#${page}`} isActive={page === 2} className='rounded-full'>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext href='#' className='rounded-full' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithRoundedButton

View file

@ -0,0 +1,43 @@
import { ChevronFirstIcon, ChevronLastIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, PaginationContent, PaginationItem, PaginationLink } from '@/components/ui/pagination'
const pages = [1, 2, 3]
const PaginationWithFirstAndLastPageButtonNavigation = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to first page' size='icon' className='rounded-full'>
<ChevronFirstIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to previous page' size='icon' className='rounded-full'>
<ChevronLeftIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
{pages.map(page => (
<PaginationItem key={page}>
<PaginationLink href={`#${page}`} isActive={page === 2} className='rounded-full'>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationLink href='#' aria-label='Go to next page' size='icon' className='rounded-full'>
<ChevronRightIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to last page' size='icon' className='rounded-full'>
<ChevronLastIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithFirstAndLastPageButtonNavigation

View file

@ -0,0 +1,45 @@
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
const PaginationWithEllipsisDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' />
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<Tooltip>
<TooltipTrigger asChild>
<PaginationEllipsis />
</TooltipTrigger>
<TooltipContent>
<p>8 other pages</p>
</TooltipContent>
</Tooltip>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithEllipsisDemo

View file

@ -0,0 +1,44 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const PaginationUnderlineDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href='#' className='rounded-none' />
</PaginationItem>
<PaginationItem>
<PaginationLink
href='#'
isActive
className='border-primary! rounded-none border-0 border-b-2 bg-transparent! !shadow-none'
>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' className='rounded-none'>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' className='rounded-none'>
3
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' className='rounded-none' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationUnderlineDemo

View file

@ -0,0 +1,36 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const CardPaginationDemo = () => {
return (
<Pagination>
<PaginationContent className='rounded-md border p-1 shadow-xs'>
<PaginationItem>
<PaginationPrevious href='#' />
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#'>3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default CardPaginationDemo

View file

@ -0,0 +1,24 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const NumberlessPaginationDemo = () => {
return (
<Pagination>
<PaginationContent className='w-full justify-between'>
<PaginationItem>
<PaginationPrevious href='#' className='border' />
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' className='border' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default NumberlessPaginationDemo

View file

@ -0,0 +1,29 @@
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationNext,
PaginationPrevious
} from '@/components/ui/pagination'
const NumberlessPaginationWithTextDemo = () => {
return (
<Pagination>
<PaginationContent className='w-full justify-between'>
<PaginationItem>
<PaginationPrevious href='#' className='border' />
</PaginationItem>
<PaginationItem>
<p className='text-muted-foreground text-sm' aria-live='polite'>
Page <span className='text-foreground'>2</span> of <span className='text-foreground'>5</span>
</p>
</PaginationItem>
<PaginationItem>
<PaginationNext href='#' className='border' />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default NumberlessPaginationWithTextDemo

View file

@ -0,0 +1,29 @@
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, PaginationContent, PaginationItem, PaginationLink } from '@/components/ui/pagination'
const MiniPagination = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to previous page' size='icon'>
<ChevronLeftIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<p className='text-muted-foreground text-sm' aria-live='polite'>
Page <span className='text-foreground'>2</span> of <span className='text-foreground'>5</span>
</p>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to next page' size='icon'>
<ChevronRightIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default MiniPagination

View file

@ -0,0 +1,51 @@
import { ChevronFirstIcon, ChevronLastIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, PaginationContent, PaginationItem, PaginationLink } from '@/components/ui/pagination'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
const PaginationWithSelectDemo = () => {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to first page' size='icon' className='rounded-full'>
<ChevronFirstIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to previous page' size='icon' className='rounded-full'>
<ChevronLeftIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<Select defaultValue={String(1)} aria-label='Select page'>
<SelectTrigger id='select-page' className='w-fit whitespace-nowrap' aria-label='Select page'>
<SelectValue placeholder='Select page' />
</SelectTrigger>
<SelectContent>
{Array.from({ length: 10 }, (_, i) => i + 1).map(page => (
<SelectItem key={page} value={String(page)}>
Page {page}
</SelectItem>
))}
</SelectContent>
</Select>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to next page' size='icon' className='rounded-full'>
<ChevronRightIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to last page' size='icon' className='rounded-full'>
<ChevronLastIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
export default PaginationWithSelectDemo

View file

@ -0,0 +1,87 @@
import { useId } from 'react'
import { ChevronFirstIcon, ChevronLastIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Label } from '@/components/ui/label'
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink
} from '@/components/ui/pagination'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
const pages = [1, 2, 3]
const TablePaginationDemo = () => {
const id = useId()
return (
<div className='flex w-full flex-wrap items-center justify-between gap-6 max-sm:justify-center'>
<div className='flex shrink-0 items-center gap-3'>
<Label htmlFor={id}>Rows per page</Label>
<Select defaultValue='10'>
<SelectTrigger id={id} className='w-fit whitespace-nowrap'>
<SelectValue placeholder='Select number of results' />
</SelectTrigger>
<SelectContent className='[&_*[role=option]]:ps-2 [&_*[role=option]]:pe-8 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:end-2'>
<SelectItem value='10'>10</SelectItem>
<SelectItem value='25'>25</SelectItem>
<SelectItem value='50'>50</SelectItem>
</SelectContent>
</Select>
</div>
<div className='text-muted-foreground flex grow items-center justify-end whitespace-nowrap max-sm:justify-center'>
<p className='text-muted-foreground text-sm whitespace-nowrap' aria-live='polite'>
Showing <span className='text-foreground'>1</span> to <span className='text-foreground'>10</span> of{' '}
<span className='text-foreground'>100</span> products
</p>
</div>
<Pagination className='w-fit max-sm:mx-0'>
<PaginationContent>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to first page' size='icon' className='rounded-full'>
<ChevronFirstIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to previous page' size='icon' className='rounded-full'>
<ChevronLeftIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
{pages.map(page => (
<PaginationItem key={page}>
<PaginationLink href={`#${page}`} isActive={page === 2} className='rounded-full'>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<Tooltip>
<TooltipTrigger asChild>
<PaginationEllipsis />
</TooltipTrigger>
<TooltipContent>
<p>2 other pages</p>
</TooltipContent>
</Tooltip>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to next page' size='icon' className='rounded-full'>
<ChevronRightIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href='#' aria-label='Go to last page' size='icon' className='rounded-full'>
<ChevronLastIcon className='h-4 w-4' />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)
}
export default TablePaginationDemo

View file

@ -0,0 +1,47 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
const SheetDemo = () => {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Default</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
)
}
export default SheetDemo

View file

@ -0,0 +1,130 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
const SheetSidesDemo = () => {
return (
<div className='flex flex-wrap gap-2'>
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Top</Button>
</SheetTrigger>
<SheetContent side='top'>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Right</Button>
</SheetTrigger>
<SheetContent side='right'>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Bottom</Button>
</SheetTrigger>
<SheetContent side='bottom'>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Left</Button>
</SheetTrigger>
<SheetContent side='left'>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
)
}
export default SheetSidesDemo

View file

@ -0,0 +1,47 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
const SheetWithNoOverlayDemo = () => {
return (
<Sheet modal={false}>
<SheetTrigger asChild>
<Button variant='outline'>No Overlay</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>Make changes to your profile here. Click save when you&apos;re done.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-name'>Name</Label>
<Input id='sheet-demo-name' defaultValue='Pedro Duarte' />
</div>
<div className='grid gap-3'>
<Label htmlFor='sheet-demo-username'>Username</Label>
<Input id='sheet-demo-username' defaultValue='@peduarte' />
</div>
</div>
<SheetFooter>
<Button type='submit'>Save changes</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
)
}
export default SheetWithNoOverlayDemo

View file

@ -0,0 +1,132 @@
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
const SheetWithScrollableContentDemo = () => {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Scrollable Content</Button>
</SheetTrigger>
<SheetContent>
<ScrollArea className='h-full'>
<SheetHeader>
<SheetTitle>Terms & Condition</SheetTitle>
<SheetDescription>Make sure read the terms and conditions before proceeding.</SheetDescription>
</SheetHeader>
<div className='space-y-1 p-4 pt-0 text-sm'>
<p className='mb-2 font-medium'>Last Updated: June 1, 2025</p>
<h3>1. Introduction</h3>
<p>
Welcome to our platform. These Terms and Conditions outline the rules and regulations for the use of our
services. By accessing or using our services, you agree to comply with these terms. If you do not agree
with any of these terms, please do not use our services.
</p>
<h3>2. Acceptance of Terms</h3>
<p>
By using our services, you confirm that you have read, understood, and accepted these terms. You also
agree to comply with any additional guidelines, policies, or rules that may apply to specific features of
our services.
</p>
<h3>3. Services Provided</h3>
<p>
We offer a range of digital services including but not limited to content creation, subscription services,
and access to various online tools. You acknowledge that the nature of our services may change over time,
and we reserve the right to modify, suspend, or discontinue services at any time.
</p>
<h3>4. User Obligations</h3>
<p>
As a user, you agree to provide accurate and complete information when required, and to update this
information if necessary. You are responsible for maintaining the confidentiality of your account details,
including username and password, and for all activities under your account.
</p>
<h3>5. Prohibited Activities</h3>
<p>You may not use our services for any unlawful activities, including but not limited to:</p>
<ul>
<li>Distributing malicious content or viruses</li>
<li>Engaging in illegal activities or fraud</li>
<li>Impersonating another user or entity</li>
<li>Harassing or bullying other users</li>
</ul>
<h3>6. Content Ownership</h3>
<p>
All content, including text, images, graphics, and software on our platform, is owned by us or our
licensors and is protected by copyright laws. You are granted a limited, non-exclusive license to access
and use this content for personal or business purposes.
</p>
<h3>7. Privacy and Data Protection</h3>
<p>
Your privacy is important to us. Please refer to our <a href='#'>Privacy Policy</a> to understand how we
collect, use, and protect your personal data.
</p>
<h3>8. Payment Terms</h3>
<p>
Some of our services are available for a fee. You agree to pay all applicable charges and fees associated
with your use of the services. We reserve the right to change the pricing of our services at any time.
</p>
<h3>9. Termination</h3>
<p>
We may suspend or terminate your account if you violate these Terms and Conditions or engage in any
behavior that we deem inappropriate. Upon termination, your access to our services will be revoked, and
any outstanding payments will be due immediately.
</p>
<h3>10. Disclaimers and Limitation of Liability</h3>
<p>
We provide our services as is and make no warranties regarding the accuracy, reliability, or
availability of the services. We are not responsible for any damages, losses, or expenses incurred by your
use of our services.
</p>
<h3>11. Governing Law</h3>
<p>
These Terms and Conditions shall be governed by and construed in accordance with the laws of the
jurisdiction in which our company is based. Any disputes arising from these terms shall be subject to the
exclusive jurisdiction of the courts of that jurisdiction.
</p>
<h3>12. Changes to Terms</h3>
<p>
We reserve the right to update or modify these Terms and Conditions at any time. Any changes will be
posted on this page, and the revised terms will take effect immediately upon posting. It is your
responsibility to review these terms periodically for any updates.
</p>
<h3>13. Contact Information</h3>
<p>If you have any questions or concerns about these Terms and Conditions, please contact us at:</p>
<p>Email: support@example.com</p>
<p>Phone: +1 (800) 123-4567</p>
</div>
<SheetFooter>
<SheetClose asChild>
<Button type='submit'>Accept</Button>
</SheetClose>
<SheetClose asChild>
<Button variant='outline'>Cancel</Button>
</SheetClose>
</SheetFooter>
</ScrollArea>
</SheetContent>
</Sheet>
)
}
export default SheetWithScrollableContentDemo

View file

@ -0,0 +1,162 @@
'use client'
import { CheckCheckIcon } from 'lucide-react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'
import { Alert, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
Sheet,
SheetClose,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
const SheetWithFormDemo = () => {
const FormSchema = z.object({
firstName: z.string().min(1, 'First name is required').min(2, 'First name must be at least 2 characters'),
lastName: z.string().min(1, 'Last name is required').min(2, 'Last name must be at least 2 characters'),
email: z.string().min(1, 'Email is required').email({ message: 'Please enter a valid email address.' }),
mobileNumber: z
.number({ required_error: 'Mobile number is required', invalid_type_error: 'Please enter a valid number' })
.int('Mobile number must be a whole number')
.positive('Mobile number must be positive')
.refine(val => val.toString().length === 10, 'Mobile number must be exactly 10 digits'),
password: z.string().min(1, 'Password is required').min(8, 'Password must be at least 8 characters')
})
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
firstName: '',
lastName: '',
email: '',
mobileNumber: undefined,
password: ''
}
})
const onSubmit = () => {
toast.custom(() => (
<Alert className='border-green-600 text-green-600 dark:border-green-400 dark:text-green-400'>
<CheckCheckIcon />
<AlertTitle>Account created successfully!</AlertTitle>
</Alert>
))
}
return (
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Sign Up</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle className='text-center text-xl font-bold'>Sign Up</SheetTitle>
</SheetHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='w-full'>
<div className='space-y-4 p-4 pt-0'>
<FormField
control={form.control}
name='firstName'
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input placeholder='First name' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='lastName'
render={({ field }) => (
<FormItem>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input placeholder='Last name' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder='Email address' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='mobileNumber'
render={({ field }) => (
<FormItem>
<FormLabel>Mobile Number</FormLabel>
<FormControl>
<Input
type='tel'
placeholder='8585858585'
value={field.value ? field.value.toString() : ''}
onChange={e => {
const value = e.target.value.replace(/[^\d]/g, '')
const limitedValue = value.slice(0, 10)
const numValue = limitedValue === '' ? undefined : parseInt(limitedValue, 10)
field.onChange(numValue)
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type='password' placeholder='Password' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<SheetFooter>
<Button type='submit'>Create Account</Button>
<SheetClose asChild>
<Button variant='outline'>Close</Button>
</SheetClose>
</SheetFooter>
</form>
</Form>
</SheetContent>
</Sheet>
)
}
export default SheetWithFormDemo

View file

@ -0,0 +1,201 @@
import type { ForwardRefExoticComponent, RefAttributes } from 'react'
import {
BookTextIcon,
CalendarDaysIcon,
ChevronRightIcon,
CircleSmallIcon,
HeartPlusIcon,
HomeIcon,
LayoutPanelTopIcon,
LogInIcon,
LogOutIcon,
MailIcon,
MessageSquareTextIcon,
PanelTopIcon,
ShoppingCartIcon,
type LucideProps
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'
type NavigationItem = {
name: string
icon: ForwardRefExoticComponent<Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>>
} & (
| {
type: 'page'
children?: never
}
| {
type: 'category'
children: NavigationItem[]
}
)
const navigationMenu: NavigationItem[] = [
{
name: 'Dashboard',
icon: HomeIcon,
type: 'page'
},
{
name: 'Layouts',
icon: LayoutPanelTopIcon,
type: 'category',
children: [
{
name: 'Content Navbar',
icon: LayoutPanelTopIcon,
type: 'page'
},
{
name: 'Horizontal',
icon: LayoutPanelTopIcon,
type: 'page'
},
{
name: 'Without Menu',
icon: LayoutPanelTopIcon,
type: 'page'
}
]
},
{
name: 'Front Pages',
icon: PanelTopIcon,
type: 'category',
children: [
{
name: 'Landing Page',
icon: PanelTopIcon,
type: 'page'
},
{
name: 'Pricing Page',
icon: PanelTopIcon,
type: 'page'
},
{
name: 'Checkout Page',
icon: PanelTopIcon,
type: 'page'
}
]
},
{
name: 'Chat',
icon: MessageSquareTextIcon,
type: 'page'
},
{
name: 'Email',
icon: MailIcon,
type: 'page'
},
{
name: 'Calendar',
icon: CalendarDaysIcon,
type: 'page'
},
{
name: 'Ecommerce',
icon: ShoppingCartIcon,
type: 'category',
children: [
{
name: 'Products',
icon: ShoppingCartIcon,
type: 'page'
},
{
name: 'Categories',
icon: ShoppingCartIcon,
type: 'page'
},
{
name: 'Shopping & Delivery',
icon: ShoppingCartIcon,
type: 'page'
},
{
name: 'Location',
icon: ShoppingCartIcon,
type: 'page'
}
]
},
{
name: 'Sign In',
icon: LogInIcon,
type: 'page'
},
{
name: 'Sign Out',
icon: LogOutIcon,
type: 'page'
},
{
name: 'Support',
icon: HeartPlusIcon,
type: 'page'
},
{
name: 'Documentation',
icon: BookTextIcon,
type: 'page'
}
]
const NavigationMenu = ({ item, level }: { level: number; item: NavigationItem }) => {
if (item.type === 'page') {
return (
<div
className='focus-visible:ring-ring/50 flex items-center gap-2 rounded-md p-1 outline-none focus-visible:ring-[3px]'
style={{ paddingLeft: `${level === 0 ? 0.25 : 1.75}rem` }}
>
{level === 0 ? <item.icon className='size-4 shrink-0' /> : <CircleSmallIcon className='size-4 shrink-0' />}
<span className='text-sm'>{item.name}</span>
</div>
)
}
return (
<Collapsible className='flex flex-col gap-1.5' style={{ paddingLeft: `${level === 0 ? 0 : 1.5}rem` }}>
<CollapsibleTrigger className='focus-visible:ring-ring/50 flex items-center gap-2 rounded-md p-1 outline-none focus-visible:ring-[3px]'>
{level === 0 ? <item.icon className='size-4 shrink-0' /> : <CircleSmallIcon className='size-4 shrink-0' />}
<span className='flex-1 text-start text-sm'>{item.name}</span>
<ChevronRightIcon className='size-4 shrink-0 transition-transform [[data-state="open"]>&]:rotate-90' />
</CollapsibleTrigger>
<CollapsibleContent className='data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down flex flex-col gap-1.5 overflow-hidden transition-all duration-300'>
{item.children.map(item => (
<NavigationMenu key={item.name} item={item} level={level + 1} />
))}
</CollapsibleContent>
</Collapsible>
)
}
const SheetWithNavigationMenuDemo = () => {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant='outline'>Navigation Menu</Button>
</SheetTrigger>
<SheetContent side='left' className='w-75'>
<SheetHeader>
<SheetTitle>Menu</SheetTitle>
</SheetHeader>
<div className='flex flex-col gap-2.5 p-4 pt-0'>
{navigationMenu.map(item => (
<NavigationMenu key={item.name} item={item} level={0} />
))}
</div>
</SheetContent>
</Sheet>
)
}
export default SheetWithNavigationMenuDemo

View file

@ -0,0 +1,316 @@
'use client'
import { useState } from 'react'
import { PlusIcon } from 'lucide-react'
import type { ColumnDef, ColumnFiltersState, SortingState, VisibilityState } from '@tanstack/react-table'
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
const data: Payment[] = [
{
id: '1',
name: 'Shang Chain',
amount: 699,
status: 'success',
email: 'shang07@yahoo.com'
},
{
id: '2',
name: 'Kevin Lincoln',
amount: 242,
status: 'success',
email: 'kevinli09@gmail.com'
},
{
id: '3',
name: 'Milton Rose',
amount: 655,
status: 'processing',
email: 'rose96@gmail.com'
},
{
id: '4',
name: 'Silas Ryan',
amount: 874,
status: 'success',
email: 'silas22@gmail.com'
},
{
id: '5',
name: 'Ben Tenison',
amount: 541,
status: 'failed',
email: 'bent@hotmail.com'
}
]
export type Payment = {
id: string
name: string
amount: number
status: 'pending' | 'processing' | 'success' | 'failed'
email: string
}
export const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
onCheckedChange={value => table.toggleAllPageRowsSelected(!!value)}
aria-label='Select all'
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={value => row.toggleSelected(!!value)}
aria-label='Select row'
/>
),
enableSorting: false,
enableHiding: false
},
{
header: 'Name',
accessorKey: 'name',
cell: ({ row }) => <div className='font-medium'>{row.getValue('name')}</div>
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => <div className='capitalize'>{row.getValue('status')}</div>
},
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => <div className='lowercase'>{row.getValue('email')}</div>
},
{
accessorKey: 'amount',
header: () => <div className='text-right'>Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
return <div className='text-right font-medium'>{formatted}</div>
}
}
]
const DataTableDensityDemo = () => {
const [tableData, setTableData] = useState(data)
const [globalFilter, setGlobalFilter] = useState('')
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const [rowSelection, setRowSelection] = useState({})
const [isSheetOpen, setIsSheetOpen] = useState(false)
// Form state for adding new user
const [newUser, setNewUser] = useState({
name: '',
email: '',
amount: '',
status: 'pending' as Payment['status']
})
// Function to handle form submission
const handleAddUser = () => {
if (!newUser.name || !newUser.email || !newUser.amount) {
return // Basic validation
}
const newPayment: Payment = {
id: String(tableData.length + 1),
name: newUser.name,
email: newUser.email,
amount: parseFloat(newUser.amount),
status: newUser.status
}
setTableData([...tableData, newPayment])
// Reset form
setNewUser({
name: '',
email: '',
amount: '',
status: 'pending'
})
setIsSheetOpen(false)
}
const table = useReactTable({
data: tableData,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: 'includesString',
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
globalFilter
}
})
return (
<div className='w-full'>
<div className='flex justify-between gap-2 py-4 max-sm:flex-col sm:items-center'>
<Input
placeholder='Search all columns...'
value={globalFilter ?? ''}
onChange={event => setGlobalFilter(String(event.target.value))}
className='max-w-2xs'
/>
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
<SheetTrigger asChild>
<Button variant='outline'>
<PlusIcon />
Add Users
</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Add New User</SheetTitle>
<SheetDescription>Add a new user to the table. Fill in all the required information.</SheetDescription>
</SheetHeader>
<div className='grid flex-1 auto-rows-min gap-6 px-4'>
<div className='grid gap-3'>
<Label htmlFor='user-name'>Name</Label>
<Input
id='user-name'
value={newUser.name}
onChange={e => setNewUser({ ...newUser, name: e.target.value })}
placeholder='Enter user name'
/>
</div>
<div className='grid gap-3'>
<Label htmlFor='user-email'>Email</Label>
<Input
id='user-email'
type='email'
value={newUser.email}
onChange={e => setNewUser({ ...newUser, email: e.target.value })}
placeholder='Enter email address'
/>
</div>
<div className='grid gap-3'>
<Label htmlFor='user-amount'>Amount</Label>
<Input
id='user-amount'
type='number'
value={newUser.amount}
onChange={e => setNewUser({ ...newUser, amount: e.target.value })}
placeholder='Enter amount'
/>
</div>
<div className='grid gap-3'>
<Label htmlFor='user-status'>Status</Label>
<Select
value={newUser.status}
onValueChange={(value: Payment['status']) => setNewUser({ ...newUser, status: value })}
>
<SelectTrigger className='w-full'>
<SelectValue placeholder='Select status' />
</SelectTrigger>
<SelectContent>
<SelectItem value='pending'>Pending</SelectItem>
<SelectItem value='processing'>Processing</SelectItem>
<SelectItem value='success'>Success</SelectItem>
<SelectItem value='failed'>Failed</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<SheetFooter>
<Button type='button' onClick={handleAddUser}>
Add User
</Button>
<SheetClose asChild>
<Button variant='outline'>Cancel</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
<div className='rounded-md border'>
<Table>
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className='h-24 text-center'>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<p className='text-muted-foreground mt-4 text-center text-sm'>Add new user with sheet</p>
</div>
)
}
export default DataTableDensityDemo

View file

@ -0,0 +1,15 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerDemo = () => {
return (
<Button variant='outline' onClick={() => toast('Action completed successfully!')}>
Default Toast
</Button>
)
}
export default SonnerDemo

View file

@ -0,0 +1,22 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerWithDescriptionDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast('Event is created', {
description: 'Friday, August 15, 2025 at 9:00 AM'
})
}
>
Toast with description
</Button>
)
}
export default SonnerWithDescriptionDemo

View file

@ -0,0 +1,27 @@
'use client'
import { TruckIcon } from 'lucide-react'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerWithIconDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast(
<div className='flex items-center gap-2'>
<TruckIcon className='size-5.5 shrink-0' />
Your order has been successfully placed, and your parcel is on its way.
</div>
)
}
>
Toast with icon
</Button>
)
}
export default SonnerWithIconDemo

View file

@ -0,0 +1,29 @@
'use client'
import { toast } from 'sonner'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
const SonnerWithAvatarDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast(
<div className='flex items-center gap-2'>
<Avatar>
<AvatarImage src='https://cdn.shadcnstudio.com/ss-assets/avatar/avatar-5.png' alt='Hallie Richards' />
<AvatarFallback className='text-xs'>HR</AvatarFallback>
</Avatar>
Hey Henry Richer, your profile is now up to date!
</div>
)
}
>
Toast with avatar
</Button>
)
}
export default SonnerWithAvatarDemo

View file

@ -0,0 +1,22 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const ClosableSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
closeButton: true
})
}
>
Closable Toast
</Button>
)
}
export default ClosableSonnerDemo

View file

@ -0,0 +1,25 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerWithActionDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
action: {
label: 'Undo',
onClick: () => console.log('Undo')
}
})
}
>
Toast with action
</Button>
)
}
export default SonnerWithActionDemo

View file

@ -0,0 +1,35 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerWithPromiseDemo = () => {
const promise = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
if (Math.random() < 0.5) {
resolve('foo')
} else {
reject('fox')
}
}, 2000)
)
return (
<Button
variant='outline'
onClick={() =>
toast.promise(promise, {
loading: 'Loading...',
success: 'Toast has been added successfully!',
error: 'Oops, there was an error adding the toast.'
})
}
>
Toast with promise
</Button>
)
}
export default SonnerWithPromiseDemo

View file

@ -0,0 +1,74 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SonnerPositionDemo = () => {
return (
<div className='grid grid-cols-2 gap-2'>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'top-left'
})
}
>
Top Left
</Button>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'top-center'
})
}
>
Top Center
</Button>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'top-right'
})
}
>
Top Right
</Button>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'bottom-left'
})
}
>
Bottom Left
</Button>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'bottom-center'
})
}
>
Bottom Center
</Button>
<Button
variant='outline'
onClick={() =>
toast('Action completed successfully!', {
position: 'bottom-right'
})
}
>
Bottom Right
</Button>
</div>
)
}
export default SonnerPositionDemo

View file

@ -0,0 +1,27 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SoftInfoSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.info('This is for your information, please note.', {
style: {
'--normal-bg':
'color-mix(in oklab, light-dark(var(--color-sky-600), var(--color-sky-400)) 10%, var(--background))',
'--normal-text': 'light-dark(var(--color-sky-600), var(--color-sky-400))',
'--normal-border': 'light-dark(var(--color-sky-600), var(--color-sky-400))'
} as React.CSSProperties
})
}
>
Soft Info Toast
</Button>
)
}
export default SoftInfoSonnerDemo

View file

@ -0,0 +1,27 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SoftSuccessSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.success('Action completed successfully!', {
style: {
'--normal-bg':
'color-mix(in oklab, light-dark(var(--color-green-600), var(--color-green-400)) 10%, var(--background))',
'--normal-text': 'light-dark(var(--color-green-600), var(--color-green-400))',
'--normal-border': 'light-dark(var(--color-green-600), var(--color-green-400))'
} as React.CSSProperties
})
}
>
Soft Success Toast
</Button>
)
}
export default SoftSuccessSonnerDemo

View file

@ -0,0 +1,27 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SoftWarningSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.warning('Warning: Please check the entered data.', {
style: {
'--normal-bg':
'color-mix(in oklab, light-dark(var(--color-amber-600), var(--color-amber-400)) 10%, var(--background))',
'--normal-text': 'light-dark(var(--color-amber-600), var(--color-amber-400))',
'--normal-border': 'light-dark(var(--color-amber-600), var(--color-amber-400))'
} as React.CSSProperties
})
}
>
Soft Warning Toast
</Button>
)
}
export default SoftWarningSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SoftDestructiveSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.error('Oops, there was an error processing your request.', {
style: {
'--normal-bg': 'color-mix(in oklab, var(--destructive) 10%, var(--background))',
'--normal-text': 'var(--destructive)',
'--normal-border': 'var(--destructive)'
} as React.CSSProperties
})
}
>
Soft Destructive Toast
</Button>
)
}
export default SoftDestructiveSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const OutlineInfoSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.info('This is for your information, please note.', {
style: {
'--normal-bg': 'var(--background)',
'--normal-text': 'light-dark(var(--color-sky-600), var(--color-sky-400))',
'--normal-border': 'light-dark(var(--color-sky-600), var(--color-sky-400))'
} as React.CSSProperties
})
}
>
Outline Info Toast
</Button>
)
}
export default OutlineInfoSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const OutlineSuccessSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.success('Action completed successfully!', {
style: {
'--normal-bg': 'var(--background)',
'--normal-text': 'light-dark(var(--color-green-600), var(--color-green-400))',
'--normal-border': 'light-dark(var(--color-green-600), var(--color-green-400))'
} as React.CSSProperties
})
}
>
Outline Success Toast
</Button>
)
}
export default OutlineSuccessSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const OutlineWarningSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.warning('Warning: Please check the entered data.', {
style: {
'--normal-bg': 'var(--background)',
'--normal-text': 'light-dark(var(--color-amber-600), var(--color-amber-400))',
'--normal-border': 'light-dark(var(--color-amber-600), var(--color-amber-400))'
} as React.CSSProperties
})
}
>
Outline Warning Toast
</Button>
)
}
export default OutlineWarningSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const OutlineDestructiveSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.error('Oops, there was an error processing your request.', {
style: {
'--normal-bg': 'var(--background)',
'--normal-text': 'var(--destructive)',
'--normal-border': 'var(--destructive)'
} as React.CSSProperties
})
}
>
Outline Destructive Toast
</Button>
)
}
export default OutlineDestructiveSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SolidInfoSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.info('This is for your information, please note.', {
style: {
'--normal-bg': 'light-dark(var(--color-sky-600), var(--color-sky-400))',
'--normal-text': 'var(--color-white)',
'--normal-border': 'light-dark(var(--color-sky-600), var(--color-sky-400))'
} as React.CSSProperties
})
}
>
Solid Info Toast
</Button>
)
}
export default SolidInfoSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SolidSuccessSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.success('Action completed successfully!', {
style: {
'--normal-bg': 'light-dark(var(--color-green-600), var(--color-green-400))',
'--normal-text': 'var(--color-white)',
'--normal-border': 'light-dark(var(--color-green-600), var(--color-green-400))'
} as React.CSSProperties
})
}
>
Solid Success Toast
</Button>
)
}
export default SolidSuccessSonnerDemo

View file

@ -0,0 +1,26 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SolidWarningSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.warning('Warning: Please check the entered data.', {
style: {
'--normal-bg': 'light-dark(var(--color-amber-600), var(--color-amber-400))',
'--normal-text': 'var(--color-white)',
'--normal-border': 'light-dark(var(--color-amber-600), var(--color-amber-400))'
} as React.CSSProperties
})
}
>
Solid Warning Toast
</Button>
)
}
export default SolidWarningSonnerDemo

View file

@ -0,0 +1,27 @@
'use client'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
const SolidDestructiveSonnerDemo = () => {
return (
<Button
variant='outline'
onClick={() =>
toast.error('Oops, there was an error processing your request.', {
style: {
'--normal-bg':
'light-dark(var(--destructive), color-mix(in oklab, var(--destructive) 60%, var(--background)))',
'--normal-text': 'var(--color-white)',
'--normal-border': 'transparent'
} as React.CSSProperties
})
}
>
Solid Destructive Toast
</Button>
)
}
export default SolidDestructiveSonnerDemo

View file

@ -0,0 +1,58 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore'>
<TabsList>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value}>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsOutlinedDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background gap-1 border p-1'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary dark:data-[state=active]:bg-primary data-[state=active]:text-primary-foreground dark:data-[state=active]:text-primary-foreground dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsOutlinedDemo

View file

@ -0,0 +1,64 @@
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsWithIconDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList>
{tabs.map(({ icon: Icon, name, value }) => (
<TabsTrigger key={value} value={value} className='flex items-center gap-1 px-2.5 sm:px-3'>
<Icon />
{name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsWithIconDemo

View file

@ -0,0 +1,63 @@
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
count: 8,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
count: 3,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise',
value: 'surprise',
count: 6,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsWithBadgeDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value} className='flex items-center gap-1 px-2.5 sm:px-3'>
{tab.name}
<Badge className='h-5 min-w-5 rounded-full px-1 tabular-nums'>{tab.count}</Badge>
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsWithBadgeDemo

View file

@ -0,0 +1,64 @@
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsWithVerticalIconDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='h-full'>
{tabs.map(({ icon: Icon, name, value }) => (
<TabsTrigger key={value} value={value} className='flex flex-col items-center gap-1 px-2.5 sm:px-3'>
<Icon />
{name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsWithVerticalIconDemo

View file

@ -0,0 +1,63 @@
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
count: 8,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
count: 3,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
count: 6,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsWithVerticalBadgeDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='h-full'>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value} className='flex flex-col items-center gap-1 px-2.5 sm:px-3'>
<Badge className='h-5 min-w-5 rounded-full px-1 tabular-nums'>{tab.count}</Badge>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsWithVerticalBadgeDemo

View file

@ -0,0 +1,75 @@
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsWithTooltipDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='h-full'>
{tabs.map(({ icon: Icon, name, value }) => (
<Tooltip key={value}>
<TooltipTrigger asChild>
<span>
<TabsTrigger
value={value}
className='flex flex-col items-center gap-1 px-2.5 sm:px-3'
aria-label='tab-trigger'
>
<Icon />
</TabsTrigger>
</span>
</TooltipTrigger>
<TooltipContent className='px-2 py-1 text-xs'>{name}</TooltipContent>
</Tooltip>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsWithTooltipDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsSoftPillsDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary/20 data-[state=active]:text-primary dark:data-[state=active]:text-primary dark:data-[state=active]:bg-primary/20 data-[state=active]:shadow-none dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsSoftPillsDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsSolidPillsDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary dark:data-[state=active]:bg-primary data-[state=active]:text-primary-foreground dark:data-[state=active]:text-primary-foreground dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsSolidPillsDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsOutlinedPillsDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:border-border data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsOutlinedPillsDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsUnderlineDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background rounded-none border-b p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary h-full rounded-none border-0 border-b-2 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsUnderlineDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsSharpDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background rounded-none border-b p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary h-full rounded-none border-b-2 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsSharpDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsLiftedDemo = () => {
return (
<div>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background justify-start rounded-none border-b p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background border-b-border dark:data-[state=active]:bg-background data-[state=active]:border-border data-[state=active]:border-b-background h-full rounded-none rounded-t border border-transparent data-[state=active]:-mb-0.5 data-[state=active]:shadow-none dark:border-b-0 dark:data-[state=active]:-mb-0.5'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsLiftedDemo

View file

@ -0,0 +1,122 @@
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
},
{
name: 'Trending',
value: 'trending',
content: (
<>
Stay on top of whats <span className='text-foreground font-semibold'>trending</span>. Discover what
everyone&apos;s talking about, from viral trends to the latest memes and conversations.
</>
)
},
{
name: 'Events',
value: 'events',
content: (
<>
Check out upcoming <span className='text-foreground font-semibold'>events</span> happening near you. Whether
virtual or in-person, theres always something to join and be a part of.
</>
)
},
{
name: 'News',
value: 'news',
content: (
<>
Stay updated with the latest <span className='text-foreground font-semibold'>news</span> across the globe. From
tech breakthroughs to world events, get the stories that matter most.
</>
)
},
{
name: 'Community',
value: 'community',
content: (
<>
Connect with the <span className='text-foreground font-semibold'>community</span>share your thoughts, ask
questions, and join discussions with like-minded individuals.
</>
)
},
{
name: 'Rewards',
value: 'rewards',
content: (
<>
Unlock exclusive <span className='text-foreground font-semibold'>rewards</span> and perks for your activity.
Keep an eye out for new ways to earn and redeem your points.
</>
)
},
{
name: 'Profile',
value: 'profile',
content: (
<>
View and edit your <span className='text-foreground font-semibold'>profile</span> information, track your
activity, and customize your experience. It&apos;s all about you here!
</>
)
}
]
const TabsOverflowDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-1'>
<ScrollArea>
<TabsList className='mb-3'>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value}>
{tab.name}
</TabsTrigger>
))}
</TabsList>
<ScrollBar orientation='horizontal' />
</ScrollArea>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsOverflowDemo

View file

@ -0,0 +1,58 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='h-full flex-col'>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value} className='w-full'>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalUnderlineDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col rounded-none border-l p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary h-full w-full justify-start rounded-none border-0 border-l-2 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalUnderlineDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsSoftVerticalDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary/20 data-[state=active]:text-primary dark:data-[state=active]:text-primary dark:data-[state=active]:bg-primary/20 w-full data-[state=active]:shadow-none dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsSoftVerticalDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalSolidDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary dark:data-[state=active]:bg-primary data-[state=active]:text-primary-foreground dark:data-[state=active]:text-primary-foreground w-full dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalSolidDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalSharpDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col rounded-none border-l p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary h-full w-full justify-start rounded-none border-l-3 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalSharpDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalLinedDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col rounded-none p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary h-full w-full justify-start rounded-none border-0 border-l-2 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalLinedDemo

View file

@ -0,0 +1,77 @@
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalWithTooltipDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row gap-4'>
<TabsList className='h-full flex-col gap-2'>
{tabs.map(({ icon: Icon, name, value }) => (
<Tooltip key={value}>
<TooltipTrigger asChild>
<span>
<TabsTrigger
value={value}
className='flex w-full flex-col items-center gap-1'
aria-label='tab-trigger'
>
<Icon />
</TabsTrigger>
</span>
</TooltipTrigger>
<TooltipContent className='px-2 py-1 text-xs' side='left'>
{name}
</TooltipContent>
</Tooltip>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalWithTooltipDemo

View file

@ -0,0 +1,68 @@
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalWithIconDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='h-full flex-col'>
{tabs.map(({ icon: Icon, name, value }) => (
<TabsTrigger
key={value}
value={value}
className='flex w-full items-center justify-start gap-1.5 px-2.5 sm:px-3'
>
<Icon />
{name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalWithIconDemo

View file

@ -0,0 +1,67 @@
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
count: 8,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
count: 3,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
count: 6,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalWithBadgeDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='h-full flex-col gap-1.5'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='flex w-full items-center justify-start gap-1.5 px-2.5 sm:px-3'
>
{tab.name}
<Badge className='h-5 min-w-5 rounded-full px-1 tabular-nums'>{tab.count}</Badge>
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalWithBadgeDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsVerticalOutlineDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='flex-row'>
<TabsList className='bg-background h-full flex-col'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:border-border w-full data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsVerticalOutlineDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsCustomDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background gap-1'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='data-[state=active]:bg-primary dark:data-[state=active]:bg-primary data-[state=active]:text-primary-foreground dark:data-[state=active]:text-primary-foreground text-muted-foreground hover:text-foreground hover:bg-muted transition-colors duration-300 hover:border dark:data-[state=active]:border-transparent'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsCustomDemo

View file

@ -0,0 +1,62 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const TabsCustomUnderlineDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList className='bg-background rounded-none border-b p-0'>
{tabs.map(tab => (
<TabsTrigger
key={tab.value}
value={tab.value}
className='bg-background data-[state=active]:border-primary dark:data-[state=active]:border-primary data-[state=active]:text-foreground text-muted-foreground dark:text-muted-foreground hover:text-foreground dark:hover:text-foreground hover:border-muted-foreground/30 h-full rounded-none border-0 border-b-2 border-transparent data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default TabsCustomUnderlineDemo

View file

@ -0,0 +1,72 @@
import { Tabs, TabsContent, TabsContents, TabsList, TabsTrigger } from '@/components/ui/motion-tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const AnimatedTabsDemo = () => {
return (
<div className='w-full max-w-md'>
<Tabs defaultValue='explore' className='gap-4'>
<TabsList>
{tabs.map(tab => (
<TabsTrigger key={tab.value} value={tab.value}>
{tab.name}
</TabsTrigger>
))}
</TabsList>
<TabsContents className='bg-background mx-1 -mt-2 mb-1 h-full rounded-sm'>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</TabsContents>
</Tabs>
<p className='text-muted-foreground mt-4 text-center text-xs'>
Inspired by{' '}
<a
className='hover:text-foreground underline'
href='https://animate-ui.com/docs/components/tabs'
target='_blank'
rel='noopener noreferrer'
>
Animate UI
</a>
</p>
</div>
)
}
export default AnimatedTabsDemo

View file

@ -0,0 +1,115 @@
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'motion/react'
import { BookIcon, GiftIcon, HeartIcon } from 'lucide-react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { cn } from '@/lib/utils'
const tabs = [
{
name: 'Explore',
value: 'explore',
icon: BookIcon,
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
icon: HeartIcon,
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
icon: GiftIcon,
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const ExpandableTabsDemo = () => {
const [activeTab, setActiveTab] = useState('explore')
return (
<div className='w-full max-w-md'>
<Tabs value={activeTab} onValueChange={setActiveTab} className='gap-4'>
<TabsList className='h-auto gap-2 rounded-xl p-1'>
{tabs.map(({ icon: Icon, name, value }) => {
const isActive = activeTab === value
return (
<motion.div
key={value}
layout
className={cn(
'flex h-8 items-center justify-center overflow-hidden rounded-md',
isActive ? 'flex-1' : 'flex-none'
)}
onClick={() => setActiveTab(value)}
initial={false}
animate={{
width: isActive ? 120 : 32
}}
transition={{
type: 'tween',
stiffness: 400,
damping: 25
}}
>
<TabsTrigger value={value} asChild>
<motion.div
className='flex h-8 w-full items-center justify-center'
animate={{ filter: 'blur(0px)' }}
exit={{ filter: 'blur(2px)' }}
transition={{ duration: 0.25, ease: 'easeOut' }}
>
<Icon className='aspect-square size-4 flex-shrink-0' />
<AnimatePresence initial={false}>
{isActive && (
<motion.span
className='font-medium max-sm:hidden'
initial={{ opacity: 0, scaleX: 0.8 }}
animate={{ opacity: 1, scaleX: 1 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
style={{ originX: 0 }}
>
{name}
</motion.span>
)}
</AnimatePresence>
</motion.div>
</TabsTrigger>
</motion.div>
)
})}
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default ExpandableTabsDemo

View file

@ -0,0 +1,103 @@
'use client'
import * as React from 'react'
import { motion } from 'motion/react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
const tabs = [
{
name: 'Explore',
value: 'explore',
content: (
<>
Discover <span className='text-foreground font-semibold'>fresh ideas</span>, trending topics, and hidden gems
curated just for you. Start exploring and let your curiosity lead the way!
</>
)
},
{
name: 'Favorites',
value: 'favorites',
content: (
<>
All your <span className='text-foreground font-semibold'>favorites</span> are saved here. Revisit articles,
collections, and moments you love, any time you want a little inspiration.
</>
)
},
{
name: 'Surprise Me',
value: 'surprise',
content: (
<>
<span className='text-foreground font-semibold'>Surprise!</span> Here&apos;s something unexpecteda fun fact, a
quirky tip, or a daily challenge. Come back for a new surprise every day!
</>
)
}
]
const AnimatedUnderlineTabsDemo = () => {
const [activeTab, setActiveTab] = React.useState('explore')
const tabRefs = React.useRef<(HTMLButtonElement | null)[]>([])
const [underlineStyle, setUnderlineStyle] = React.useState({ left: 0, width: 0 })
React.useLayoutEffect(() => {
const activeIndex = tabs.findIndex(tab => tab.value === activeTab)
const activeTabElement = tabRefs.current[activeIndex]
if (activeTabElement) {
const { offsetLeft, offsetWidth } = activeTabElement
setUnderlineStyle({
left: offsetLeft,
width: offsetWidth
})
}
}, [activeTab])
return (
<div className='w-full max-w-md'>
<Tabs value={activeTab} onValueChange={setActiveTab} className='gap-4'>
<TabsList className='bg-background relative rounded-none border-b p-0'>
{tabs.map((tab, index) => (
<TabsTrigger
key={tab.value}
value={tab.value}
ref={el => {
tabRefs.current[index] = el
}}
className='bg-background dark:data-[state=active]:bg-background relative z-10 rounded-none border-0 data-[state=active]:shadow-none'
>
{tab.name}
</TabsTrigger>
))}
<motion.div
className='bg-primary absolute bottom-0 z-20 h-0.5'
layoutId='underline'
style={{
left: underlineStyle.left,
width: underlineStyle.width
}}
transition={{
type: 'spring',
stiffness: 400,
damping: 40
}}
/>
</TabsList>
{tabs.map(tab => (
<TabsContent key={tab.value} value={tab.value}>
<p className='text-muted-foreground text-sm'>{tab.content}</p>
</TabsContent>
))}
</Tabs>
</div>
)
}
export default AnimatedUnderlineTabsDemo

View file

@ -0,0 +1,551 @@
'use client'
import * as React from 'react'
import type { Transition } from 'motion/react'
import { AnimatePresence, motion } from 'motion/react'
import { cn } from '@/lib/utils'
type MotionHighlightMode = 'children' | 'parent'
type Bounds = {
top: number
left: number
width: number
height: number
}
type MotionHighlightContextType<T extends string> = {
mode: MotionHighlightMode
activeValue: T | null
setActiveValue: (value: T | null) => void
setBounds: (bounds: DOMRect) => void
clearBounds: () => void
id: string
hover: boolean
className?: string
activeClassName?: string
setActiveClassName: (className: string) => void
transition?: Transition
disabled?: boolean
enabled?: boolean
exitDelay?: number
forceUpdateBounds?: boolean
}
const MotionHighlightContext = React.createContext<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
MotionHighlightContextType<any> | undefined
>(undefined)
function useMotionHighlight<T extends string>(): MotionHighlightContextType<T> {
const context = React.useContext(MotionHighlightContext)
if (!context) {
throw new Error('useMotionHighlight must be used within a MotionHighlightProvider')
}
return context as unknown as MotionHighlightContextType<T>
}
type BaseMotionHighlightProps<T extends string> = {
mode?: MotionHighlightMode
value?: T | null
defaultValue?: T | null
onValueChange?: (value: T | null) => void
className?: string
transition?: Transition
hover?: boolean
disabled?: boolean
enabled?: boolean
exitDelay?: number
}
type ParentModeMotionHighlightProps = {
boundsOffset?: Partial<Bounds>
containerClassName?: string
forceUpdateBounds?: boolean
}
type ControlledParentModeMotionHighlightProps<T extends string> = BaseMotionHighlightProps<T> &
ParentModeMotionHighlightProps & {
mode: 'parent'
controlledItems: true
children: React.ReactNode
}
type ControlledChildrenModeMotionHighlightProps<T extends string> = BaseMotionHighlightProps<T> & {
mode?: 'children' | undefined
controlledItems: true
children: React.ReactNode
}
type UncontrolledParentModeMotionHighlightProps<T extends string> = BaseMotionHighlightProps<T> &
ParentModeMotionHighlightProps & {
mode: 'parent'
controlledItems?: false
itemsClassName?: string
children: React.ReactElement | React.ReactElement[]
}
type UncontrolledChildrenModeMotionHighlightProps<T extends string> = BaseMotionHighlightProps<T> & {
mode?: 'children'
controlledItems?: false
itemsClassName?: string
children: React.ReactElement | React.ReactElement[]
}
type MotionHighlightProps<T extends string> = React.ComponentProps<'div'> &
(
| ControlledParentModeMotionHighlightProps<T>
| ControlledChildrenModeMotionHighlightProps<T>
| UncontrolledParentModeMotionHighlightProps<T>
| UncontrolledChildrenModeMotionHighlightProps<T>
)
function MotionHighlight<T extends string>({ ref, ...props }: MotionHighlightProps<T>) {
const {
children,
value,
defaultValue,
onValueChange,
className,
transition = { type: 'spring', stiffness: 350, damping: 35 },
hover = false,
enabled = true,
controlledItems,
disabled = false,
exitDelay = 0.2,
mode = 'children'
} = props
const localRef = React.useRef<HTMLDivElement>(null)
React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement)
const [activeValue, setActiveValue] = React.useState<T | null>(value ?? defaultValue ?? null)
const [boundsState, setBoundsState] = React.useState<Bounds | null>(null)
const [activeClassNameState, setActiveClassNameState] = React.useState<string>('')
const safeSetActiveValue = React.useCallback(
(id: T | null) => {
setActiveValue(prev => (prev === id ? prev : id))
if (id !== activeValue) onValueChange?.(id as T)
},
[activeValue, onValueChange]
)
const safeSetBounds = React.useCallback(
(bounds: DOMRect) => {
if (!localRef.current) return
const boundsOffset = (props as ParentModeMotionHighlightProps)?.boundsOffset ?? {
top: 0,
left: 0,
width: 0,
height: 0
}
const containerRect = localRef.current.getBoundingClientRect()
const newBounds: Bounds = {
top: bounds.top - containerRect.top + (boundsOffset.top ?? 0),
left: bounds.left - containerRect.left + (boundsOffset.left ?? 0),
width: bounds.width + (boundsOffset.width ?? 0),
height: bounds.height + (boundsOffset.height ?? 0)
}
setBoundsState(prev => {
if (
prev &&
prev.top === newBounds.top &&
prev.left === newBounds.left &&
prev.width === newBounds.width &&
prev.height === newBounds.height
) {
return prev
}
return newBounds
})
},
[props]
)
const clearBounds = React.useCallback(() => {
setBoundsState(prev => (prev === null ? prev : null))
}, [])
React.useEffect(() => {
if (value !== undefined) setActiveValue(value)
else if (defaultValue !== undefined) setActiveValue(defaultValue)
}, [value, defaultValue])
const id = React.useId()
React.useEffect(() => {
if (mode !== 'parent') return
const container = localRef.current
if (!container) return
const onScroll = () => {
if (!activeValue) return
const activeEl = container.querySelector<HTMLElement>(`[data-value="${activeValue}"][data-highlight="true"]`)
if (activeEl) safeSetBounds(activeEl.getBoundingClientRect())
}
container.addEventListener('scroll', onScroll, { passive: true })
return () => container.removeEventListener('scroll', onScroll)
}, [mode, activeValue, safeSetBounds])
const render = React.useCallback(
(children: React.ReactNode) => {
if (mode === 'parent') {
return (
<div
ref={localRef}
data-slot='motion-highlight-container'
className={cn('relative', (props as ParentModeMotionHighlightProps)?.containerClassName)}
>
<AnimatePresence initial={false}>
{boundsState && (
<motion.div
data-slot='motion-highlight'
animate={{
top: boundsState.top,
left: boundsState.left,
width: boundsState.width,
height: boundsState.height,
opacity: 1
}}
initial={{
top: boundsState.top,
left: boundsState.left,
width: boundsState.width,
height: boundsState.height,
opacity: 0
}}
exit={{
opacity: 0,
transition: {
...transition,
delay: (transition?.delay ?? 0) + (exitDelay ?? 0)
}
}}
transition={transition}
className={cn('bg-muted absolute z-0', className, activeClassNameState)}
/>
)}
</AnimatePresence>
{children}
</div>
)
}
return children
},
[mode, props, boundsState, transition, exitDelay, className, activeClassNameState]
)
return (
<MotionHighlightContext.Provider
value={{
mode,
activeValue,
setActiveValue: safeSetActiveValue,
id,
hover,
className,
transition,
disabled,
enabled,
exitDelay,
setBounds: safeSetBounds,
clearBounds,
activeClassName: activeClassNameState,
setActiveClassName: setActiveClassNameState,
forceUpdateBounds: (props as ParentModeMotionHighlightProps)?.forceUpdateBounds
}}
>
{enabled
? controlledItems
? render(children)
: render(
React.Children.map(children, (child, index) => (
<MotionHighlightItem key={index} className={props?.itemsClassName}>
{child}
</MotionHighlightItem>
))
)
: children}
</MotionHighlightContext.Provider>
)
}
function getNonOverridingDataAttributes(
element: React.ReactElement,
dataAttributes: Record<string, unknown>
): Record<string, unknown> {
return Object.keys(dataAttributes).reduce<Record<string, unknown>>((acc, key) => {
if ((element.props as Record<string, unknown>)[key] === undefined) {
acc[key] = dataAttributes[key]
}
return acc
}, {})
}
type ExtendedChildProps = React.ComponentProps<'div'> & {
id?: string
ref?: React.Ref<HTMLElement>
'data-active'?: string
'data-value'?: string
'data-disabled'?: boolean
'data-highlight'?: boolean
'data-slot'?: string
}
type MotionHighlightItemProps = React.ComponentProps<'div'> & {
children: React.ReactElement
id?: string
value?: string
className?: string
transition?: Transition
activeClassName?: string
disabled?: boolean
exitDelay?: number
asChild?: boolean
forceUpdateBounds?: boolean
}
function MotionHighlightItem({
ref,
children,
id,
value,
className,
transition,
disabled = false,
activeClassName,
exitDelay,
asChild = false,
forceUpdateBounds,
...props
}: MotionHighlightItemProps) {
const itemId = React.useId()
const {
activeValue,
setActiveValue,
mode,
setBounds,
clearBounds,
hover,
enabled,
className: contextClassName,
transition: contextTransition,
id: contextId,
disabled: contextDisabled,
exitDelay: contextExitDelay,
forceUpdateBounds: contextForceUpdateBounds,
setActiveClassName
} = useMotionHighlight()
const element = children as React.ReactElement<ExtendedChildProps>
const childValue = id ?? value ?? element.props?.['data-value'] ?? element.props?.id ?? itemId
const isActive = activeValue === childValue
const isDisabled = disabled === undefined ? contextDisabled : disabled
const itemTransition = transition ?? contextTransition
const localRef = React.useRef<HTMLDivElement>(null)
React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement)
React.useEffect(() => {
if (mode !== 'parent') return
let rafId: number
let previousBounds: Bounds | null = null
const shouldUpdateBounds = forceUpdateBounds === true || (contextForceUpdateBounds && forceUpdateBounds !== false)
const updateBounds = () => {
if (!localRef.current) return
const bounds = localRef.current.getBoundingClientRect()
if (shouldUpdateBounds) {
if (
previousBounds &&
previousBounds.top === bounds.top &&
previousBounds.left === bounds.left &&
previousBounds.width === bounds.width &&
previousBounds.height === bounds.height
) {
rafId = requestAnimationFrame(updateBounds)
return
}
previousBounds = bounds
rafId = requestAnimationFrame(updateBounds)
}
setBounds(bounds)
}
if (isActive) {
updateBounds()
setActiveClassName(activeClassName ?? '')
} else if (!activeValue) clearBounds()
if (shouldUpdateBounds) return () => cancelAnimationFrame(rafId)
}, [
mode,
isActive,
activeValue,
setBounds,
clearBounds,
activeClassName,
setActiveClassName,
forceUpdateBounds,
contextForceUpdateBounds
])
if (!React.isValidElement(children)) return children
const dataAttributes = {
'data-active': isActive ? 'true' : 'false',
'aria-selected': isActive,
'data-disabled': isDisabled,
'data-value': childValue,
'data-highlight': true
}
const commonHandlers = hover
? {
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
setActiveValue(childValue)
element.props.onMouseEnter?.(e)
},
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
setActiveValue(null)
element.props.onMouseLeave?.(e)
}
}
: {
onClick: (e: React.MouseEvent<HTMLDivElement>) => {
setActiveValue(childValue)
element.props.onClick?.(e)
}
}
if (asChild) {
if (mode === 'children') {
return React.cloneElement(
element,
{
key: childValue,
ref: localRef,
className: cn('relative', element.props.className),
...getNonOverridingDataAttributes(element, {
...dataAttributes,
'data-slot': 'motion-highlight-item-container'
}),
...commonHandlers,
...props
},
<>
<AnimatePresence initial={false}>
{isActive && !isDisabled && (
<motion.div
layoutId={`transition-background-${contextId}`}
data-slot='motion-highlight'
className={cn('bg-muted absolute inset-0 z-0', contextClassName, activeClassName)}
transition={itemTransition}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: {
...itemTransition,
delay: (itemTransition?.delay ?? 0) + (exitDelay ?? contextExitDelay ?? 0)
}
}}
{...dataAttributes}
/>
)}
</AnimatePresence>
<div data-slot='motion-highlight-item' className={cn('relative z-[1]', className)} {...dataAttributes}>
{children}
</div>
</>
)
}
return React.cloneElement(element, {
ref: localRef,
...getNonOverridingDataAttributes(element, {
...dataAttributes,
'data-slot': 'motion-highlight-item'
}),
...commonHandlers
})
}
return enabled ? (
<div
key={childValue}
ref={localRef}
data-slot='motion-highlight-item-container'
className={cn(mode === 'children' && 'relative', className)}
{...dataAttributes}
{...props}
{...commonHandlers}
>
{mode === 'children' && (
<AnimatePresence initial={false}>
{isActive && !isDisabled && (
<motion.div
layoutId={`transition-background-${contextId}`}
data-slot='motion-highlight'
className={cn('bg-muted absolute inset-0 z-0', contextClassName, activeClassName)}
transition={itemTransition}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: {
...itemTransition,
delay: (itemTransition?.delay ?? 0) + (exitDelay ?? contextExitDelay ?? 0)
}
}}
{...dataAttributes}
/>
)}
</AnimatePresence>
)}
{React.cloneElement(element, {
className: cn('relative z-[1]', element.props.className),
...getNonOverridingDataAttributes(element, {
...dataAttributes,
'data-slot': 'motion-highlight-item'
})
})}
</div>
) : (
children
)
}
export {
MotionHighlight,
MotionHighlightItem,
useMotionHighlight,
type MotionHighlightProps,
type MotionHighlightItemProps
}

View file

@ -0,0 +1,261 @@
'use client'
import * as React from 'react'
import { motion, type Transition, type HTMLMotionProps } from 'motion/react'
import { cn } from '@/lib/utils'
import { MotionHighlight, MotionHighlightItem } from '@/components/ui/motion-highlight'
type TabsContextType<T extends string> = {
activeValue: T
handleValueChange: (value: T) => void
registerTrigger: (value: T, node: HTMLElement | null) => void
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const TabsContext = React.createContext<TabsContextType<any> | undefined>(undefined)
function useTabs<T extends string = string>(): TabsContextType<T> {
const context = React.useContext(TabsContext)
if (!context) {
throw new Error('useTabs must be used within a TabsProvider')
}
return context
}
type BaseTabsProps = React.ComponentProps<'div'> & {
children: React.ReactNode
}
type UnControlledTabsProps<T extends string = string> = BaseTabsProps & {
defaultValue?: T
value?: never
onValueChange?: never
}
type ControlledTabsProps<T extends string = string> = BaseTabsProps & {
value: T
onValueChange?: (value: T) => void
defaultValue?: never
}
type TabsProps<T extends string = string> = UnControlledTabsProps<T> | ControlledTabsProps<T>
function Tabs<T extends string = string>({
defaultValue,
value,
onValueChange,
children,
className,
...props
}: TabsProps<T>) {
const [activeValue, setActiveValue] = React.useState<T | undefined>(defaultValue ?? undefined)
const triggersRef = React.useRef(new Map<string, HTMLElement>())
const initialSet = React.useRef(false)
const isControlled = value !== undefined
React.useEffect(() => {
if (!isControlled && activeValue === undefined && triggersRef.current.size > 0 && !initialSet.current) {
const firstTab = Array.from(triggersRef.current.keys())[0]
setActiveValue(firstTab as T)
initialSet.current = true
}
}, [activeValue, isControlled])
const registerTrigger = (value: string, node: HTMLElement | null) => {
if (node) {
triggersRef.current.set(value, node)
if (!isControlled && activeValue === undefined && !initialSet.current) {
setActiveValue(value as T)
initialSet.current = true
}
} else {
triggersRef.current.delete(value)
}
}
const handleValueChange = (val: T) => {
if (!isControlled) setActiveValue(val)
else onValueChange?.(val)
}
return (
<TabsContext.Provider
value={{
activeValue: (value ?? activeValue)!,
handleValueChange,
registerTrigger
}}
>
<div data-slot='tabs' className={cn('flex flex-col gap-2', className)} {...props}>
{children}
</div>
</TabsContext.Provider>
)
}
type TabsListProps = React.ComponentProps<'div'> & {
children: React.ReactNode
activeClassName?: string
transition?: Transition
}
function TabsList({
children,
className,
activeClassName,
transition = {
type: 'spring',
stiffness: 200,
damping: 25
},
...props
}: TabsListProps) {
const { activeValue } = useTabs()
return (
<MotionHighlight
controlledItems
className={cn('bg-background rounded-sm shadow-sm', activeClassName)}
value={activeValue}
transition={transition}
>
<div
role='tablist'
data-slot='tabs-list'
className={cn(
'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',
className
)}
{...props}
>
{children}
</div>
</MotionHighlight>
)
}
type TabsTriggerProps = HTMLMotionProps<'button'> & {
value: string
children: React.ReactNode
}
function TabsTrigger({ ref, value, children, className, ...props }: TabsTriggerProps) {
const { activeValue, handleValueChange, registerTrigger } = useTabs()
const localRef = React.useRef<HTMLButtonElement | null>(null)
React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement)
React.useEffect(() => {
registerTrigger(value, localRef.current)
return () => registerTrigger(value, null)
}, [value, registerTrigger])
return (
<MotionHighlightItem value={value} className='size-full'>
<motion.button
ref={localRef}
data-slot='tabs-trigger'
role='tab'
onClick={() => handleValueChange(value)}
data-state={activeValue === value ? 'active' : 'inactive'}
className={cn(
'ring-offset-background focus-visible:ring-ring data-[state=active]:text-foreground z-[1] inline-flex size-full cursor-pointer items-center justify-center rounded-sm px-2 py-1 text-sm font-medium whitespace-nowrap transition-transform focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
className
)}
{...props}
>
{children}
</motion.button>
</MotionHighlightItem>
)
}
type TabsContentsProps = React.ComponentProps<'div'> & {
children: React.ReactNode
transition?: Transition
}
function TabsContents({
children,
className,
transition = {
type: 'spring',
stiffness: 300,
damping: 30,
bounce: 0,
restDelta: 0.01
},
...props
}: TabsContentsProps) {
const { activeValue } = useTabs()
const childrenArray = React.Children.toArray(children)
const activeIndex = childrenArray.findIndex(
(child): child is React.ReactElement<{ value: string }> =>
React.isValidElement(child) &&
typeof child.props === 'object' &&
child.props !== null &&
'value' in child.props &&
child.props.value === activeValue
)
return (
<div data-slot='tabs-contents' className={cn('overflow-hidden', className)} {...props}>
<motion.div className='-mx-2 flex' animate={{ x: activeIndex * -100 + '%' }} transition={transition}>
{childrenArray.map((child, index) => (
<div key={index} className='w-full shrink-0 px-2'>
{child}
</div>
))}
</motion.div>
</div>
)
}
type TabsContentProps = HTMLMotionProps<'div'> & {
value: string
children: React.ReactNode
}
function TabsContent({ children, value, className, ...props }: TabsContentProps) {
const { activeValue } = useTabs()
const isActive = activeValue === value
return (
<motion.div
role='tabpanel'
data-slot='tabs-content'
className={cn('overflow-hidden', className)}
initial={{ filter: 'blur(0px)' }}
animate={{ filter: isActive ? 'blur(0px)' : 'blur(2px)' }}
exit={{ filter: 'blur(0px)' }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
{...props}
>
{children}
</motion.div>
)
}
export {
Tabs,
TabsList,
TabsTrigger,
TabsContents,
TabsContent,
useTabs,
type TabsContextType,
type TabsProps,
type TabsListProps,
type TabsTriggerProps,
type TabsContentsProps,
type TabsContentProps
}

View file

@ -27,11 +27,15 @@ import DropdownSVG from '@/assets/svg/Dropdown'
import FormSVG from '@/assets/svg/Form'
import InputSVG from '@/assets/svg/Input'
import InputOTPSVG from '@/assets/svg/InputOTP'
import PaginationSVG from '@/assets/svg/Pagination'
import PopoverSVG from '@/assets/svg/Popover'
import RadioGroupSVG from '@/assets/svg/RadioGroup'
import SelectSVG from '@/assets/svg/Select'
import SonnerSVG from '@/assets/svg/Sonner'
import SheetSVG from '@/assets/svg/Sheet'
import SwitchSVG from '@/assets/svg/Switch'
import TableSVG from '@/assets/svg/Table'
import TabsSVG from '@/assets/svg/Tabs'
import TextareaSVG from '@/assets/svg/Textarea'
import TooltipSVG from '@/assets/svg/Tooltip'
@ -450,13 +454,11 @@ export const categories: ComponentCategory[] = [
slug: 'collapsible',
name: 'Collapsible',
svg: CollapsibleSVG,
badge: 'New',
breakpoints: {
xl: 2
},
hasAnimation: true,
animation: {
badge: 'New',
breakpoints: {
xl: 2
}
@ -510,7 +512,6 @@ export const categories: ComponentCategory[] = [
slug: 'data-table',
name: 'Data Table',
svg: DataTableSVG,
badge: 'New',
breakpoints: {},
components: [
{ name: 'data-table-01' },
@ -651,7 +652,6 @@ export const categories: ComponentCategory[] = [
slug: 'form',
name: 'Form',
svg: FormSVG,
badge: 'Updated',
breakpoints: {
md: 2
},
@ -784,18 +784,42 @@ export const categories: ComponentCategory[] = [
{ name: 'input-otp-10' }
]
},
{
slug: 'pagination',
name: 'Pagination',
svg: PaginationSVG,
badge: 'New',
breakpoints: {
md: 2
},
components: [
{ name: 'pagination-01' },
{ name: 'pagination-02' },
{ name: 'pagination-03' },
{ name: 'pagination-04' },
{ name: 'pagination-05' },
{ name: 'pagination-06' },
{ name: 'pagination-07' },
{ name: 'pagination-08' },
{ name: 'pagination-09' },
{ name: 'pagination-10' },
{ name: 'pagination-11' },
{ name: 'pagination-12' },
{ name: 'pagination-13' },
{ name: 'pagination-14' },
{ name: 'pagination-15' }
]
},
{
slug: 'popover',
name: 'Popover',
svg: PopoverSVG,
badge: 'New',
breakpoints: {
sm: 2,
xl: 3
},
hasAnimation: true,
animation: {
badge: 'New',
breakpoints: {
sm: 2,
xl: 3
@ -906,6 +930,58 @@ export const categories: ComponentCategory[] = [
{ name: 'select-38' }
]
},
{
slug: 'sheet',
name: 'Sheet',
svg: SheetSVG,
badge: 'New',
breakpoints: {
sm: 2,
md: 3
},
components: [
{ name: 'sheet-01' },
{ name: 'sheet-02' },
{ name: 'sheet-03' },
{ name: 'sheet-04' },
{ name: 'sheet-05' },
{ name: 'sheet-06' },
{ name: 'sheet-07' }
]
},
{
slug: 'sonner',
name: 'Sonner',
badge: 'New',
svg: SonnerSVG,
breakpoints: {
md: 2,
xl: 3
},
components: [
{ name: 'sonner-01' },
{ name: 'sonner-02' },
{ name: 'sonner-03' },
{ name: 'sonner-04' },
{ name: 'sonner-05' },
{ name: 'sonner-06' },
{ name: 'sonner-07' },
{ name: 'sonner-08' },
{ name: 'sonner-09' },
{ name: 'sonner-10' },
{ name: 'sonner-11' },
{ name: 'sonner-12' },
{ name: 'sonner-13' },
{ name: 'sonner-14' },
{ name: 'sonner-15' },
{ name: 'sonner-16' },
{ name: 'sonner-17' },
{ name: 'sonner-18' },
{ name: 'sonner-19' },
{ name: 'sonner-20' }
]
},
{
slug: 'switch',
name: 'Switch',
@ -948,7 +1024,6 @@ export const categories: ComponentCategory[] = [
slug: 'table',
name: 'Table',
svg: TableSVG,
badge: 'New',
breakpoints: {},
components: [
{ name: 'table-01' },
@ -969,6 +1044,53 @@ export const categories: ComponentCategory[] = [
{ name: 'table-16' }
]
},
{
slug: 'tabs',
name: 'Tabs',
svg: TabsSVG,
badge: 'New',
breakpoints: {
xl: 2
},
hasAnimation: true,
animation: {
badge: 'New',
breakpoints: {
xl: 2
}
},
components: [
{ name: 'tabs-01' },
{ name: 'tabs-02' },
{ name: 'tabs-03' },
{ name: 'tabs-04' },
{ name: 'tabs-05' },
{ name: 'tabs-06' },
{ name: 'tabs-07' },
{ name: 'tabs-08' },
{ name: 'tabs-09' },
{ name: 'tabs-10' },
{ name: 'tabs-11' },
{ name: 'tabs-12' },
{ name: 'tabs-13' },
{ name: 'tabs-14' },
{ name: 'tabs-15' },
{ name: 'tabs-16' },
{ name: 'tabs-17' },
{ name: 'tabs-18' },
{ name: 'tabs-19' },
{ name: 'tabs-20' },
{ name: 'tabs-21' },
{ name: 'tabs-22' },
{ name: 'tabs-23' },
{ name: 'tabs-24' },
{ name: 'tabs-25' },
{ name: 'tabs-26' },
{ name: 'tabs-27' },
{ name: 'tabs-28' },
{ name: 'tabs-29' }
]
},
{
slug: 'textarea',
name: 'Textarea',
@ -1005,14 +1127,12 @@ export const categories: ComponentCategory[] = [
slug: 'tooltip',
name: 'Tooltip',
svg: TooltipSVG,
badge: 'New',
breakpoints: {
sm: 2,
lg: 3
},
hasAnimation: true,
animation: {
badge: 'New',
breakpoints: {
sm: 2,
lg: 3
@ -1073,11 +1193,6 @@ export const categories: ComponentCategory[] = [
name: 'Navigation Menu',
isComingSoon: true
},
{
slug: 'pagination',
name: 'Pagination',
isComingSoon: true
},
{
slug: 'progress',
name: 'Progress',
@ -1089,11 +1204,6 @@ export const categories: ComponentCategory[] = [
name: 'Separator',
isComingSoon: true
},
{
slug: 'sheet',
name: 'Sheet',
isComingSoon: true
},
{
slug: 'sidebar',
name: 'Sidebar',
@ -1109,16 +1219,6 @@ export const categories: ComponentCategory[] = [
name: 'Slider',
isComingSoon: true
},
{
slug: 'sonner',
name: 'Sonner',
isComingSoon: true
},
{
slug: 'tabs',
name: 'Tabs',
isComingSoon: true
},
{
slug: 'toggle',
name: 'Toggle',