From aca54b5e9689289af40e3331b08853e2bc81d575 Mon Sep 17 00:00:00 2001 From: Alice Dahan Date: Tue, 16 Jul 2024 17:07:10 +0200 Subject: [PATCH] =?UTF-8?q?feat(site):=20Cr=C3=A9e=20un=20composant=20de?= =?UTF-8?q?=20recherche=20et=20s=C3=A9lection=20d'une=20entreprise.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../source/components/company/SearchField.tsx | 31 ++-- .../SearchableSelectField.tsx | 136 ++++++++++++++++++ 2 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 site/source/design-system/field/SearchableSelectField/SearchableSelectField.tsx diff --git a/site/source/components/company/SearchField.tsx b/site/source/components/company/SearchField.tsx index 6e762ed44..6fa44b24a 100644 --- a/site/source/components/company/SearchField.tsx +++ b/site/source/components/company/SearchField.tsx @@ -6,7 +6,7 @@ import { styled } from 'styled-components' import { ForceThemeProvider } from '@/components/utils/DarkModeContext' import { Message } from '@/design-system' import { Card } from '@/design-system/card' -import { SearchField } from '@/design-system/field' +import { SearchableSelectField } from '@/design-system/field/SearchableSelectField/SearchableSelectField' import { FocusStyle } from '@/design-system/global-style' import { ChevronIcon } from '@/design-system/icons' import { Grid } from '@/design-system/layout' @@ -30,6 +30,7 @@ const StyledCard = styled(Card)` export function EntrepriseSearchField(props: { label?: ReactNode + selectedValue?: ReactNode | null onValue?: () => void onClear?: () => void onSubmit?: (search: Entreprise | null) => void @@ -39,15 +40,22 @@ export function EntrepriseSearchField(props: { const searchFieldProps = { ...props, - label: t('CompanySearchField.label', "Nom de l'entreprise, SIREN ou SIRET"), - description: t( - 'CompanySearchField.description', - 'Le numéro Siret est un numéro de 14 chiffres unique pour chaque entreprise. Exemple : 40123778000127' - ), + label: + !props.selectedValue && + t('CompanySearchField.label', "Nom de l'entreprise, SIREN ou SIRET"), + description: + !props.selectedValue && + t( + 'CompanySearchField.description', + 'Le numéro Siret est un numéro de 14 chiffres unique pour chaque entreprise. Exemple : 40123778000127' + ), onSubmit() { const results = refResults.current props.onSubmit?.(results?.[0] ?? null) }, + onClear() { + props.onClear?.() + }, placeholder: t( 'CompanySearchField.placeholder', 'Exemple : Café de la gare ou 40123778000127' @@ -56,11 +64,7 @@ export function EntrepriseSearchField(props: { const state = useSearchFieldState(searchFieldProps) - const { onValue, onClear, onSubmit } = props - useEffect( - () => (!state.value ? onClear?.() : onValue?.()), - [state.value, onValue, onClear] - ) + const { onSubmit } = props const [searchPending, results] = useSearchCompany(state.value) @@ -71,11 +75,10 @@ export function EntrepriseSearchField(props: { return ( - - {state.value && !searchPending && ( + {state.value && !searchPending && !props.selectedValue && ( )} diff --git a/site/source/design-system/field/SearchableSelectField/SearchableSelectField.tsx b/site/source/design-system/field/SearchableSelectField/SearchableSelectField.tsx new file mode 100644 index 000000000..2e811f9b8 --- /dev/null +++ b/site/source/design-system/field/SearchableSelectField/SearchableSelectField.tsx @@ -0,0 +1,136 @@ +import { useButton } from '@react-aria/button' +import { useSearchField } from '@react-aria/searchfield' +import { + SearchFieldState, + useSearchFieldState, +} from '@react-stately/searchfield' +import { AriaSearchFieldProps } from '@react-types/searchfield' +import { ReactNode, useRef } from 'react' +import { css, styled } from 'styled-components' + +import { SearchIcon } from '@/design-system/icons' +import { Loader } from '@/design-system/icons/Loader' + +import { FocusStyle } from '../../global-style' +import { + StyledContainer, + StyledDescription, + StyledErrorMessage, + StyledInput, + StyledInputContainer, + StyledLabel, +} from '../TextField' + +const SearchInput = styled(StyledInput)` + &, + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + -webkit-appearance: none; + } +` + +const SearchInputContainer = styled(StyledInputContainer)` + padding-left: 0.5rem; + &:focus-within { + ${FocusStyle} + } +` + +const IconContainer = styled.div<{ $hasLabel?: boolean; $hasValue?: boolean }>` + padding: calc( + ${({ $hasLabel = false }) => ($hasLabel ? '1rem' : '0rem')} + 0.5rem + ) + 0 0.5rem; + ${({ $hasValue }) => { + if ($hasValue) { + return css` + width: 100%; + ` + } + }} +` + +const StyledClearButton = styled.button` + position: absolute; + right: 0; + background: transparent; + border: none; + font-size: 2rem; + line-height: 2rem; + height: ${({ theme }) => theme.spacings.xxxl}; + padding: ${({ theme }) => `${theme.spacings.md} ${theme.spacings.xs}`}; + ${({ theme: { darkMode } }) => + darkMode && + css` + color: white !important; + `} +` + +export function SearchableSelectField( + props: AriaSearchFieldProps & { + state?: SearchFieldState + isSearchStalled?: boolean + selectedValue?: ReactNode | null + } +) { + const innerState = useSearchFieldState(props) + const state = props.state || innerState + const ref = useRef(null) + const buttonRef = useRef(null) + const { + labelProps, + inputProps, + descriptionProps, + errorMessageProps, + clearButtonProps, + } = useSearchField(props, state, ref) + const { buttonProps } = useButton(clearButtonProps, buttonRef) + + return ( + + + {props.selectedValue ? ( + + {props.selectedValue} + + ) : ( + <> + + {props.isSearchStalled ? : } + + + + )} + {props.label && ( + + {props.label} + + )} + {(state.value !== '' || props.selectedValue) && ( + + × + + )} + + {props.errorMessage && ( + + {props.errorMessage} + + )} + {props.description && ( + + {props.description} + + )} + + ) +}