Feature/dropdown (#179)
This commit is contained in:
parent
3e9c096deb
commit
8738d8faf4
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
44
src/front/Components/DesignSystem/Dropdown/index.tsx
Normal file
44
src/front/Components/DesignSystem/Dropdown/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
.components {
|
||||
max-width: 600px;
|
||||
.inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -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 (
|
||||
<DefaultTemplate title={"DesignSystem"}>
|
||||
<Typography typo={ETypo.DISPLAY_LARGE}>DesignSystem</Typography>
|
||||
<Newsletter isOpen />
|
||||
<Newsletter isOpen={false} />
|
||||
<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"]}>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Navigation latérale</Typography>
|
||||
<SearchBlockList
|
||||
|
Loading…
x
Reference in New Issue
Block a user