From 327ba170eff2e2d1af111a944387b7f9831c5179 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Wed, 17 Jul 2024 15:52:34 +0200 Subject: [PATCH 1/3] :sparkles: Tabs almost working --- .../DesignSystem/Typography/index.tsx | 5 +- .../Tabs/HorizontalTab/classes.module.scss | 16 +++ .../Elements/Tabs/HorizontalTab/index.tsx | 33 +++++ .../Tabs/VerticalTabs/classes.module.scss | 6 + .../Elements/Tabs/VerticalTabs/index.tsx | 23 ++++ .../Elements/Tabs/classes.module.scss | 49 ++++++++ src/front/Components/Elements/Tabs/index.tsx | 116 ++++++++++++++++++ .../Components/Layouts/DesignSystem/index.tsx | 67 +++++++--- 8 files changed, 298 insertions(+), 17 deletions(-) create mode 100644 src/front/Components/Elements/Tabs/HorizontalTab/classes.module.scss create mode 100644 src/front/Components/Elements/Tabs/HorizontalTab/index.tsx create mode 100644 src/front/Components/Elements/Tabs/VerticalTabs/classes.module.scss create mode 100644 src/front/Components/Elements/Tabs/VerticalTabs/index.tsx create mode 100644 src/front/Components/Elements/Tabs/classes.module.scss create mode 100644 src/front/Components/Elements/Tabs/index.tsx 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
- +
- +
- +
- +
- +
From 88acd08bcf65c4e8da1b65f16fab2e892c8915f1 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Wed, 17 Jul 2024 15:56:29 +0200 Subject: [PATCH 2/3] :bug: Fixing border bottom --- src/front/Components/Elements/Tabs/classes.module.scss | 2 +- src/front/Components/Elements/Tabs/index.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/front/Components/Elements/Tabs/classes.module.scss b/src/front/Components/Elements/Tabs/classes.module.scss index 3498652b..89a0e389 100644 --- a/src/front/Components/Elements/Tabs/classes.module.scss +++ b/src/front/Components/Elements/Tabs/classes.module.scss @@ -18,6 +18,7 @@ .show-more-container { position: relative; + border-bottom: 1px solid var(--color-neutral-500); .show-more { padding: 8px 16px; display: flex; @@ -27,7 +28,6 @@ align-items: center; color: white; cursor: pointer; - border-bottom: 1px solid var(--color-neutral-500); } .vertical-container { diff --git a/src/front/Components/Elements/Tabs/index.tsx b/src/front/Components/Elements/Tabs/index.tsx index cb552b36..08cc6438 100644 --- a/src/front/Components/Elements/Tabs/index.tsx +++ b/src/front/Components/Elements/Tabs/index.tsx @@ -3,8 +3,7 @@ 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 { useDebounce, useWindowSize } from "@uidotdev/usehooks"; import useOpenable from "@Front/Hooks/useOpenable"; type ITabInternal = ITab & { From e07e5cacde669f17eab20c35a283baa9a51aaa90 Mon Sep 17 00:00:00 2001 From: Maxime Lalo Date: Wed, 17 Jul 2024 16:07:38 +0200 Subject: [PATCH 3/3] :bug: Fixing selected tab --- .../Elements/Tabs/HorizontalTab/index.tsx | 5 +++-- .../Elements/Tabs/VerticalTabs/index.tsx | 5 +++-- src/front/Components/Elements/Tabs/index.tsx | 14 +++++++++----- .../Components/Layouts/DesignSystem/index.tsx | 14 +++++++++++--- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx b/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx index 3c0f46fa..198cd857 100644 --- a/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx +++ b/src/front/Components/Elements/Tabs/HorizontalTab/index.tsx @@ -2,13 +2,14 @@ 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"; +import { ITabValue } from ".."; export type ITab = { label: React.ReactNode; }; export type IProps = { - onSelect: (value: T) => void; - value: T; + onSelect: (value: ITabValue) => void; + value: ITabValue; isSelected: boolean; } & ITab; diff --git a/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx b/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx index dc901e91..e54247bb 100644 --- a/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx +++ b/src/front/Components/Elements/Tabs/VerticalTabs/index.tsx @@ -1,13 +1,14 @@ import { useCallback } from "react"; import classes from "./classes.module.scss"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { ITabValue } from ".."; export type ITab = { label: React.ReactNode; }; export type IProps = { - onSelect: (value: T) => void; - value: T; + onSelect: (value: ITabValue) => void; + value: ITabValue; isSelected: boolean; } & ITab; diff --git a/src/front/Components/Elements/Tabs/index.tsx b/src/front/Components/Elements/Tabs/index.tsx index 08cc6438..bd5d04bd 100644 --- a/src/front/Components/Elements/Tabs/index.tsx +++ b/src/front/Components/Elements/Tabs/index.tsx @@ -6,9 +6,13 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty import { useDebounce, useWindowSize } from "@uidotdev/usehooks"; import useOpenable from "@Front/Hooks/useOpenable"; +export type ITabValue = T & { + id: unknown; +}; + type ITabInternal = ITab & { key?: string; - value: T; + value: ITabValue; }; type IProps = { @@ -22,7 +26,7 @@ export default function Tabs(props: IProps) { const [visibleElements, setVisibleElements] = useState[]>([]); const [overflowedElements, setOverflowedElements] = useState[]>([]); - const [selectedTab, setSelectedTab] = useState(props.tabs[0]!.value); + const [selectedTab, setSelectedTab] = useState>(props.tabs[0]!.value); const { close, isOpen, toggle } = useOpenable(); @@ -55,7 +59,7 @@ export default function Tabs(props: IProps) { }, [calculateVisibleElements, windowSizeDebounced]); const handleSelect = useCallback( - (value: T) => { + (value: ITabValue) => { setSelectedTab(value); onSelect(value); close(); @@ -72,7 +76,7 @@ export default function Tabs(props: IProps) { key={element.key ?? index} value={element.value} onSelect={handleSelect} - isSelected={selectedTab === element.value} + isSelected={element.value.id === selectedTab.id} /> ))}
@@ -84,7 +88,7 @@ export default function Tabs(props: IProps) { key={element.key ?? index} value={element.value} onSelect={handleSelect} - isSelected={selectedTab === element.value} + isSelected={element.value.id === selectedTab.id} /> ))}
diff --git a/src/front/Components/Layouts/DesignSystem/index.tsx b/src/front/Components/Layouts/DesignSystem/index.tsx index 93c99890..37d26764 100644 --- a/src/front/Components/Layouts/DesignSystem/index.tsx +++ b/src/front/Components/Layouts/DesignSystem/index.tsx @@ -8,7 +8,7 @@ import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import classes from "./classes.module.scss"; import Tabs from "@Front/Components/Elements/Tabs"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; export default function DesignSystem() { const userDb = [ @@ -34,13 +34,17 @@ export default function DesignSystem() { }, ]; + const [selectedTab, setSelectedTab] = useState<(typeof userDb)[number]>(userDb[0]!); + const onSelect = useCallback((value: (typeof userDb)[number]) => { - console.log(value); + setSelectedTab(value); }, []); return ( + DesignSystem + Tabs tabs={userDb.map((user) => ({ label: user.username, @@ -49,8 +53,12 @@ export default function DesignSystem() { }))} onSelect={onSelect} /> +
+ + {selectedTab.id} - {selectedTab.username} + +
- DesignSystem
Circle Progress