diff --git a/src/front/Components/DesignSystem/Autocomplete/classes.module.scss b/src/front/Components/DesignSystem/Autocomplete/classes.module.scss new file mode 100644 index 00000000..452fdaad --- /dev/null +++ b/src/front/Components/DesignSystem/Autocomplete/classes.module.scss @@ -0,0 +1,41 @@ +@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); + } + + &.disabled { + opacity: var(--opacity-disabled, 0.3); + pointer-events: none; + } +} diff --git a/src/front/Components/DesignSystem/Autocomplete/index.tsx b/src/front/Components/DesignSystem/Autocomplete/index.tsx new file mode 100644 index 00000000..d95a7a47 --- /dev/null +++ b/src/front/Components/DesignSystem/Autocomplete/index.tsx @@ -0,0 +1,65 @@ +import useOpenable from "@Front/Hooks/useOpenable"; +import { useState, useEffect, useCallback } from "react"; +import DropdownMenu from "../Dropdown/DropdownMenu"; +import { IOption } from "../Dropdown/DropdownMenu/DropdownOption"; +import SearchBar from "../SearchBar"; + +type IProps = { + options: IOption[]; + placeholder: string; + disabled?: boolean; +}; + +export default function Autocomplete(props: IProps) { + const { options, placeholder, disabled } = props; + const [selectedOption, setSelectedOption] = useState(null); + const [searchValue, setSearchValue] = useState(""); + const [filteredOptions, setFilteredOptions] = useState(options); + const openable = useOpenable({ defaultOpen: false }); + + useEffect(() => { + if (searchValue) { + const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase())); + console.log(filteredOptions); + if (filteredOptions.length === 0) + return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]); + return setFilteredOptions(filteredOptions); + } + return setFilteredOptions(options); + }, [searchValue, options]); + + const handleSearchChange = useCallback( + (value: string) => { + setSearchValue(value); + if (value) { + openable.open(); + } else { + openable.close(); + } + }, + [openable], + ); + + const handleSelectOption = useCallback( + (option: IOption) => { + setSelectedOption(option); + setSearchValue(getLabel(option) || ""); + openable.close(); + }, + [openable], + ); + + function getLabel(option: IOption | null): string | null { + if (!option) return null; + if (typeof option.label === "string") { + return option.label; + } + return `${option.label.text} ${option.label.subtext}`; + } + + return ( + + + + ); +} diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss index e959747b..0cd1dc23 100644 --- a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss @@ -30,4 +30,9 @@ &:active { background-color: var(--dropdown-option-background-pressed); } + + &[data-not-selectable="true"] { + pointer-events: none; + user-select: none; + } } diff --git a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx index 1970ab48..dee89d43 100644 --- a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx @@ -1,4 +1,3 @@ -import IconButton from "@Front/Components/DesignSystem/IconButton"; import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import { CheckIcon } from "@heroicons/react/24/outline"; import { useCallback } from "react"; @@ -8,6 +7,7 @@ import classes from "./classes.module.scss"; export type IOption = { id: string; label: string | { text: string; subtext: string }; + notSelectable?: boolean; }; type IProps = { @@ -22,7 +22,7 @@ export default function DropdownOption(props: IProps) { const handleOnClick = useCallback(() => onClick && onClick(option), [onClick, option]); return ( -
+
{getContent(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 index 19135403..d8742a45 100644 --- a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/classes.module.scss @@ -7,6 +7,7 @@ display: flex; flex-direction: column; gap: 8px; + z-index: 3; padding: var(--spacing-sm, 8px); border-radius: var(--dropdown-radius, 0px); diff --git a/src/front/Components/DesignSystem/SearchBar/classes.module.scss b/src/front/Components/DesignSystem/SearchBar/classes.module.scss index bfb3e58c..a9a21a68 100644 --- a/src/front/Components/DesignSystem/SearchBar/classes.module.scss +++ b/src/front/Components/DesignSystem/SearchBar/classes.module.scss @@ -1,6 +1,8 @@ @import "@Themes/constants.scss"; .root { + height: 56px; + display: flex; padding: var(--spacing-2, 16px) var(--spacing-sm, 8px); align-items: flex-start; @@ -10,6 +12,10 @@ border: 1px solid var(--input-main-border-default, #d7dce0); background: var(--input-background, #fff); + svg { + stroke: var(--button-icon-button-default-default); + } + &:hover { border-radius: var(--input-radius, 0px); border: 1px solid var(--input-main-border-hovered, #b4bec5); @@ -28,6 +34,11 @@ background: var(--input-background, #fff); } + &[data-is-disabled="true"] { + opacity: var(--opacity-disabled, 0.3); + pointer-events: none; + } + .input-container { display: flex; flex: 1; diff --git a/src/front/Components/DesignSystem/SearchBar/index.tsx b/src/front/Components/DesignSystem/SearchBar/index.tsx index 57a7af19..113305ce 100644 --- a/src/front/Components/DesignSystem/SearchBar/index.tsx +++ b/src/front/Components/DesignSystem/SearchBar/index.tsx @@ -1,16 +1,18 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useEffect } from "react"; import classes from "./classes.module.scss"; import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline"; type IProps = { onChange?: (input: string) => void; + value?: string; placeholder?: string; + disabled?: boolean; }; -export default function SearchBar({ onChange, placeholder = "Rechercher" }: IProps) { +export default function SearchBar({ onChange, value: propValue, placeholder = "Rechercher", disabled = false }: IProps) { const [isFocused, setIsFocused] = React.useState(false); - const [value, setValue] = React.useState(""); + const [value, setValue] = React.useState(propValue || ""); const changeValue = useCallback( (value: string) => { @@ -25,8 +27,14 @@ export default function SearchBar({ onChange, placeholder = "Rechercher" }: IPro const handleBlur = () => setIsFocused(false); const clearValue = () => changeValue(""); + useEffect(() => { + if (propValue !== undefined) { + setValue(propValue); + } + }, [propValue]); + return ( -