AutocompleteField

This commit is contained in:
Max S 2024-07-26 16:37:24 +02:00
parent d777d15db6
commit 07d368fae2
7 changed files with 104 additions and 42 deletions

View File

@ -1,41 +1,7 @@
@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;
.label {
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;
}
}

View File

@ -1,18 +1,24 @@
import useOpenable from "@Front/Hooks/useOpenable";
import { useState, useEffect, useCallback } from "react";
import { useCallback, useEffect, useState } from "react";
import DropdownMenu from "../Dropdown/DropdownMenu";
import { IOption } from "../Dropdown/DropdownMenu/DropdownOption";
import SearchBar from "../SearchBar";
import Typography, { ETypo, ETypoColor } from "../Typography";
import classes from "./classes.module.scss";
type IProps = {
options: IOption[];
placeholder: string;
placeholder?: string;
disabled?: boolean;
label?: string;
onSelect?: (option: IOption) => void;
selectedOption?: IOption | null;
};
export default function Autocomplete(props: IProps) {
const { options, placeholder, disabled } = props;
const [selectedOption, setSelectedOption] = useState<IOption | null>(null);
const { options, placeholder, disabled, label, selectedOption: selectedOptionProps } = props;
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
const [searchValue, setSearchValue] = useState("");
const [filteredOptions, setFilteredOptions] = useState<IOption[]>(options);
const openable = useOpenable({ defaultOpen: false });
@ -40,6 +46,10 @@ export default function Autocomplete(props: IProps) {
[openable],
);
useEffect(() => {
setSelectedOption(selectedOptionProps ?? null);
}, [selectedOptionProps]);
const handleSelectOption = useCallback(
(option: IOption) => {
setSelectedOption(option);
@ -59,6 +69,13 @@ export default function Autocomplete(props: IProps) {
return (
<DropdownMenu options={filteredOptions} openable={openable} onSelect={handleSelectOption} selectedOption={selectedOption}>
<div className={classes["root"]}>
{label && (
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
{label}
</Typography>
)}
</div>
<SearchBar placeholder={placeholder} disabled={disabled} onChange={handleSearchChange} value={searchValue} />
</DropdownMenu>
);

View File

@ -18,7 +18,7 @@ type IProps = {
};
export default function Dropdown(props: IProps) {
const { options, placeholder, disabled, onSelect, selectedOption: selectedOptionProps } = props;
const { options, placeholder, disabled, onSelect, selectedOption: selectedOptionProps, label } = props;
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
const openable = useOpenable({ defaultOpen: false });
@ -37,9 +37,9 @@ export default function Dropdown(props: IProps) {
return (
<DropdownMenu options={options} openable={openable} onSelect={handleOnSelect} selectedOption={selectedOption}>
<div className={classes["root"]}>
{props.label && (
{label && (
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
{props.label}
{label}
</Typography>
)}
<div

View File

@ -0,0 +1,11 @@
@import "@Themes/constants.scss";
.root {
.hidden-input {
position: absolute;
opacity: 0;
}
.errors-container {
margin-top: 8px;
}
}

View File

@ -0,0 +1,65 @@
import React from "react";
import { ReactNode } from "react";
import Autocomplete from "../../Autocomplete";
import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption";
import BaseField, { IProps as IBaseFieldProps, IState as IBaseFieldState } from "../BaseField";
import classes from "./classes.module.scss";
export type IProps = IBaseFieldProps & {
onSelect?: (option: IOption) => void;
options: IOption[];
selectedOption?: IOption | null;
label?: string;
};
type IState = IBaseFieldState & {
selectedOption: IOption | null;
};
export default class AutocompleteField extends BaseField<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
selectedOption: this.props.selectedOption ?? null,
...this.getDefaultState(),
};
this.handleOnChange = this.handleOnChange.bind(this);
}
private handleOnChange = (option: IOption) => {
this.setState({ selectedOption: option });
this.props.onSelect?.(option);
};
public override componentDidUpdate(prevProps: IProps): void {
if (prevProps.selectedOption !== this.props.selectedOption) {
this.setState({ selectedOption: this.props.selectedOption ?? null });
}
}
public override render(): ReactNode {
return (
<div className={classes["root"]}>
<Autocomplete
options={this.props.options}
placeholder={this.props.placeholder}
onSelect={this.handleOnChange}
selectedOption={this.state.selectedOption}
label={this.props.label}
/>
{this.state.selectedOption && (
<input
className={classes["hidden-input"]}
name={this.props.name}
type="text"
defaultValue={this.state.selectedOption.id}
hidden
/>
)}
{this.hasError() && <div className={classes["errors-container"]}>{this.renderErrors()}</div>}
</div>
);
}
}

View File

@ -10,6 +10,7 @@ export type IProps = IBaseFieldProps & {
onSelect?: (option: IOption) => void;
options: IOption[];
selectedOption?: IOption | null;
label?: string;
};
type IState = IBaseFieldState & {
@ -46,6 +47,7 @@ export default class SelectField extends BaseField<IProps, IState> {
placeholder={this.props.placeholder}
onSelect={this.handleOnChange}
selectedOption={this.state.selectedOption}
label={this.props.label}
/>
{this.state.selectedOption && (
<input

View File

@ -105,6 +105,7 @@ export default function DesignSystem() {
},
]}
placeholder="Placeholder"
label="Label"
/>
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>
<Dropdown