-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add fluid tabs component (#351)
- Loading branch information
1 parent
a1d8297
commit 46ce9d9
Showing
3 changed files
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import FluidTabs from "@/animata/card/fluid-tabs"; | ||
import { Meta, StoryObj } from "@storybook/react"; | ||
|
||
const meta = { | ||
title: "Tabs/Fluid Tabs", | ||
component: FluidTabs, | ||
parameters: { | ||
layout: "centered", | ||
}, | ||
tags: ["autodocs"], | ||
argTypes: {}, | ||
} satisfies Meta<typeof FluidTabs>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Primary: Story = { | ||
args: {}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
"use client"; | ||
|
||
import { useEffect, useRef, useState } from "react"; | ||
import { AnimatePresence, motion } from "framer-motion"; | ||
import { Inbox, Landmark, PieChart } from "lucide-react"; | ||
|
||
const tabs = [ | ||
{ | ||
id: "accounts", | ||
label: "Accounts", | ||
icon: <Landmark size={18} />, | ||
}, | ||
{ | ||
id: "deposits", | ||
label: "Deposits", | ||
icon: <Inbox size={18} />, | ||
}, | ||
{ | ||
id: "funds", | ||
label: "Funds", | ||
icon: <PieChart size={18} />, | ||
}, | ||
]; | ||
|
||
export default function FluidTabs() { | ||
const [activeTab, setActiveTab] = useState("funds"); | ||
const [touchedTab, setTouchedTab] = useState<string | null>(null); | ||
const [prevActiveTab, setPrevActiveTab] = useState("funds"); | ||
const timeoutRef = useRef<NodeJS.Timeout | null>(null); | ||
|
||
useEffect(() => { | ||
return () => { | ||
if (timeoutRef.current) { | ||
clearTimeout(timeoutRef.current); | ||
} | ||
}; | ||
}, []); | ||
|
||
const handleTabClick = (tabId: string) => { | ||
setPrevActiveTab(activeTab); | ||
setActiveTab(tabId); | ||
setTouchedTab(tabId); | ||
|
||
if (timeoutRef.current) { | ||
clearTimeout(timeoutRef.current); | ||
} | ||
timeoutRef.current = setTimeout(() => { | ||
setTouchedTab(null); | ||
}, 300); | ||
}; | ||
|
||
const getTabIndex = (tabId: string) => tabs.findIndex((tab) => tab.id === tabId); | ||
|
||
return ( | ||
<div className="flex items-center justify-center py-4"> | ||
<div className="relative flex w-full max-w-md space-x-2 overflow-hidden rounded-full bg-[#f5f1eb] p-1 shadow-lg"> | ||
<AnimatePresence initial={false}> | ||
<motion.div | ||
key={activeTab} | ||
className="absolute inset-y-0 my-1 rounded-full bg-white" | ||
initial={{ x: `${getTabIndex(prevActiveTab) * 100}%` }} | ||
animate={{ x: `${getTabIndex(activeTab) * 100}%` }} | ||
transition={{ type: "spring", stiffness: 300, damping: 30 }} | ||
style={{ width: `${100 / tabs.length}%` }} | ||
/> | ||
</AnimatePresence> | ||
{tabs.map((tab) => ( | ||
<motion.button | ||
key={tab.id} | ||
className={`relative z-10 flex w-full items-center justify-center gap-1.5 px-5 py-3 text-sm font-bold transition-colors duration-300 ${ | ||
activeTab === tab.id ? "font-bold text-black" : "text-gray-500" | ||
} ${touchedTab === tab.id ? "blur-sm" : ""}`} | ||
onClick={() => handleTabClick(tab.id)} | ||
> | ||
{tab.icon} | ||
{tab.label} | ||
</motion.button> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
--- | ||
title: Fluid Tabs | ||
description: The component is a sliding animation card | ||
author: RudraSankha | ||
--- | ||
|
||
<ComponentPreview name="tabs-fluid-tabs--primary" /> | ||
|
||
## Installation | ||
|
||
<Steps> | ||
<Step>Install dependencies</Step> | ||
|
||
```bash | ||
npm install framer-motion lucide-react | ||
``` | ||
|
||
<Step>Update `tailwind.config.js`</Step> | ||
|
||
Add the following to your tailwind.config.js file. | ||
|
||
```json | ||
module.exports = { | ||
theme: { | ||
extend: { | ||
} | ||
} | ||
} | ||
``` | ||
|
||
<Step>Run the following command</Step> | ||
|
||
It will create a new file `fluid-tabs.tsx` inside the `components/animata/card` directory. | ||
|
||
```bash | ||
mkdir -p components/animata/card && touch components/animata/card/fluid-tabs.tsx | ||
``` | ||
|
||
<Step>Paste the code</Step>{" "} | ||
|
||
Open the newly created file and paste the following code: | ||
|
||
```jsx file=<rootDir>/animata/card/fluid-tabs.tsx | ||
|
||
``` | ||
|
||
</Steps> | ||
|
||
## Credits | ||
|
||
Built by [Rudra Sankha Sinhamahapatra](https://github.com/Rudra-Sankha-Sinhamahapatra) | ||
Twitter Handle [Rudra Sankha](https://x.com/RudraSankha) |