From 8738d8faf43423686057e26b6e91b7f4b0eec34f Mon Sep 17 00:00:00 2001 From: Maxime Sallerin <97036207+maxime-sallerin@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:58:54 +0200 Subject: [PATCH] Feature/dropdown (#179) --- .../DropdownOption/classes.module.scss | 21 +++++++++ .../DropdownMenu/DropdownOption/index.tsx | 34 ++++++++++++++ .../Dropdown/DropdownMenu/classes.module.scss | 35 +++++++++++++++ .../Dropdown/DropdownMenu/index.tsx | 44 ++++++++++++++++++ .../DesignSystem/Dropdown/classes.module.scss | 45 +++++++++++++++++++ .../DesignSystem/Dropdown/index.tsx | 44 ++++++++++++++++++ .../DesignSystem/Typography/index.tsx | 3 ++ .../Layouts/DesignSystem/classes.module.scss | 1 + .../Components/Layouts/DesignSystem/index.tsx | 31 ++++++++++--- 9 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss create mode 100644 src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx create mode 100644 src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss create mode 100644 src/front/Components/DesignSystem/Dropdown/DropdownMenu/index.tsx create mode 100644 src/front/Components/DesignSystem/Dropdown/classes.module.scss create mode 100644 src/front/Components/DesignSystem/Dropdown/index.tsx diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss new file mode 100644 index 00000000..884a962d --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss @@ -0,0 +1,21 @@ +.root { + display: flex; + padding: var(--spacing-1, 8px) var(--spacing-2, 16px); + align-items: center; + gap: var(--spacing-sm, 8px); + justify-content: space-between; + + border-radius: var(--dropdown-radius, 0px); + border: 1px solid var(--dropdown-border, rgba(0, 0, 0, 0)); + + background: var(--dropdown-option-background-default, #fff); + + &:hover { + background-color: var(--dropdown-option-background-hovered); + } + + &:focus, + &:active { + background-color: var(--dropdown-option-background-pressed); + } +} diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx new file mode 100644 index 00000000..05be3eb9 --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx @@ -0,0 +1,34 @@ +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { CheckIcon } from "@heroicons/react/24/outline"; +import { useCallback } from "react"; + +import classes from "./classes.module.scss"; +import IconButton from "@Front/Components/DesignSystem/IconButton"; + +export type IOption = { + id: string; + label: string; +}; + +type IProps = { + option: IOption; + isActive: boolean; + onClick?: (option: IOption) => void; +}; + +export default function DropdownOption(props: IProps) { + const { option, onClick, isActive } = props; + + const handleOnClick = useCallback(() => onClick && onClick(option), [onClick, option]); + + return ( +
+ + {option.label} + + {isActive && } />} +
+ ); +} diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss new file mode 100644 index 00000000..19135403 --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss @@ -0,0 +1,35 @@ +.root { + position: relative; + overflow: hidden; + + .content { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + + padding: var(--spacing-sm, 8px); + border-radius: var(--dropdown-radius, 0px); + + background: var(--dropdown-menu-background, #fff); + + border: 1px solid var(--dropdown-menu-border-primary, #005bcb); + + max-height: 0; + opacity: 0; + transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out; + + position: absolute; + top: 100%; + left: 0; + transform: translateY(8px); + } + + &.open { + overflow: visible; + .content { + max-height: 500px; + opacity: 1; + } + } +} diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/index.tsx b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/index.tsx new file mode 100644 index 00000000..c19f6982 --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/index.tsx @@ -0,0 +1,44 @@ +import classNames from "classnames"; +import React, { useCallback } from "react"; + +import classes from "./classes.module.scss"; +import DropdownOption, { IOption } from "./DropdownOption"; + +type IProps = { + options: IOption[]; + selectedOption: IOption | null; + children: React.ReactNode; + openable: { + isOpen: boolean; + open: () => void; + close: () => void; + toggle: () => void; + }; + onSelect?: (option: IOption) => void; +}; +export default function DropdownMenu(props: IProps) { + const { children, options, onSelect, openable, selectedOption } = props; + + const handleSelect = useCallback( + (option: IOption) => { + onSelect?.(option); + openable.close(); + }, + [onSelect, openable], + ); + + return ( +
+ {children} +
+ {options.map((option) => { + return ; + })} +
+
+ ); + + function isActive(option: IOption): boolean { + return selectedOption?.id === option.id; + } +} diff --git a/src/front/Components/DesignSystem/Dropdown/classes.module.scss b/src/front/Components/DesignSystem/Dropdown/classes.module.scss new file mode 100644 index 00000000..8406a67c --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/classes.module.scss @@ -0,0 +1,45 @@ +@import "@Themes/constants.scss"; + +.root { + cursor: pointer; + height: 56px; + + display: flex; + align-items: center; + + padding: var(--spacing-2, 16px) var(--spacing-sm, 8px); + gap: var(--spacing-2, 16px); + + border-radius: var(--input-radius, 0px); + border: 1px solid var(--dropdown-input-border-default, #d7dce0); + background: var(--dropdown-input-background, #fff); + + .content { + width: 100%; + display: flex; + padding: 0px var(--spacing-2, 16px); + align-items: center; + flex: 1 0 0; + } + + &:hover { + border-color: var(--dropdown-input-border-hovered); + } + + &.active { + border-color: var(--dropdown-input-border-filled); + } + + &.open { + border-color: var(--dropdown-input-border-expanded); + + svg { + transform: rotate(180deg); + } + } + + &.disabled { + opacity: var(--opacity-disabled, 0.3); + pointer-events: none; + } +} diff --git a/src/front/Components/DesignSystem/Dropdown/index.tsx b/src/front/Components/DesignSystem/Dropdown/index.tsx new file mode 100644 index 00000000..a819e349 --- /dev/null +++ b/src/front/Components/DesignSystem/Dropdown/index.tsx @@ -0,0 +1,44 @@ +import useOpenable from "@Front/Hooks/useOpenable"; +import classNames from "classnames"; +import { useState } from "react"; + +import Typography, { ETypo, ETypoColor } from "../Typography"; +import classes from "./classes.module.scss"; +import DropdownMenu from "./DropdownMenu"; +import { IOption } from "./DropdownMenu/DropdownOption"; +import IconButton from "../IconButton"; +import { ChevronDownIcon } from "@heroicons/react/24/outline"; + +type IProps = { + options: IOption[]; + placeholder: string; + disabled?: boolean; +}; + +export default function Dropdown(props: IProps) { + const { options, placeholder, disabled } = props; + const [selectedOption, setSelectedOption] = useState(null); + const openable = useOpenable({ defaultOpen: false }); + + return ( + +
+
+ + {selectedOption?.label ?? placeholder} + +
+ } /> +
+
+ ); +} diff --git a/src/front/Components/DesignSystem/Typography/index.tsx b/src/front/Components/DesignSystem/Typography/index.tsx index 2aaeec59..9f63599c 100644 --- a/src/front/Components/DesignSystem/Typography/index.tsx +++ b/src/front/Components/DesignSystem/Typography/index.tsx @@ -156,6 +156,9 @@ export enum ETypoColor { CONTRAST_ACTIVED = "--contrast-actived", NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default", NAVIGATION_BUTTON_CONTRAST_ACTIVE = "--navigation-button-contrast-active", + + DROPDOWN_CONTRAST_DEFAULT = "--dropdown-contrast-default", + DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active", } export default function Typography(props: IProps) { diff --git a/src/front/Components/Layouts/DesignSystem/classes.module.scss b/src/front/Components/Layouts/DesignSystem/classes.module.scss index 57f1d643..90be8834 100644 --- a/src/front/Components/Layouts/DesignSystem/classes.module.scss +++ b/src/front/Components/Layouts/DesignSystem/classes.module.scss @@ -3,6 +3,7 @@ flex-direction: column; gap: 32px; .components { + max-width: 600px; .inputs { display: flex; flex-direction: column; diff --git a/src/front/Components/Layouts/DesignSystem/index.tsx b/src/front/Components/Layouts/DesignSystem/index.tsx index 276577a6..708e58a8 100644 --- a/src/front/Components/Layouts/DesignSystem/index.tsx +++ b/src/front/Components/Layouts/DesignSystem/index.tsx @@ -1,12 +1,17 @@ import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; +import Dropdown from "@Front/Components/DesignSystem/Dropdown"; +import Footer from "@Front/Components/DesignSystem/Footer"; import Form from "@Front/Components/DesignSystem/Form"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextField from "@Front/Components/DesignSystem/Form/TextField"; import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; +import Menu from "@Front/Components/DesignSystem/Menu"; import Modal from "@Front/Components/DesignSystem/Modal"; import Newsletter from "@Front/Components/DesignSystem/Newsletter"; +import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList"; +import DropdownNavigation from "@Front/Components/DesignSystem/SearchBlockList/DropdownNavigation"; import Table from "@Front/Components/DesignSystem/Table"; import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; @@ -14,7 +19,6 @@ import NumberPicker from "@Front/Components/Elements/NumberPicker"; import Tabs from "@Front/Components/Elements/Tabs"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import useOpenable from "@Front/Hooks/useOpenable"; -import Footer from "@Front/Components/DesignSystem/Footer"; import { ArchiveBoxIcon, ArrowLongLeftIcon, @@ -27,9 +31,6 @@ import { import { useCallback, useMemo, useState } from "react"; import classes from "./classes.module.scss"; -import Menu from "@Front/Components/DesignSystem/Menu"; -import DropdownNavigation from "@Front/Components/DesignSystem/SearchBlockList/DropdownNavigation"; -import SearchBlockList from "@Front/Components/DesignSystem/SearchBlockList"; export default function DesignSystem() { const { isOpen, open, close } = useOpenable(); @@ -78,8 +79,28 @@ export default function DesignSystem() { return ( DesignSystem - +
+
+ Dropdown + +
Navigation latérale