Autocomplete Multi Select (chip input)
This commit is contained in:
parent
07d368fae2
commit
5f1da86813
@ -6,6 +6,7 @@ import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
|
|||||||
import SearchBar from "../SearchBar";
|
import SearchBar from "../SearchBar";
|
||||||
import Typography, { ETypo, ETypoColor } from "../Typography";
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
|
import { getLabel } from "../Dropdown";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
options: IOption[];
|
options: IOption[];
|
||||||
@ -51,24 +52,21 @@ export default function Autocomplete(props: IProps) {
|
|||||||
}, [selectedOptionProps]);
|
}, [selectedOptionProps]);
|
||||||
|
|
||||||
const handleSelectOption = useCallback(
|
const handleSelectOption = useCallback(
|
||||||
(option: IOption) => {
|
(newOption: IOption, _options: IOption[]) => {
|
||||||
setSelectedOption(option);
|
setSelectedOption(newOption);
|
||||||
setSearchValue(getLabel(option) || "");
|
setSearchValue(getLabel(newOption) || "");
|
||||||
openable.close();
|
openable.close();
|
||||||
},
|
},
|
||||||
[openable],
|
[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 (
|
return (
|
||||||
<DropdownMenu options={filteredOptions} openable={openable} onSelect={handleSelectOption} selectedOption={selectedOption}>
|
<DropdownMenu
|
||||||
|
options={filteredOptions}
|
||||||
|
openable={openable}
|
||||||
|
onSelect={handleSelectOption}
|
||||||
|
selectedOptions={selectedOption ? [selectedOption] : []}
|
||||||
|
>
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
{label && (
|
{label && (
|
||||||
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
||||||
@ -76,7 +74,14 @@ export default function Autocomplete(props: IProps) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<SearchBar placeholder={placeholder} disabled={disabled} onChange={handleSearchChange} value={searchValue} />
|
<SearchBar
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
value={searchValue}
|
||||||
|
onClear={() => setSelectedOption(null)}
|
||||||
|
onFocus={openable.open}
|
||||||
|
/>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
width: fit-content;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 4px 12px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
border-radius: var(--input-chip-radius, 360px);
|
||||||
|
border: 1px solid var(--input-chip-default-border, #b7d1f1);
|
||||||
|
background: var(--input-chip-default-background, #e5eefa);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--input-chip-hovered-background);
|
||||||
|
border-color: var(--input-chip-hovered-border);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import IconButton from "../../IconButton";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "../../Typography";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
text: string;
|
||||||
|
className?: string;
|
||||||
|
onDelete?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Chip(props: IProps) {
|
||||||
|
const { className, text, onDelete } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(classes["root"], className)}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_SEMIBOLD} color={ETypoColor.INPUT_CHIP_CONTRAST}>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
<IconButton icon={<XMarkIcon />} onClick={onDelete} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
border-radius: var(--input-radius, 0px);
|
||||||
|
border: 1px solid var(--input-main-border-filled, #6d7e8a);
|
||||||
|
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);
|
||||||
|
background: var(--input-background, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-has-value="true"] {
|
||||||
|
border-radius: var(--input-radius, 0px);
|
||||||
|
border: 1px solid var(--input-main-border-filled, #6d7e8a);
|
||||||
|
background: var(--input-background, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-is-focused="true"] {
|
||||||
|
border-radius: var(--input-radius, 0px);
|
||||||
|
border: 1px solid var(--input-main-border-focused, #005bcb);
|
||||||
|
background: var(--input-background, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-is-disabled="true"] {
|
||||||
|
opacity: var(--opacity-disabled, 0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 16px var(--spacing-2, 16px);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
min-height: 56px;
|
||||||
|
|
||||||
|
padding: var(--spacing-1-5, 12px) var(--spacing-sm, 8px);
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
color: var(--input-placeholder-filled, #24282e);
|
||||||
|
|
||||||
|
/* text/md/semibold */
|
||||||
|
font-family: var(--font-text-family, Poppins);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-text-weight-semibold, 600);
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0.08px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--input-placeholder-empty, #6d7e8a);
|
||||||
|
/* text/md/regular */
|
||||||
|
font-family: var(--font-text-family, Poppins);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-text-weight-regular, 400);
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0.08px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
import { getLabel } from "../../Dropdown";
|
||||||
|
import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption";
|
||||||
|
import Chip from "../Chip";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
selectedOptions: IOption[];
|
||||||
|
onSelectedOptionsChange: (options: IOption[]) => void;
|
||||||
|
onChange?: (input: string) => void;
|
||||||
|
value?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onClear?: () => void;
|
||||||
|
onFocus?: () => void;
|
||||||
|
onBlur?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ChipContainer(props: IProps) {
|
||||||
|
const {
|
||||||
|
selectedOptions,
|
||||||
|
onChange,
|
||||||
|
value: propValue,
|
||||||
|
placeholder = "Rechercher",
|
||||||
|
disabled = false,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
onSelectedOptionsChange,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
|
const [value, setValue] = React.useState(propValue || "");
|
||||||
|
|
||||||
|
const changeValue = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setValue(value);
|
||||||
|
onChange && onChange(value);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value), [changeValue]);
|
||||||
|
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
setIsFocused(true);
|
||||||
|
onFocus?.();
|
||||||
|
}, [onFocus]);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(
|
||||||
|
(e: React.FocusEvent<HTMLInputElement, Element>) => {
|
||||||
|
setIsFocused(false);
|
||||||
|
onBlur?.();
|
||||||
|
},
|
||||||
|
[onBlur],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChipDelete = useCallback(
|
||||||
|
(option: IOption) => {
|
||||||
|
const newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.id !== option.id);
|
||||||
|
onSelectedOptionsChange && onSelectedOptionsChange(newSelectedOptions);
|
||||||
|
},
|
||||||
|
[selectedOptions, onSelectedOptionsChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (propValue !== undefined) {
|
||||||
|
setValue(propValue);
|
||||||
|
}
|
||||||
|
}, [propValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""} data-is-disabled={disabled}>
|
||||||
|
<div className={classes["content"]}>
|
||||||
|
{selectedOptions.map((option) => (
|
||||||
|
<Chip key={option.id} text={getLabel(option) ?? ""} onDelete={() => onChipDelete(option)} />
|
||||||
|
))}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={classes["input"]}
|
||||||
|
onChange={handleOnChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
.label {
|
||||||
|
padding: 0px var(--spacing-2, 16px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import DropdownMenu from "../Dropdown/DropdownMenu";
|
||||||
|
import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "../Typography";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import ChipContainer from "./ChipContainer";
|
||||||
|
import { getLabel } from "../Dropdown";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
options: IOption[];
|
||||||
|
placeholder?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
label?: string;
|
||||||
|
onSelect?: (option: IOption) => void;
|
||||||
|
selectedOptions?: IOption[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Autocomplete(props: IProps) {
|
||||||
|
const { options, placeholder, disabled, label, selectedOptions: selectedOptionsProps } = props;
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<IOption[] | null>(selectedOptionsProps ?? null);
|
||||||
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
const [filteredOptions, setFilteredOptions] = useState<IOption[]>(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],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOptions(selectedOptionsProps ?? null);
|
||||||
|
}, [selectedOptionsProps]);
|
||||||
|
|
||||||
|
const handleSelectOption = useCallback(
|
||||||
|
(_newOption: IOption, options: IOption[]) => {
|
||||||
|
setSelectedOptions(options);
|
||||||
|
setSearchValue("");
|
||||||
|
openable.close();
|
||||||
|
},
|
||||||
|
[openable],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu
|
||||||
|
options={filteredOptions}
|
||||||
|
openable={openable}
|
||||||
|
onSelect={handleSelectOption}
|
||||||
|
selectedOptions={selectedOptions ? selectedOptions : []}>
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
{label && (
|
||||||
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ChipContainer
|
||||||
|
placeholder={placeholder}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
value={searchValue}
|
||||||
|
onClear={() => setSelectedOptions(null)}
|
||||||
|
onFocus={openable.open}
|
||||||
|
selectedOptions={selectedOptions ?? []}
|
||||||
|
onSelectedOptionsChange={setSelectedOptions}
|
||||||
|
/>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import DropdownOption, { IOption } from "./DropdownOption";
|
import DropdownOption, { IOption } from "./DropdownOption";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
options: IOption[];
|
options: IOption[];
|
||||||
selectedOption: IOption | null;
|
selectedOptions: IOption[];
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
openable: {
|
openable: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -14,21 +14,40 @@ type IProps = {
|
|||||||
close: () => void;
|
close: () => void;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
};
|
};
|
||||||
onSelect?: (option: IOption) => void;
|
onSelect?: (newOption: IOption, options: IOption[]) => void;
|
||||||
};
|
};
|
||||||
export default function DropdownMenu(props: IProps) {
|
export default function DropdownMenu(props: IProps) {
|
||||||
const { children, options, onSelect, openable, selectedOption } = props;
|
const { children, options, onSelect, openable, selectedOptions } = props;
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(option: IOption) => {
|
(option: IOption) => {
|
||||||
onSelect?.(option);
|
const newOptions = selectedOptions.some((selectedOption) => selectedOption.id === option.id)
|
||||||
|
? selectedOptions
|
||||||
|
: [...selectedOptions, option];
|
||||||
|
|
||||||
|
onSelect?.(option, newOptions);
|
||||||
openable.close();
|
openable.close();
|
||||||
},
|
},
|
||||||
[onSelect, openable],
|
[onSelect, openable, selectedOptions],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClickOutside = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||||
|
openable.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[openable],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, [handleClickOutside]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames([classes["root"], openable.isOpen && classes["open"]])}>
|
<div className={classNames([classes["root"], openable.isOpen && classes["open"]])} ref={ref}>
|
||||||
{children}
|
{children}
|
||||||
<div className={classes["content"]}>
|
<div className={classes["content"]}>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
@ -39,6 +58,6 @@ export default function DropdownMenu(props: IProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
function isActive(option: IOption): boolean {
|
function isActive(option: IOption): boolean {
|
||||||
return selectedOption?.id === option.id;
|
return selectedOptions.some((selectedOption) => selectedOption.id === option.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,19 @@ export default function Dropdown(props: IProps) {
|
|||||||
}, [selectedOptionProps]);
|
}, [selectedOptionProps]);
|
||||||
|
|
||||||
const handleOnSelect = useCallback(
|
const handleOnSelect = useCallback(
|
||||||
(option: IOption) => {
|
(newOption: IOption, _options: IOption[]) => {
|
||||||
setSelectedOption(option);
|
setSelectedOption(newOption);
|
||||||
onSelect?.(option);
|
onSelect?.(newOption);
|
||||||
},
|
},
|
||||||
[onSelect],
|
[onSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu options={options} openable={openable} onSelect={handleOnSelect} selectedOption={selectedOption}>
|
<DropdownMenu
|
||||||
|
options={options}
|
||||||
|
openable={openable}
|
||||||
|
onSelect={handleOnSelect}
|
||||||
|
selectedOptions={selectedOption ? [selectedOption] : []}>
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
{label && (
|
{label && (
|
||||||
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
||||||
|
@ -94,6 +94,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.default {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
opacity: var(--opacity-disabled, 0.3);
|
opacity: var(--opacity-disabled, 0.3);
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
|
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
onChange?: (input: string) => void;
|
onChange?: (input: string) => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onClear?: () => void;
|
||||||
|
onFocus?: () => void;
|
||||||
|
onBlur?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchBar({ onChange, value: propValue, placeholder = "Rechercher", disabled = false }: IProps) {
|
export default function SearchBar(props: IProps) {
|
||||||
|
const { onChange, value: propValue, placeholder = "Rechercher", disabled = false, onClear, onFocus, onBlur } = props;
|
||||||
|
|
||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
const [value, setValue] = React.useState(propValue || "");
|
const [value, setValue] = React.useState(propValue || "");
|
||||||
|
|
||||||
@ -22,10 +27,25 @@ export default function SearchBar({ onChange, value: propValue, placeholder = "R
|
|||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value);
|
const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value), [changeValue]);
|
||||||
const handleFocus = () => setIsFocused(true);
|
|
||||||
const handleBlur = () => setIsFocused(false);
|
const handleFocus = useCallback(() => {
|
||||||
const clearValue = () => changeValue("");
|
setIsFocused(true);
|
||||||
|
onFocus?.();
|
||||||
|
}, [onFocus]);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(
|
||||||
|
(e: React.FocusEvent<HTMLInputElement, Element>) => {
|
||||||
|
setIsFocused(false);
|
||||||
|
onBlur?.();
|
||||||
|
},
|
||||||
|
[onBlur],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearValue = useCallback(() => {
|
||||||
|
changeValue("");
|
||||||
|
onClear?.();
|
||||||
|
}, [changeValue, onClear]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (propValue !== undefined) {
|
if (propValue !== undefined) {
|
||||||
|
@ -159,6 +159,8 @@ export enum ETypoColor {
|
|||||||
|
|
||||||
DROPDOWN_CONTRAST_DEFAULT = "--dropdown-contrast-default",
|
DROPDOWN_CONTRAST_DEFAULT = "--dropdown-contrast-default",
|
||||||
DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active",
|
DROPDOWN_CONTRAST_ACTIVE = "--dropdown-contrast-active",
|
||||||
|
|
||||||
|
INPUT_CHIP_CONTRAST = "--input-chip-contrast",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Typography(props: IProps) {
|
export default function Typography(props: IProps) {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
import Alert, { EAlertVariant } from "@Front/Components/DesignSystem/Alert";
|
||||||
|
import Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
|
||||||
|
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
||||||
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 Dropdown from "@Front/Components/DesignSystem/Dropdown";
|
||||||
@ -19,19 +21,10 @@ 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 {
|
import { ArchiveBoxIcon, ArrowLongLeftIcon, ArrowLongRightIcon, EllipsisHorizontalIcon, PencilSquareIcon, UsersIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
ArchiveBoxIcon,
|
|
||||||
ArrowLongLeftIcon,
|
|
||||||
ArrowLongRightIcon,
|
|
||||||
EllipsisHorizontalIcon,
|
|
||||||
PencilSquareIcon,
|
|
||||||
UsersIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
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 Autocomplete from "@Front/Components/DesignSystem/Autocomplete";
|
|
||||||
|
|
||||||
export default function DesignSystem() {
|
export default function DesignSystem() {
|
||||||
const { isOpen, open, close } = useOpenable();
|
const { isOpen, open, close } = useOpenable();
|
||||||
@ -84,6 +77,29 @@ export default function DesignSystem() {
|
|||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
<div />
|
<div />
|
||||||
<div className={classes["components"]}>
|
<div className={classes["components"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete Multi Select</Typography>
|
||||||
|
|
||||||
|
<AutocompleteMultiSelect
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
label: "Option 1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
label: "Option 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
label: "Option 3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
label: { text: "Option 4", subtext: "Subtext" },
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
label="Label"
|
||||||
|
/>
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Autocomplete</Typography>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={[
|
options={[
|
||||||
@ -104,7 +120,6 @@ export default function DesignSystem() {
|
|||||||
label: { text: "Option 4", subtext: "Subtext" },
|
label: { text: "Option 4", subtext: "Subtext" },
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
placeholder="Placeholder"
|
|
||||||
label="Label"
|
label="Label"
|
||||||
/>
|
/>
|
||||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>
|
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user