143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import classes from "./classes.module.scss";
|
|
import HorizontalTab, { ITab } from "./HorizontalTab";
|
|
import VerticalTabs from "./VerticalTabs";
|
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
|
import { useWindowSize } from "@uidotdev/usehooks";
|
|
import useOpenable from "@Front/Hooks/useOpenable";
|
|
|
|
export type ITabValue<T> = T & {
|
|
id: unknown;
|
|
}
|
|
|
|
type ITabInternal<T> = ITab & {
|
|
key?: string;
|
|
value: ITabValue<T>;
|
|
};
|
|
|
|
type IProps<T> = {
|
|
tabs: ITabInternal<T>[];
|
|
onSelect: (value: T) => void;
|
|
};
|
|
|
|
export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
|
|
const tabs = useRef(propsTabs);
|
|
|
|
const shadowElementRef = useRef<HTMLDivElement>(null);
|
|
const [visibleElements, setVisibleElements] = useState<ITabInternal<T>[]>([]);
|
|
const [overflowedElements, setOverflowedElements] = useState<ITabInternal<T>[]>([]);
|
|
|
|
const [selectedTab, setSelectedTab] = useState<ITabValue<T>>(tabs.current[0]!.value);
|
|
|
|
const { close, isOpen, toggle } = useOpenable();
|
|
|
|
const windowSize = useWindowSize();
|
|
|
|
const calculateVisibleElements = useCallback(() => {
|
|
const shadowElement = shadowElementRef.current;
|
|
if (!shadowElement) return;
|
|
|
|
const shadowElementWidth = shadowElement.offsetWidth;
|
|
// The first element is the show more element, it needs to be ignored in the calculation
|
|
let totalWidth = (shadowElement.children[0]! as HTMLElement).offsetWidth;
|
|
|
|
let visibleCount = 0;
|
|
for (let i = 1; i < shadowElement.children.length; i++) {
|
|
totalWidth += (shadowElement.children[i]! as HTMLElement).offsetWidth;
|
|
if (totalWidth > shadowElementWidth) {
|
|
break;
|
|
}
|
|
visibleCount++;
|
|
}
|
|
|
|
setVisibleElements(tabs.current.slice(0, visibleCount));
|
|
setOverflowedElements(tabs.current.slice(visibleCount));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
tabs.current = propsTabs;
|
|
}, [propsTabs]);
|
|
|
|
useEffect(() => {
|
|
calculateVisibleElements();
|
|
}, [calculateVisibleElements, windowSize]);
|
|
|
|
const handleSelect = useCallback(
|
|
(value: ITabValue<T>) => {
|
|
setSelectedTab(value);
|
|
onSelect(value);
|
|
close();
|
|
calculateVisibleElements();
|
|
},
|
|
[close, onSelect, calculateVisibleElements],
|
|
);
|
|
|
|
const handleVerticalSelect = useCallback(
|
|
(value: ITabValue<T>) => {
|
|
const index = tabs.current.findIndex((tab) => tab.value.id === value.id);
|
|
const newTabs = [...tabs.current];
|
|
newTabs.splice(index, 1);
|
|
newTabs.unshift(tabs.current[index]!);
|
|
tabs.current = newTabs;
|
|
console.log("Updated values ; ", tabs.current);
|
|
handleSelect(value);
|
|
},
|
|
[handleSelect],
|
|
);
|
|
|
|
return (
|
|
<div className={classes["root"]}>
|
|
<div className={classes["mirror-shadow-element"]} ref={shadowElementRef}>
|
|
<div className={classes["show-more"]}>
|
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
|
{overflowedElements.length} de plus...
|
|
</Typography>
|
|
</div>
|
|
{tabs.current.map((element, index) => (
|
|
<HorizontalTab<T>
|
|
label={element.label}
|
|
key={element.key ?? index}
|
|
value={element.value}
|
|
onSelect={handleSelect}
|
|
isSelected={element.value.id === selectedTab.id}
|
|
/>
|
|
))}
|
|
</div>
|
|
<div className={classes["horizontal-container"]}>
|
|
<div className={classes["horizontal-tab"]}>
|
|
{visibleElements.map((element, index) => (
|
|
<HorizontalTab<T>
|
|
label={element.label}
|
|
key={element.key ?? index}
|
|
value={element.value}
|
|
onSelect={handleSelect}
|
|
isSelected={element.value.id === selectedTab.id}
|
|
/>
|
|
))}
|
|
</div>
|
|
{overflowedElements.length > 0 && (
|
|
<div className={classes["show-more-container"]}>
|
|
<div className={classes["show-more"]} onClick={toggle}>
|
|
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
|
|
{overflowedElements.length} de plus...
|
|
</Typography>
|
|
</div>
|
|
<div className={classes["vertical-container"]} data-visible={isOpen}>
|
|
{overflowedElements.length > 0 &&
|
|
overflowedElements.map((element, index) => (
|
|
<VerticalTabs<T>
|
|
label={element.label}
|
|
key={element.key ?? index}
|
|
value={element.value}
|
|
onSelect={handleVerticalSelect}
|
|
isSelected={selectedTab === element.value}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|