✨ AutocompleteField
This commit is contained in:
parent
d777d15db6
commit
07d368fae2
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,11 @@
|
||||
@import "@Themes/constants.scss";
|
||||
|
||||
.root {
|
||||
.hidden-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
.errors-container {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -105,6 +105,7 @@ export default function DesignSystem() {
|
||||
},
|
||||
]}
|
||||
placeholder="Placeholder"
|
||||
label="Label"
|
||||
/>
|
||||
<Typography typo={ETypo.TEXT_LG_BOLD}>Dropdown</Typography>
|
||||
<Dropdown
|
||||
|
Loading…
x
Reference in New Issue
Block a user