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..0a331a1d --- /dev/null +++ b/src/front/Components/DesignSystem/Autocomplete/classes.module.scss @@ -0,0 +1,7 @@ +@import "@Themes/constants.scss"; + +.root { + .label { + padding: 0px var(--spacing-2, 16px); + } +} diff --git a/src/front/Components/DesignSystem/Autocomplete/index.tsx b/src/front/Components/DesignSystem/Autocomplete/index.tsx new file mode 100644 index 00000000..8ecfc3db --- /dev/null +++ b/src/front/Components/DesignSystem/Autocomplete/index.tsx @@ -0,0 +1,82 @@ +import useOpenable from "@Front/Hooks/useOpenable"; +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; + disabled?: boolean; + label?: string; + onSelect?: (option: IOption) => void; + selectedOption?: IOption | null; +}; + +export default function Autocomplete(props: IProps) { + const { options, placeholder, disabled, label, selectedOption: selectedOptionProps } = props; + const [selectedOption, setSelectedOption] = useState(selectedOptionProps ?? 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], + ); + + useEffect(() => { + setSelectedOption(selectedOptionProps ?? null); + }, [selectedOptionProps]); + + 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 ( + +
+ {label && ( + + {label} + + )} +
+ +
+ ); +} diff --git a/src/front/Components/DesignSystem/CheckBox/index.tsx b/src/front/Components/DesignSystem/CheckBox/index.tsx index 70f2c91a..d200511d 100644 --- a/src/front/Components/DesignSystem/CheckBox/index.tsx +++ b/src/front/Components/DesignSystem/CheckBox/index.tsx @@ -1,14 +1,14 @@ import React from "react"; -import { IOption } from "../Form/SelectField"; import Tooltip from "../ToolTip"; import Typography, { ETypo, ETypoColor } from "../Typography"; import classes from "./classes.module.scss"; import classNames from "classnames"; +import { IOptionOld } from "../Form/SelectFieldOld"; type IProps = { name?: string; - option: IOption; + option: IOptionOld; toolTip?: string; onChange?: (e: React.ChangeEvent) => void; checked: boolean; 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 884a962d..0cd1dc23 100644 --- a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/classes.module.scss @@ -10,6 +10,18 @@ background: var(--dropdown-option-background-default, #fff); + svg { + width: 24px; + height: 24px; + } + + .content { + display: flex; + flex-direction: column; + align-items: flex-start; + flex: 1 0 0; + } + &:hover { background-color: var(--dropdown-option-background-hovered); } @@ -18,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 05be3eb9..dee89d43 100644 --- a/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx +++ b/src/front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption/index.tsx @@ -3,11 +3,11 @@ 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; + label: string | { text: string; subtext: string }; + notSelectable?: boolean; }; type IProps = { @@ -22,13 +22,35 @@ export default function DropdownOption(props: IProps) { const handleOnClick = useCallback(() => onClick && onClick(option), [onClick, option]); return ( -
- - {option.label} - - {isActive && } />} +
+ {getContent(option.label)} + {isActive && }
); + + function getContent(label: string | { text: string; subtext: string }) { + if (typeof label === "string") { + return ( + + {label} + + ); + } + return ( +
+ + {label.text} + + + {label.subtext} + +
+ ); + } } 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/Dropdown/classes.module.scss b/src/front/Components/DesignSystem/Dropdown/classes.module.scss index 8406a67c..ccb9c08a 100644 --- a/src/front/Components/DesignSystem/Dropdown/classes.module.scss +++ b/src/front/Components/DesignSystem/Dropdown/classes.module.scss @@ -1,45 +1,64 @@ @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); + } + .container { + cursor: pointer; + + display: flex; align-items: center; - flex: 1 0 0; - } + height: 56px; - &:hover { - border-color: var(--dropdown-input-border-hovered); - } + padding: var(--spacing-2, 16px) var(--spacing-sm, 8px); + gap: var(--spacing-2, 16px); - &.active { - border-color: var(--dropdown-input-border-filled); - } + border-radius: var(--input-radius, 0px); + border: 1px solid var(--dropdown-input-border-default, #d7dce0); + background: var(--dropdown-input-background, #fff); - &.open { - border-color: var(--dropdown-input-border-expanded); + .content { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + height: 24px; + .value { + width: 100%; + display: flex; + padding: 0px var(--spacing-2, 16px); + align-items: center; + flex: 1 0 0; + } - svg { - transform: rotate(180deg); + svg { + width: 24px; + height: 24px; + cursor: pointer; + stroke: var(--button-icon-button-default-default); + } + } + + &: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; } } - - &.disabled { - opacity: var(--opacity-disabled, 0.3); - pointer-events: none; - } } diff --git a/src/front/Components/DesignSystem/Dropdown/index.tsx b/src/front/Components/DesignSystem/Dropdown/index.tsx index a819e349..a3875994 100644 --- a/src/front/Components/DesignSystem/Dropdown/index.tsx +++ b/src/front/Components/DesignSystem/Dropdown/index.tsx @@ -1,44 +1,74 @@ import useOpenable from "@Front/Hooks/useOpenable"; +import { ChevronDownIcon } from "@heroicons/react/24/outline"; import classNames from "classnames"; -import { useState } from "react"; +import { useCallback, useEffect, 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; + label?: string; + placeholder?: string; disabled?: boolean; + onSelect?: (option: IOption) => void; + selectedOption?: IOption | null; }; export default function Dropdown(props: IProps) { - const { options, placeholder, disabled } = props; - const [selectedOption, setSelectedOption] = useState(null); + const { options, placeholder, disabled, onSelect, selectedOption: selectedOptionProps, label } = props; + const [selectedOption, setSelectedOption] = useState(selectedOptionProps ?? null); const openable = useOpenable({ defaultOpen: false }); + useEffect(() => { + setSelectedOption(selectedOptionProps ?? null); + }, [selectedOptionProps]); + + const handleOnSelect = useCallback( + (option: IOption) => { + setSelectedOption(option); + onSelect?.(option); + }, + [onSelect], + ); + return ( - -
-
- - {selectedOption?.label ?? placeholder} + +
+ {label && ( + + {label} + )} +
+
+ + {getLabel(selectedOption) ?? placeholder} + + +
- } />
); } + +export 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}`; +} diff --git a/src/front/Components/DesignSystem/Form/AutocompleteField/classes.module.scss b/src/front/Components/DesignSystem/Form/AutocompleteField/classes.module.scss new file mode 100644 index 00000000..caa72fd2 --- /dev/null +++ b/src/front/Components/DesignSystem/Form/AutocompleteField/classes.module.scss @@ -0,0 +1,11 @@ +@import "@Themes/constants.scss"; + +.root { + .hidden-input { + position: absolute; + opacity: 0; + } + .errors-container { + margin-top: 8px; + } +} diff --git a/src/front/Components/DesignSystem/Form/AutocompleteField/index.tsx b/src/front/Components/DesignSystem/Form/AutocompleteField/index.tsx new file mode 100644 index 00000000..ce36524a --- /dev/null +++ b/src/front/Components/DesignSystem/Form/AutocompleteField/index.tsx @@ -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 { + 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 ( +
+ + {this.state.selectedOption && ( + + )} + {this.hasError() &&
{this.renderErrors()}
} +
+ ); + } +} diff --git a/src/front/Components/DesignSystem/Form/BaseField.tsx b/src/front/Components/DesignSystem/Form/BaseField.tsx index 3fe14388..fd16a00a 100644 --- a/src/front/Components/DesignSystem/Form/BaseField.tsx +++ b/src/front/Components/DesignSystem/Form/BaseField.tsx @@ -18,7 +18,7 @@ export type IProps = { label?: string; }; -type IState = { +export type IState = { value: string; validationError: ValidationError | null; }; diff --git a/src/front/Components/DesignSystem/Form/SelectField/classes.module.scss b/src/front/Components/DesignSystem/Form/SelectField/classes.module.scss index 07a3b243..caa72fd2 100644 --- a/src/front/Components/DesignSystem/Form/SelectField/classes.module.scss +++ b/src/front/Components/DesignSystem/Form/SelectField/classes.module.scss @@ -1,143 +1,11 @@ @import "@Themes/constants.scss"; .root { - display: flex; - position: relative; - flex-direction: column; - width: 100%; - border: 1px solid var(--color-neutral-200); - - &[data-errored="true"] { - border: 1px solid var(--color-error-600); + .hidden-input { + position: absolute; + opacity: 0; } - - &[data-disabled="true"] { - .container-label { - cursor: not-allowed; - } - opacity: 0.6; - } - - .container-label { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - background-color: var(--color-generic-white); - cursor: pointer; - padding: 24px; - z-index: 1; - - &[data-border-right-collapsed="true"] { - border-radius: 8px 0 0 8px; - } - - .container-input chevron-icon { - display: flex; - align-items: center; - - span { - display: flex; - - .icon { - display: flex; - margin-right: 8px; - align-items: center; - } - } - - .placeholder { - position: absolute; - top: 24px; - left: 8px; - background-color: var(--color-generic-white); - padding: 0 16px; - - &[data-open="true"] { - transform: translateY(-36px); - } - } - } - - .chevron-icon { - height: 24px; - - fill: var(--color-neutral-500); - transition: all 350ms $custom-easing; - transform: rotate(90deg); - - &[data-open="true"] { - transform: rotate(-90deg); - } - } - } - - .container-ul { - padding-left: 24px; - z-index: 3; - list-style: none; - margin: 0; - outline: 0; - display: flex; - flex-direction: column; - width: 100%; - transition: height 350ms $custom-easing, opacity 350ms $custom-easing; - opacity: 1; - overflow: hidden; - top: 50px; - background-color: var(--color-generic-white); - - &[data-open="false"] { - height: 0; - opacity: 0; - border: none; - } - } - - .container-li { - display: flex; - justify-content: flex-start; - align-items: center; - padding-bottom: 24px; - border-radius: 8px; - cursor: pointer; - background: var(--color-neutral-50); - - &:hover { - background: var(--color-neutral-100); - } - - &:active { - background: var(--color-neutral-200); - } - - span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - - .token-icon { - max-width: 20px; - display: flex; - align-items: center; - margin-right: 11px; - - > svg { - height: 20px; - margin-right: 11px; - } - - > img { - height: 20px; - width: 20px; - } - } - - .backdrop { - position: fixed; - z-index: 1; - inset: 0; + .errors-container { + margin-top: 8px; } } diff --git a/src/front/Components/DesignSystem/Form/SelectField/index.tsx b/src/front/Components/DesignSystem/Form/SelectField/index.tsx index 55418357..3a79d80c 100644 --- a/src/front/Components/DesignSystem/Form/SelectField/index.tsx +++ b/src/front/Components/DesignSystem/Form/SelectField/index.tsx @@ -1,212 +1,65 @@ -import ChevronIcon from "@Assets/Icons/chevron.svg"; -import WindowStore from "@Front/Stores/WindowStore"; -import { ValidationError } from "class-validator"; -import classNames from "classnames"; -import Image from "next/image"; -import React, { FormEvent, ReactNode } from "react"; +import React from "react"; +import { ReactNode } from "react"; -import Typography, { ETypo, ETypoColor } from "../../Typography"; +import { IOption } from "../../Dropdown/DropdownMenu/DropdownOption"; +import BaseField, { IProps as IBaseFieldProps, IState as IBaseFieldState } from "../BaseField"; import classes from "./classes.module.scss"; -import { NextRouter, useRouter } from "next/router"; +import Dropdown from "../../Dropdown"; -type IProps = { - selectedOption?: IOption; - onChange?: (selectedOption: IOption) => void; +export type IProps = IBaseFieldProps & { + onSelect?: (option: IOption) => void; options: IOption[]; - hasBorderRightCollapsed?: boolean; - placeholder?: string; - className?: string; - name: string; - disabled?: boolean; - errors?: ValidationError; + selectedOption?: IOption | null; + label?: string; }; -export type IOption = { - value: unknown; - label: string; - icon?: ReactNode; - description?: string; -}; - -type IState = { - isOpen: boolean; - listWidth: number; - listHeight: number; +type IState = IBaseFieldState & { selectedOption: IOption | null; - errors: ValidationError | null; }; -type IPropsClass = IProps & { - router: NextRouter; -}; - -class SelectFieldClass extends React.Component { - private contentRef = React.createRef(); - private rootRef = React.createRef(); - private removeOnresize = () => {}; - - static defaultProps = { - disabled: false, - }; - - constructor(props: IPropsClass) { +export default class SelectField extends BaseField { + constructor(props: IProps) { super(props); this.state = { - isOpen: false, - listHeight: 0, - listWidth: 0, - selectedOption: null, - errors: this.props.errors ?? null, + selectedOption: this.props.selectedOption ?? null, + ...this.getDefaultState(), }; - this.toggle = this.toggle.bind(this); - this.onSelect = this.onSelect.bind(this); + + this.handleOnChange = this.handleOnChange.bind(this); } - public override render(): JSX.Element { - const selectedOption = this.state.selectedOption ?? this.props.selectedOption; + 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 ( -
-
- {selectedOption && } - - -
    - {this.props.options.map((option, index) => ( -
  • this.onSelect(option, e)}> -
    {option.icon}
    - {option.label} -
  • - ))} -
- - {this.state.isOpen &&
} -
- {this.state.errors !== null &&
{this.renderErrors()}
} +
+ + {this.state.selectedOption && ( + + )} + {this.hasError() &&
{this.renderErrors()}
}
); } - public override componentDidMount(): void { - this.onResize(); - this.removeOnresize = WindowStore.getInstance().onResize(() => this.onResize()); - - this.props.router.events.on("routeChangeStart", () => { - this.setState({ - isOpen: false, - selectedOption: null, - listHeight: 0, - listWidth: 0, - }); - }); - } - - public override componentWillUnmount() { - this.removeOnresize(); - } - - public override componentDidUpdate(prevProps: IProps) { - if (this.props.errors !== prevProps.errors) { - this.setState({ - errors: this.props.errors ?? null, - }); - } - - if (this.props.selectedOption !== prevProps.selectedOption) { - this.setState({ - selectedOption: this.props.selectedOption ?? null, - }); - } - } - - static getDerivedStateFromProps(props: IProps, state: IState) { - if (props.selectedOption?.value) { - return { - value: props.selectedOption?.value, - }; - } - return null; - } - - private onResize() { - let listHeight = 0; - let listWidth = 0; - listWidth = this.rootRef.current?.scrollWidth ?? 0; - if (this.state.listHeight) { - listHeight = this.contentRef.current?.scrollHeight ?? 0; - } - this.setState({ listHeight, listWidth }); - } - - private toggle(e: FormEvent) { - if (this.props.disabled) return; - e.preventDefault(); - let listHeight = 0; - let listWidth = this.rootRef.current?.scrollWidth ?? 0; - - if (!this.state.listHeight) { - listHeight = this.contentRef.current?.scrollHeight ?? 0; - } - - this.setState((state) => { - return { isOpen: !state.isOpen, listHeight, listWidth }; - }); - } - - private onSelect(option: IOption, e: React.MouseEvent) { - if (this.props.disabled) return; - this.props.onChange && this.props.onChange(option); - this.setState({ - selectedOption: option, - }); - this.toggle(e); - } - - private renderErrors(): JSX.Element | null { - if (!this.state.errors) return null; - return ( - - {this.props.placeholder} ne peut pas être vide - - ); - } -} - -export default function SelectField(props: IProps) { - const router = useRouter(); - return ; } diff --git a/src/front/Components/DesignSystem/Form/SelectFieldOld/classes.module.scss b/src/front/Components/DesignSystem/Form/SelectFieldOld/classes.module.scss new file mode 100644 index 00000000..caa72fd2 --- /dev/null +++ b/src/front/Components/DesignSystem/Form/SelectFieldOld/classes.module.scss @@ -0,0 +1,11 @@ +@import "@Themes/constants.scss"; + +.root { + .hidden-input { + position: absolute; + opacity: 0; + } + .errors-container { + margin-top: 8px; + } +} diff --git a/src/front/Components/DesignSystem/Form/SelectFieldOld/index.tsx b/src/front/Components/DesignSystem/Form/SelectFieldOld/index.tsx new file mode 100644 index 00000000..ba077302 --- /dev/null +++ b/src/front/Components/DesignSystem/Form/SelectFieldOld/index.tsx @@ -0,0 +1,212 @@ +import ChevronIcon from "@Assets/Icons/chevron.svg"; +import WindowStore from "@Front/Stores/WindowStore"; +import { ValidationError } from "class-validator"; +import classNames from "classnames"; +import Image from "next/image"; +import React, { FormEvent, ReactNode } from "react"; + +import Typography, { ETypo, ETypoColor } from "../../Typography"; +import classes from "./classes.module.scss"; +import { NextRouter, useRouter } from "next/router"; + +type IProps = { + selectedOption?: IOptionOld; + onChange?: (selectedOption: IOptionOld) => void; + options: IOptionOld[]; + hasBorderRightCollapsed?: boolean; + placeholder?: string; + className?: string; + name: string; + disabled?: boolean; + errors?: ValidationError; +}; + +export type IOptionOld = { + value: unknown; + label: string; + icon?: ReactNode; + description?: string; +}; + +type IState = { + isOpen: boolean; + listWidth: number; + listHeight: number; + selectedOption: IOptionOld | null; + errors: ValidationError | null; +}; + +type IPropsClass = IProps & { + router: NextRouter; +}; + +class SelectFieldClass extends React.Component { + private contentRef = React.createRef(); + private rootRef = React.createRef(); + private removeOnresize = () => {}; + + static defaultProps = { + disabled: false, + }; + + constructor(props: IPropsClass) { + super(props); + this.state = { + isOpen: false, + listHeight: 0, + listWidth: 0, + selectedOption: null, + errors: this.props.errors ?? null, + }; + this.toggle = this.toggle.bind(this); + this.onSelect = this.onSelect.bind(this); + } + + public override render(): JSX.Element { + const selectedOption = this.state.selectedOption ?? this.props.selectedOption; + return ( +
+
+ {selectedOption && } + + +
    + {this.props.options.map((option, index) => ( +
  • this.onSelect(option, e)}> +
    {option.icon}
    + {option.label} +
  • + ))} +
+ + {this.state.isOpen &&
} +
+ {this.state.errors !== null &&
{this.renderErrors()}
} +
+ ); + } + public override componentDidMount(): void { + this.onResize(); + this.removeOnresize = WindowStore.getInstance().onResize(() => this.onResize()); + + this.props.router.events.on("routeChangeStart", () => { + this.setState({ + isOpen: false, + selectedOption: null, + listHeight: 0, + listWidth: 0, + }); + }); + } + + public override componentWillUnmount() { + this.removeOnresize(); + } + + public override componentDidUpdate(prevProps: IProps) { + if (this.props.errors !== prevProps.errors) { + this.setState({ + errors: this.props.errors ?? null, + }); + } + + if (this.props.selectedOption !== prevProps.selectedOption) { + this.setState({ + selectedOption: this.props.selectedOption ?? null, + }); + } + } + + static getDerivedStateFromProps(props: IProps, state: IState) { + if (props.selectedOption?.value) { + return { + value: props.selectedOption?.value, + }; + } + return null; + } + + private onResize() { + let listHeight = 0; + let listWidth = 0; + listWidth = this.rootRef.current?.scrollWidth ?? 0; + if (this.state.listHeight) { + listHeight = this.contentRef.current?.scrollHeight ?? 0; + } + this.setState({ listHeight, listWidth }); + } + + private toggle(e: FormEvent) { + if (this.props.disabled) return; + e.preventDefault(); + let listHeight = 0; + let listWidth = this.rootRef.current?.scrollWidth ?? 0; + + if (!this.state.listHeight) { + listHeight = this.contentRef.current?.scrollHeight ?? 0; + } + + this.setState((state) => { + return { isOpen: !state.isOpen, listHeight, listWidth }; + }); + } + + private onSelect(option: IOptionOld, e: React.MouseEvent) { + if (this.props.disabled) return; + this.props.onChange && this.props.onChange(option); + this.setState({ + selectedOption: option, + }); + this.toggle(e); + } + + private renderErrors(): JSX.Element | null { + if (!this.state.errors) return null; + return ( + + {this.props.placeholder} ne peut pas être vide + + ); + } +} + +export default function SelectField(props: IProps) { + const router = useRouter(); + return ; +} diff --git a/src/front/Components/DesignSystem/MultiSelect/index.tsx b/src/front/Components/DesignSystem/MultiSelect/index.tsx index 535d36c2..d505f4f6 100644 --- a/src/front/Components/DesignSystem/MultiSelect/index.tsx +++ b/src/front/Components/DesignSystem/MultiSelect/index.tsx @@ -1,28 +1,28 @@ +import { ValidationError } from "class-validator"; import classNames from "classnames"; import React from "react"; import ReactSelect, { ActionMeta, MultiValue, Options, PropsValue } from "react-select"; -import { IOption } from "../Form/SelectField"; +import { IOptionOld } from "../Form/SelectFieldOld"; import Typography, { ETypo, ETypoColor } from "../Typography"; import classes from "./classes.module.scss"; import { styles } from "./styles"; -import { ValidationError } from "class-validator"; type IProps = { - options: IOption[]; + options: IOptionOld[]; label?: string | JSX.Element; placeholder?: string; - onChange?: (newValue: MultiValue, actionMeta: ActionMeta) => void; - defaultValue?: PropsValue; - value?: PropsValue; + onChange?: (newValue: MultiValue, actionMeta: ActionMeta) => void; + defaultValue?: PropsValue; + value?: PropsValue; isMulti?: boolean; shouldCloseMenuOnSelect: boolean; - isOptionDisabled?: (option: IOption, selectValue: Options) => boolean; + isOptionDisabled?: (option: IOptionOld, selectValue: Options) => boolean; validationError?: ValidationError; }; type IState = { isFocused: boolean; - selectedOptions: MultiValue; + selectedOptions: MultiValue; validationError: ValidationError | null; }; @@ -124,7 +124,7 @@ export default class MultiSelect extends React.Component { this.setState({ isFocused: false }); } - private onChange(newValue: MultiValue, actionMeta: ActionMeta) { + private onChange(newValue: MultiValue, actionMeta: ActionMeta) { this.props.onChange && this.props.onChange(newValue, actionMeta); this.setState({ selectedOptions: newValue, diff --git a/src/front/Components/DesignSystem/SearchBar/classes.module.scss b/src/front/Components/DesignSystem/SearchBar/classes.module.scss index bfb3e58c..f759e141 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,8 +34,14 @@ background: var(--input-background, #fff); } + &[data-is-disabled="true"] { + opacity: var(--opacity-disabled, 0.3); + pointer-events: none; + } + .input-container { display: flex; + align-items: center; flex: 1; gap: 8px; padding: 0px var(--spacing-2, 16px); 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 ( -