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",
|
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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user