mirror of
https://github.com/shadcnstudio/shadcn-studio
synced 2026-04-21 13:27:16 +00:00
feat(multi): added new component variants
Added Pagination, Sheet, Sonner and Tabs component variants
This commit is contained in:
parent
20c634e8b5
commit
71f477b1db
86 changed files with 6211 additions and 52 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -52,6 +52,7 @@
|
|||
"Dribbble",
|
||||
"Duarte",
|
||||
"Dulce",
|
||||
"Ecommerce",
|
||||
"emblor",
|
||||
"Español",
|
||||
"firstname",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "shadcn-studio",
|
||||
"version": "1.0.0-beta.2",
|
||||
"version": "1.0.0-beta.3",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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' />
|
||||
|
|
|
|||
95
src/assets/svg/Pagination.tsx
Normal file
95
src/assets/svg/Pagination.tsx
Normal 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
79
src/assets/svg/Sheet.tsx
Normal 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
92
src/assets/svg/Sonner.tsx
Normal 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
80
src/assets/svg/Tabs.tsx
Normal 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
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
36
src/components/shadcn-studio/pagination/pagination-01.tsx
Normal file
36
src/components/shadcn-studio/pagination/pagination-01.tsx
Normal 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
|
||||
35
src/components/shadcn-studio/pagination/pagination-02.tsx
Normal file
35
src/components/shadcn-studio/pagination/pagination-02.tsx
Normal 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
|
||||
49
src/components/shadcn-studio/pagination/pagination-03.tsx
Normal file
49
src/components/shadcn-studio/pagination/pagination-03.tsx
Normal 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
|
||||
49
src/components/shadcn-studio/pagination/pagination-04.tsx
Normal file
49
src/components/shadcn-studio/pagination/pagination-04.tsx
Normal 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
|
||||
54
src/components/shadcn-studio/pagination/pagination-05.tsx
Normal file
54
src/components/shadcn-studio/pagination/pagination-05.tsx
Normal 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
|
||||
34
src/components/shadcn-studio/pagination/pagination-06.tsx
Normal file
34
src/components/shadcn-studio/pagination/pagination-06.tsx
Normal 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
|
||||
43
src/components/shadcn-studio/pagination/pagination-07.tsx
Normal file
43
src/components/shadcn-studio/pagination/pagination-07.tsx
Normal 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
|
||||
45
src/components/shadcn-studio/pagination/pagination-08.tsx
Normal file
45
src/components/shadcn-studio/pagination/pagination-08.tsx
Normal 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
|
||||
44
src/components/shadcn-studio/pagination/pagination-09.tsx
Normal file
44
src/components/shadcn-studio/pagination/pagination-09.tsx
Normal 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
|
||||
36
src/components/shadcn-studio/pagination/pagination-10.tsx
Normal file
36
src/components/shadcn-studio/pagination/pagination-10.tsx
Normal 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
|
||||
24
src/components/shadcn-studio/pagination/pagination-11.tsx
Normal file
24
src/components/shadcn-studio/pagination/pagination-11.tsx
Normal 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
|
||||
29
src/components/shadcn-studio/pagination/pagination-12.tsx
Normal file
29
src/components/shadcn-studio/pagination/pagination-12.tsx
Normal 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
|
||||
29
src/components/shadcn-studio/pagination/pagination-13.tsx
Normal file
29
src/components/shadcn-studio/pagination/pagination-13.tsx
Normal 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
|
||||
51
src/components/shadcn-studio/pagination/pagination-14.tsx
Normal file
51
src/components/shadcn-studio/pagination/pagination-14.tsx
Normal 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
|
||||
87
src/components/shadcn-studio/pagination/pagination-15.tsx
Normal file
87
src/components/shadcn-studio/pagination/pagination-15.tsx
Normal 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
|
||||
47
src/components/shadcn-studio/sheet/sheet-01.tsx
Normal file
47
src/components/shadcn-studio/sheet/sheet-01.tsx
Normal 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'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
|
||||
130
src/components/shadcn-studio/sheet/sheet-02.tsx
Normal file
130
src/components/shadcn-studio/sheet/sheet-02.tsx
Normal 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'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'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'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'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
|
||||
47
src/components/shadcn-studio/sheet/sheet-03.tsx
Normal file
47
src/components/shadcn-studio/sheet/sheet-03.tsx
Normal 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'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
|
||||
132
src/components/shadcn-studio/sheet/sheet-04.tsx
Normal file
132
src/components/shadcn-studio/sheet/sheet-04.tsx
Normal 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
|
||||
162
src/components/shadcn-studio/sheet/sheet-05.tsx
Normal file
162
src/components/shadcn-studio/sheet/sheet-05.tsx
Normal 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
|
||||
201
src/components/shadcn-studio/sheet/sheet-06.tsx
Normal file
201
src/components/shadcn-studio/sheet/sheet-06.tsx
Normal 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
|
||||
316
src/components/shadcn-studio/sheet/sheet-07.tsx
Normal file
316
src/components/shadcn-studio/sheet/sheet-07.tsx
Normal 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
|
||||
15
src/components/shadcn-studio/sonner/sonner-01.tsx
Normal file
15
src/components/shadcn-studio/sonner/sonner-01.tsx
Normal 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
|
||||
22
src/components/shadcn-studio/sonner/sonner-02.tsx
Normal file
22
src/components/shadcn-studio/sonner/sonner-02.tsx
Normal 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
|
||||
27
src/components/shadcn-studio/sonner/sonner-03.tsx
Normal file
27
src/components/shadcn-studio/sonner/sonner-03.tsx
Normal 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
|
||||
29
src/components/shadcn-studio/sonner/sonner-04.tsx
Normal file
29
src/components/shadcn-studio/sonner/sonner-04.tsx
Normal 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
|
||||
22
src/components/shadcn-studio/sonner/sonner-05.tsx
Normal file
22
src/components/shadcn-studio/sonner/sonner-05.tsx
Normal 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
|
||||
25
src/components/shadcn-studio/sonner/sonner-06.tsx
Normal file
25
src/components/shadcn-studio/sonner/sonner-06.tsx
Normal 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
|
||||
35
src/components/shadcn-studio/sonner/sonner-07.tsx
Normal file
35
src/components/shadcn-studio/sonner/sonner-07.tsx
Normal 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
|
||||
74
src/components/shadcn-studio/sonner/sonner-08.tsx
Normal file
74
src/components/shadcn-studio/sonner/sonner-08.tsx
Normal 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
|
||||
27
src/components/shadcn-studio/sonner/sonner-09.tsx
Normal file
27
src/components/shadcn-studio/sonner/sonner-09.tsx
Normal 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
|
||||
27
src/components/shadcn-studio/sonner/sonner-10.tsx
Normal file
27
src/components/shadcn-studio/sonner/sonner-10.tsx
Normal 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
|
||||
27
src/components/shadcn-studio/sonner/sonner-11.tsx
Normal file
27
src/components/shadcn-studio/sonner/sonner-11.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-12.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-12.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-13.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-13.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-14.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-14.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-15.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-15.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-16.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-16.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-17.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-17.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-18.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-18.tsx
Normal 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
|
||||
26
src/components/shadcn-studio/sonner/sonner-19.tsx
Normal file
26
src/components/shadcn-studio/sonner/sonner-19.tsx
Normal 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
|
||||
27
src/components/shadcn-studio/sonner/sonner-20.tsx
Normal file
27
src/components/shadcn-studio/sonner/sonner-20.tsx
Normal 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
|
||||
58
src/components/shadcn-studio/tabs/tabs-01.tsx
Normal file
58
src/components/shadcn-studio/tabs/tabs-01.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-02.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-02.tsx
Normal 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's something unexpected—a 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
|
||||
64
src/components/shadcn-studio/tabs/tabs-03.tsx
Normal file
64
src/components/shadcn-studio/tabs/tabs-03.tsx
Normal 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's something unexpected—a 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
|
||||
63
src/components/shadcn-studio/tabs/tabs-04.tsx
Normal file
63
src/components/shadcn-studio/tabs/tabs-04.tsx
Normal 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's something unexpected—a 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
|
||||
64
src/components/shadcn-studio/tabs/tabs-05.tsx
Normal file
64
src/components/shadcn-studio/tabs/tabs-05.tsx
Normal 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's something unexpected—a 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
|
||||
63
src/components/shadcn-studio/tabs/tabs-06.tsx
Normal file
63
src/components/shadcn-studio/tabs/tabs-06.tsx
Normal 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's something unexpected—a 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
|
||||
75
src/components/shadcn-studio/tabs/tabs-07.tsx
Normal file
75
src/components/shadcn-studio/tabs/tabs-07.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-08.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-08.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-09.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-09.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-10.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-10.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-11.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-11.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-12.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-12.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-13.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-13.tsx
Normal 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's something unexpected—a 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
|
||||
122
src/components/shadcn-studio/tabs/tabs-14.tsx
Normal file
122
src/components/shadcn-studio/tabs/tabs-14.tsx
Normal 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's something unexpected—a 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 what’s <span className='text-foreground font-semibold'>trending</span>. Discover what
|
||||
everyone'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, there’s 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'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
|
||||
58
src/components/shadcn-studio/tabs/tabs-15.tsx
Normal file
58
src/components/shadcn-studio/tabs/tabs-15.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-16.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-16.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-17.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-17.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-18.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-18.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-19.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-19.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-20.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-20.tsx
Normal 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's something unexpected—a 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
|
||||
77
src/components/shadcn-studio/tabs/tabs-21.tsx
Normal file
77
src/components/shadcn-studio/tabs/tabs-21.tsx
Normal 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's something unexpected—a 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
|
||||
68
src/components/shadcn-studio/tabs/tabs-22.tsx
Normal file
68
src/components/shadcn-studio/tabs/tabs-22.tsx
Normal 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's something unexpected—a 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
|
||||
67
src/components/shadcn-studio/tabs/tabs-23.tsx
Normal file
67
src/components/shadcn-studio/tabs/tabs-23.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-24.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-24.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-25.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-25.tsx
Normal 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's something unexpected—a 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
|
||||
62
src/components/shadcn-studio/tabs/tabs-26.tsx
Normal file
62
src/components/shadcn-studio/tabs/tabs-26.tsx
Normal 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's something unexpected—a 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
|
||||
72
src/components/shadcn-studio/tabs/tabs-27.tsx
Normal file
72
src/components/shadcn-studio/tabs/tabs-27.tsx
Normal 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's something unexpected—a 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
|
||||
115
src/components/shadcn-studio/tabs/tabs-28.tsx
Normal file
115
src/components/shadcn-studio/tabs/tabs-28.tsx
Normal 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's something unexpected—a 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
|
||||
103
src/components/shadcn-studio/tabs/tabs-29.tsx
Normal file
103
src/components/shadcn-studio/tabs/tabs-29.tsx
Normal 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's something unexpected—a 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
|
||||
551
src/components/ui/motion-highlight.tsx
Normal file
551
src/components/ui/motion-highlight.tsx
Normal 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
|
||||
}
|
||||
261
src/components/ui/motion-tabs.tsx
Normal file
261
src/components/ui/motion-tabs.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue