reviewed code

This commit is contained in:
Maxime Lalo 2024-07-18 11:42:53 +02:00
parent e07e5cacde
commit 69595e652a
3 changed files with 95 additions and 65 deletions

View File

@ -1,5 +1,5 @@
.root { .root {
.hidden-tester { .mirror-shadow-element {
display: flex; display: flex;
overflow: hidden; overflow: hidden;
background: red; background: red;
@ -19,16 +19,6 @@
.show-more-container { .show-more-container {
position: relative; position: relative;
border-bottom: 1px solid var(--color-neutral-500); border-bottom: 1px solid var(--color-neutral-500);
.show-more {
padding: 8px 16px;
display: flex;
color: white;
font-size: 16px;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
}
.vertical-container { .vertical-container {
position: absolute; position: absolute;
@ -46,4 +36,15 @@
} }
} }
} }
.show-more {
padding: 8px 16px;
display: flex;
color: white;
font-size: 16px;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
}
} }

View File

@ -3,7 +3,7 @@ import classes from "./classes.module.scss";
import HorizontalTab, { ITab } from "./HorizontalTab"; import HorizontalTab, { ITab } from "./HorizontalTab";
import VerticalTabs from "./VerticalTabs"; import VerticalTabs from "./VerticalTabs";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { useDebounce, useWindowSize } from "@uidotdev/usehooks"; import { useWindowSize } from "@uidotdev/usehooks";
import useOpenable from "@Front/Hooks/useOpenable"; import useOpenable from "@Front/Hooks/useOpenable";
export type ITabValue<T> = T & { export type ITabValue<T> = T & {
@ -20,57 +20,80 @@ type IProps<T> = {
onSelect: (value: T) => void; onSelect: (value: T) => void;
}; };
export default function Tabs<T>(props: IProps<T>) { export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
const { onSelect } = props; const tabs = useRef(propsTabs);
const rootRef = useRef<HTMLDivElement>(null);
const shadowElementRef = useRef<HTMLDivElement>(null);
const [visibleElements, setVisibleElements] = useState<ITabInternal<T>[]>([]); const [visibleElements, setVisibleElements] = useState<ITabInternal<T>[]>([]);
const [overflowedElements, setOverflowedElements] = useState<ITabInternal<T>[]>([]); const [overflowedElements, setOverflowedElements] = useState<ITabInternal<T>[]>([]);
const [selectedTab, setSelectedTab] = useState<ITabValue<T>>(props.tabs[0]!.value); const [selectedTab, setSelectedTab] = useState<ITabValue<T>>(tabs.current[0]!.value);
const { close, isOpen, toggle } = useOpenable(); const { close, isOpen, toggle } = useOpenable();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const windowSizeDebounced = useDebounce(windowSize, 100);
const calculateVisibleElements = useCallback(() => { const calculateVisibleElements = useCallback(() => {
const container = rootRef.current; const shadowElement = shadowElementRef.current;
if (!container) return; 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;
const containerWidth = container.offsetWidth;
let totalWidth = 115;
let visibleCount = 0; let visibleCount = 0;
for (let i = 1; i < shadowElement.children.length; i++) {
const children = Array.from(container.children) as HTMLDivElement[]; totalWidth += (shadowElement.children[i]! as HTMLElement).offsetWidth;
for (let i = 0; i < children.length; i++) { if (totalWidth > shadowElementWidth) {
totalWidth += children[i]!.offsetWidth;
if (totalWidth > containerWidth) {
break; break;
} }
visibleCount++; visibleCount++;
} }
setVisibleElements(props.tabs.slice(0, visibleCount)); setVisibleElements(tabs.current.slice(0, visibleCount));
setOverflowedElements(props.tabs.slice(visibleCount)); setOverflowedElements(tabs.current.slice(visibleCount));
}, [props.tabs]); }, []);
useEffect(() => {
tabs.current = propsTabs;
}, [propsTabs]);
useEffect(() => { useEffect(() => {
calculateVisibleElements(); calculateVisibleElements();
}, [calculateVisibleElements, windowSizeDebounced]); }, [calculateVisibleElements, windowSize]);
const handleSelect = useCallback( const handleSelect = useCallback(
(value: ITabValue<T>) => { (value: ITabValue<T>) => {
setSelectedTab(value); setSelectedTab(value);
onSelect(value); onSelect(value);
close(); close();
calculateVisibleElements();
}, },
[close, onSelect], [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 ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["hidden-tester"]} ref={rootRef}> <div className={classes["mirror-shadow-element"]} ref={shadowElementRef}>
{props.tabs.map((element, index) => ( <div className={classes["show-more"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_NEUTRAL_500}>
{overflowedElements.length}&nbsp;de&nbsp;plus...
</Typography>
</div>
{tabs.current.map((element, index) => (
<HorizontalTab<T> <HorizontalTab<T>
label={element.label} label={element.label}
key={element.key ?? index} key={element.key ?? index}
@ -106,7 +129,7 @@ export default function Tabs<T>(props: IProps<T>) {
label={element.label} label={element.label}
key={element.key ?? index} key={element.key ?? index}
value={element.value} value={element.value}
onSelect={handleSelect} onSelect={handleVerticalSelect}
isSelected={selectedTab === element.value} isSelected={selectedTab === element.value}
/> />
))} ))}

View File

@ -8,31 +8,44 @@ import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Tabs from "@Front/Components/Elements/Tabs"; import Tabs from "@Front/Components/Elements/Tabs";
import { useCallback, useState } from "react"; import { useCallback, useMemo, useState } from "react";
export default function DesignSystem() { export default function DesignSystem() {
const userDb = [ const userDb = useMemo(
{ () => [
username: "Maxime", {
id: 1, username: "Maxime",
}, id: 1,
{ },
username: "Vincent", {
id: 2, username: "Vincent",
}, id: 2,
{ },
username: "Massi", {
id: 3, username: "Massi",
}, id: 3,
{ },
username: "Maxime", {
id: 4, username: "Maxime",
}, id: 4,
{ },
username: "Arnaud", {
id: 5, username: "Arnaud",
}, id: 5,
]; },
],
[],
);
const userDbArray = useMemo(
() =>
userDb.map((user) => ({
label: user.username,
key: user.id.toString(),
value: user,
})),
[userDb],
);
const [selectedTab, setSelectedTab] = useState<(typeof userDb)[number]>(userDb[0]!); const [selectedTab, setSelectedTab] = useState<(typeof userDb)[number]>(userDb[0]!);
@ -45,14 +58,7 @@ export default function DesignSystem() {
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography> <Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
<Newsletter isOpen /> <Newsletter isOpen />
<Typography typo={ETypo.TEXT_LG_BOLD}>Tabs</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Tabs</Typography>
<Tabs<(typeof userDb)[number]> <Tabs<(typeof userDb)[number]> tabs={userDbArray} onSelect={onSelect} />
tabs={userDb.map((user) => ({
label: user.username,
key: user.id.toString(),
value: user,
}))}
onSelect={onSelect}
/>
<div className={classes["tab-content"]}> <div className={classes["tab-content"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR}> <Typography typo={ETypo.TEXT_MD_REGULAR}>
{selectedTab.id} - {selectedTab.username} {selectedTab.id} - {selectedTab.username}