diff --git a/src/front/Components/DesignSystem/Typography/index.tsx b/src/front/Components/DesignSystem/Typography/index.tsx
index 04349b33..06d6179c 100644
--- a/src/front/Components/DesignSystem/Typography/index.tsx
+++ b/src/front/Components/DesignSystem/Typography/index.tsx
@@ -8,6 +8,7 @@ type IProps = {
color?: ETypoColor;
className?: string;
title?: string;
+ onClick?: () => void;
};
export enum ETypo {
@@ -142,12 +143,12 @@ export enum ETypoColor {
}
export default function Typography(props: IProps) {
- const { typo, color, className, title, children } = props;
+ const { typo, color, className, title, children, onClick } = props;
const style = color ? ({ "--data-color": `var(${color})` } as React.CSSProperties) : undefined;
return (
-
+
{children}
);
diff --git a/src/front/Components/Elements/Tabs/HorizontalTab/classes.module.scss b/src/front/Components/Elements/Tabs/HorizontalTab/classes.module.scss
new file mode 100644
index 00000000..34782dad
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/HorizontalTab/classes.module.scss
@@ -0,0 +1,16 @@
+.root {
+ padding: 8px 16px;
+ font-size: 16px;
+ letter-spacing: 0.08px;
+
+ border-bottom: 1px solid var(--color-neutral-500);
+ cursor: pointer;
+
+ &[data-is-selected="true"] {
+ border-bottom: 2px solid var(--color-neutral-950, #24282e);
+ }
+
+ &:hover {
+ border-bottom: 2px solid var(--color-neutral-950, #24282e);
+ }
+}
diff --git a/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx b/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx
new file mode 100644
index 00000000..3c0f46fa
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx
@@ -0,0 +1,33 @@
+import { useCallback } from "react";
+import classes from "./classes.module.scss";
+import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
+import useHoverable from "@Front/Hooks/useHoverable";
+export type ITab = {
+ label: React.ReactNode;
+};
+
+export type IProps
= {
+ onSelect: (value: T) => void;
+ value: T;
+ isSelected: boolean;
+} & ITab;
+
+export default function HorizontalTabs(props: IProps) {
+ const onClick = useCallback(() => props.onSelect(props.value), [props]);
+
+ const { isHovered, handleMouseEnter, handleMouseLeave } = useHoverable();
+ return (
+
+
+ {props.label}
+
+
+ );
+}
diff --git a/src/front/Components/Elements/Tabs/VerticalTabs/classes.module.scss b/src/front/Components/Elements/Tabs/VerticalTabs/classes.module.scss
new file mode 100644
index 00000000..e7dfd493
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/VerticalTabs/classes.module.scss
@@ -0,0 +1,6 @@
+.root {
+ padding: 8px 16px;
+ font-size: 16px;
+ letter-spacing: 0.08px;
+ cursor: pointer;
+}
diff --git a/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx b/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx
new file mode 100644
index 00000000..dc901e91
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx
@@ -0,0 +1,23 @@
+import { useCallback } from "react";
+import classes from "./classes.module.scss";
+import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
+export type ITab = {
+ label: React.ReactNode;
+};
+
+export type IProps = {
+ onSelect: (value: T) => void;
+ value: T;
+ isSelected: boolean;
+} & ITab;
+
+export default function VerticalTabs(props: IProps) {
+ const onClick = useCallback(() => props.onSelect(props.value), [props]);
+ return (
+
+
+ {props.label}
+
+
+ );
+}
diff --git a/src/front/Components/Elements/Tabs/classes.module.scss b/src/front/Components/Elements/Tabs/classes.module.scss
new file mode 100644
index 00000000..3498652b
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/classes.module.scss
@@ -0,0 +1,49 @@
+.root {
+ .hidden-tester {
+ display: flex;
+ overflow: hidden;
+ background: red;
+ height: 0px;
+ }
+ .horizontal-container {
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ .horizontal-tab {
+ display: flex;
+ justify-content: space-evenly;
+ overflow: hidden;
+ }
+ }
+
+ .show-more-container {
+ position: relative;
+ .show-more {
+ padding: 8px 16px;
+ display: flex;
+ color: white;
+ font-size: 16px;
+ justify-content: center;
+ align-items: center;
+ color: white;
+ cursor: pointer;
+ border-bottom: 1px solid var(--color-neutral-500);
+ }
+
+ .vertical-container {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ top: 50px;
+ padding: var(--spacing-05, 4px) var(--spacing-2, 16px);
+ background: var(--color-generic-white, #fff);
+
+ border: 1px solid var(--menu-border, #d7dce0);
+ border-radius: var(--menu-radius, 0px);
+ box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1);
+ &[data-visible="false"] {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/src/front/Components/Elements/Tabs/index.tsx b/src/front/Components/Elements/Tabs/index.tsx
new file mode 100644
index 00000000..cb552b36
--- /dev/null
+++ b/src/front/Components/Elements/Tabs/index.tsx
@@ -0,0 +1,116 @@
+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 { useDebounce, useToggle, useWindowSize } from "@uidotdev/usehooks";
+import Button from "@Front/Components/DesignSystem/Button";
+import useOpenable from "@Front/Hooks/useOpenable";
+
+type ITabInternal = ITab & {
+ key?: string;
+ value: T;
+};
+
+type IProps = {
+ tabs: ITabInternal[];
+ onSelect: (value: T) => void;
+};
+
+export default function Tabs(props: IProps) {
+ const { onSelect } = props;
+ const rootRef = useRef(null);
+ const [visibleElements, setVisibleElements] = useState[]>([]);
+ const [overflowedElements, setOverflowedElements] = useState[]>([]);
+
+ const [selectedTab, setSelectedTab] = useState(props.tabs[0]!.value);
+
+ const { close, isOpen, toggle } = useOpenable();
+
+ const windowSize = useWindowSize();
+ const windowSizeDebounced = useDebounce(windowSize, 100);
+
+ const calculateVisibleElements = useCallback(() => {
+ const container = rootRef.current;
+ if (!container) return;
+
+ const containerWidth = container.offsetWidth;
+ let totalWidth = 115;
+ let visibleCount = 0;
+
+ const children = Array.from(container.children) as HTMLDivElement[];
+ for (let i = 0; i < children.length; i++) {
+ totalWidth += children[i]!.offsetWidth;
+ if (totalWidth > containerWidth) {
+ break;
+ }
+ visibleCount++;
+ }
+
+ setVisibleElements(props.tabs.slice(0, visibleCount));
+ setOverflowedElements(props.tabs.slice(visibleCount));
+ }, [props.tabs]);
+
+ useEffect(() => {
+ calculateVisibleElements();
+ }, [calculateVisibleElements, windowSizeDebounced]);
+
+ const handleSelect = useCallback(
+ (value: T) => {
+ setSelectedTab(value);
+ onSelect(value);
+ close();
+ },
+ [close, onSelect],
+ );
+
+ return (
+
+
+ {props.tabs.map((element, index) => (
+
+ label={element.label}
+ key={element.key ?? index}
+ value={element.value}
+ onSelect={handleSelect}
+ isSelected={selectedTab === element.value}
+ />
+ ))}
+
+
+
+ {visibleElements.map((element, index) => (
+
+ label={element.label}
+ key={element.key ?? index}
+ value={element.value}
+ onSelect={handleSelect}
+ isSelected={selectedTab === element.value}
+ />
+ ))}
+
+ {overflowedElements.length > 0 && (
+
+
+
+ {overflowedElements.length} de plus...
+
+
+
+ {overflowedElements.length > 0 &&
+ overflowedElements.map((element, index) => (
+
+ label={element.label}
+ key={element.key ?? index}
+ value={element.value}
+ onSelect={handleSelect}
+ isSelected={selectedTab === element.value}
+ />
+ ))}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/front/Components/Layouts/DesignSystem/index.tsx b/src/front/Components/Layouts/DesignSystem/index.tsx
index c8d45fe9..93c99890 100644
--- a/src/front/Components/Layouts/DesignSystem/index.tsx
+++ b/src/front/Components/Layouts/DesignSystem/index.tsx
@@ -7,11 +7,48 @@ import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import classes from "./classes.module.scss";
+import Tabs from "@Front/Components/Elements/Tabs";
+import { useCallback } from "react";
export default function DesignSystem() {
+ const userDb = [
+ {
+ username: "Maxime",
+ id: 1,
+ },
+ {
+ username: "Vincent",
+ id: 2,
+ },
+ {
+ username: "Massi",
+ id: 3,
+ },
+ {
+ username: "Maxime",
+ id: 4,
+ },
+ {
+ username: "Arnaud",
+ id: 5,
+ },
+ ];
+
+ const onSelect = useCallback((value: (typeof userDb)[number]) => {
+ console.log(value);
+ }, []);
+
return (
+
+ tabs={userDb.map((user) => ({
+ label: user.username,
+ key: user.id.toString(),
+ value: user,
+ }))}
+ onSelect={onSelect}
+ />
DesignSystem
@@ -83,48 +120,48 @@ export default function DesignSystem() {
Buttons
-
+
-
+
-
+
-
+
-
+