Skip to content

Commit

Permalink
feat(tab): add tabs component and handle dynamic tab
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbadri committed Apr 28, 2024
1 parent 0f00d6a commit 03ca70e
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 12 deletions.
21 changes: 9 additions & 12 deletions apps/docs/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { Link, Route, Routes } from 'react-router-dom';

import { Stack } from '@mui/material';

import Builder, { BuilderProps } from '@mui-builder/core';
import Tabs, { TabData } from '@mui-builder/tab';

export function App() {
const children: BuilderProps[] = [
Expand Down Expand Up @@ -219,25 +221,20 @@ export function App() {
},
];

const [tabs, setTabs] = useState<TabData[]>([
{ label: 'Tab 1', content: 'Content for Tab 1' },
]);

return (
<div>
<div role="navigation">
----
<Tabs tabs={tabs} setTabs={setTabs} />
---
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/grid">Grid</Link>
</li>
<li>
<Link to="/utils">Utils</Link>
</li>
<li>
<Link to="/form">Form</Link>
</li>
<li>
<Link to="/core">Core</Link>
</li>
</ul>
</div>
<Routes>
Expand Down
46 changes: 46 additions & 0 deletions packages/tab/src/components/tabs/tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';

import { Stack, Tab } from '@mui/material';
import IconButton from '@mui/material/IconButton';
import TabsMui from '@mui/material/Tabs';

import { TabsProps } from './tabs.types';

import TabPanel from '../tabPanel/tabPanel';
import useTabs from './useTabs';

const Tabs: React.FC<TabsProps> = (props) => {
const {
tabs,
getTabsProps,
getTabProps,
getDeleteTabProps,
showDeleteIcon,
getAddTabProps,
getTabPanelProps,
} = useTabs(props);

return (
<div>
<TabsMui {...getTabsProps()}>
{tabs.map((tab, index) => (
<Tab
{...getTabProps(tab, index)}
icon={
<div {...getDeleteTabProps(index)}>
{showDeleteIcon(index) && 'x'}
</div>
}
/>
))}
<IconButton {...getAddTabProps()}>+</IconButton>
</TabsMui>

{tabs.map((tab, index) => (
<TabPanel {...getTabPanelProps(index)}>{tab.content}</TabPanel>
))}
</div>
);
};

export default Tabs;
20 changes: 20 additions & 0 deletions packages/tab/src/components/tabs/tabs.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TabProps as TabPropsMui } from '@mui/material';

export type TabData = {
label: string;
content: string;
};

export type TabProps = TabPropsMui & {
tab: TabData;
index: number;
setTabs: React.Dispatch<React.SetStateAction<TabData[]>>;
tabs: TabData[];
value: number | null;
setValue: React.Dispatch<React.SetStateAction<number | null>>;
};

export type TabsProps = {
tabs: TabData[];
setTabs: React.Dispatch<React.SetStateAction<TabData[]>>;
};
84 changes: 84 additions & 0 deletions packages/tab/src/components/tabs/useTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from 'react';

import { TabsProps as TabsPropsMui } from '@mui/material';
import { TabProps as TabPropsMui } from '@mui/material';

import { TabData, TabsProps } from './tabs.types';

const useTabs = (props: TabsProps) => {
const { tabs, setTabs } = props;
const [value, setValue] = useState<number | null>(0);
const [deleteIndex, setDeleteIndex] = useState<number | null>(null);

const handleChange = (
event: React.SyntheticEvent,
newValue: number | null
) => {
setValue(newValue);
};

const handleAddTab = () => {
const newTabs = [
...tabs,
{
label: `Tab ${tabs.length + 1}`,
content: `Content for Tab ${tabs.length + 1}`,
},
];
setTabs(newTabs);
setValue(newTabs.length - 1);
};

const handleDeleteTab = (index: number) => {
const newTabs = tabs.filter((_, i) => i !== index);
setTabs(newTabs);
setValue(Math.min(value as number, newTabs.length - 1));
setDeleteIndex(null);
};

const showDeleteIcon = (index: number) => deleteIndex === index;

// Props
const getTabsProps = (): TabsPropsMui => ({
value,
onChange: handleChange,
'aria-label': 'dynamic tabs example',
});

const getTabProps = (tab: TabData, index: number): TabPropsMui => ({
key: index,
label: tab.label,
onMouseEnter: () => setDeleteIndex(index), // Set deleteIndex on hover
onMouseLeave: () => setDeleteIndex(null), // Reset deleteIndex when mouse leaves
iconPosition: 'end',
onClick: () => setValue(index),
});

const getDeleteTabProps = (index: number) => ({
onClick: () => handleDeleteTab(index),
style: { fontSize: '14px' },
});

const getAddTabProps = () => ({
onClick: handleAddTab,
sx: { width: '72px' },
});

const getTabPanelProps = (index: number) => ({
key: index,
value: value,
index: index,
});

return {
tabs,
showDeleteIcon,
getTabsProps,
getTabProps,
getDeleteTabProps,
getAddTabProps,
getTabPanelProps,
};
};

export default useTabs;
7 changes: 7 additions & 0 deletions packages/tab/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { TabData } from './components/tabs/tabs.types';

import TabPanel from './components/tabPanel/tabPanel';
import Tabs from './components/tabs/tabs';

export { TabPanel };

export type { TabData };

export default Tabs;

0 comments on commit 03ca70e

Please sign in to comment.