Feature/dropdown (#179)

This commit is contained in:
Maxime Sallerin 2024-07-26 09:58:54 +02:00 committed by GitHub
parent 3e9c096deb
commit 8738d8faf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 253 additions and 5 deletions

View File

@ -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);
}
}

View File

@ -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 (
<div className={classes["root"]} onClick={handleOnClick}>
<Typography
typo={isActive ? ETypo.TEXT_MD_SEMIBOLD : ETypo.TEXT_MD_REGULAR}
color={isActive ? ETypoColor.DROPDOWN_CONTRAST_ACTIVE : ETypoColor.DROPDOWN_CONTRAST_DEFAULT}>
{option.label}
</Typography>
{isActive && <IconButton icon={<CheckIcon />} />}
</div>
);
}

View File

@ -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;
}
}
}

View File

@ -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 (
<div className={classNames([classes["root"], openable.isOpen && classes["open"]])}>
{children}
<div className={classes["content"]}>
{options.map((option) => {
return <DropdownOption key={option.id} option={option} onClick={handleSelect} isActive={isActive(option)} />;
})}
</div>
</div>
);
function isActive(option: IOption): boolean {
return selectedOption?.id === option.id;
}
}

View File

@ -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;
}
}

View File

@ -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<IOption | null>(null);
const openable = useOpenable({ defaultOpen: false });
return (
<DropdownMenu options={options} openable={openable} onSelect={setSelectedOption} selectedOption={selectedOption}>
<div
className={classNames([
classes["root"],
openable.isOpen && classes["open"],
disabled && classes["disabled"],
!!selectedOption && classes["active"],
])}
onClick={openable.toggle}>
<div className={classes["content"]}>
<Typography
typo={ETypo.TEXT_MD_REGULAR}
color={!!selectedOption ? ETypoColor.DROPDOWN_CONTRAST_ACTIVE : ETypoColor.DROPDOWN_CONTRAST_DEFAULT}>
{selectedOption?.label ?? placeholder}
</Typography>
</div>
<IconButton icon={<ChevronDownIcon />} />
</div>
</DropdownMenu>
);
}

View File

@ -156,6 +156,9 @@ export enum ETypoColor {
CONTRAST_ACTIVED = "--contrast-actived", CONTRAST_ACTIVED = "--contrast-actived",
NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default", NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default",
NAVIGATION_BUTTON_CONTRAST_ACTIVE = "--navigation-button-contrast-active", 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) { export default function Typography(props: IProps) {

View File

@ -3,6 +3,7 @@
flex-direction: column; flex-direction: column;
gap: 32px; gap: 32px;
.components { .components {
max-width: 600px;
.inputs { .inputs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,12 +1,17 @@
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert"; import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; 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 Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import TextField from "@Front/Components/DesignSystem/Form/TextField"; import TextField from "@Front/Components/DesignSystem/Form/TextField";
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
import Menu from "@Front/Components/DesignSystem/Menu";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import Newsletter from "@Front/Components/DesignSystem/Newsletter"; 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 Table from "@Front/Components/DesignSystem/Table";
import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"; import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; 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 Tabs from "@Front/Components/Elements/Tabs";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import useOpenable from "@Front/Hooks/useOpenable"; import useOpenable from "@Front/Hooks/useOpenable";
import Footer from "@Front/Components/DesignSystem/Footer";
import { import {
ArchiveBoxIcon, ArchiveBoxIcon,
ArrowLongLeftIcon, ArrowLongLeftIcon,
@ -27,9 +31,6 @@ import {
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import classes from "./classes.module.scss"; 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() { export default function DesignSystem() {
const { isOpen, open, close } = useOpenable(); const { isOpen, open, close } = useOpenable();
@ -78,8 +79,28 @@ export default function DesignSystem() {
return ( return (
<DefaultTemplate title={"DesignSystem"}> <DefaultTemplate title={"DesignSystem"}>
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography> <Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
<Newsletter isOpen /> <Newsletter isOpen={false} />
<div className={classes["root"]}> <div className={classes["root"]}>
<div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>
<Dropdown
options={[
{
id: "1",
label: "Option 1",
},
{
id: "2",
label: "Option 2",
},
{
id: "3",
label: "Option 3",
},
]}
placeholder="Placeholder"
/>
</div>
<div className={classes["components"]}> <div className={classes["components"]}>
<Typography typo={ETypo.TEXT_LG_BOLD}>Navigation latérale</Typography> <Typography typo={ETypo.TEXT_LG_BOLD}>Navigation latérale</Typography>
<SearchBlockList <SearchBlockList