From 7aca42393184a28defb7ffc6bdf72978a4ea5e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Wed, 26 Apr 2023 11:24:17 +0200 Subject: [PATCH] Replace popper by floating-ui; add aria label --- site/package.json | 2 +- site/source/design-system/field/DateField.tsx | 149 ++++++++++-------- site/source/design-system/tooltip/Tooltip.tsx | 1 + yarn.lock | 60 ++++--- 4 files changed, 114 insertions(+), 98 deletions(-) diff --git a/site/package.json b/site/package.json index ed109f337..aa99d1c3a 100644 --- a/site/package.json +++ b/site/package.json @@ -52,6 +52,7 @@ "@atomik-color/component": "^1.0.17", "@atomik-color/core": "^1.0.13", "@axe-core/react": "^4.5.2", + "@floating-ui/react-dom": "^1.3.0", "@internationalized/number": "^3.2.0", "@leeoniya/ufuzzy": "^1.0.2", "@popperjs/core": "^2.11.7", @@ -98,7 +99,6 @@ "react-i18next": "^12.1.5", "react-instantsearch": "^6.38.1", "react-instantsearch-dom": "^6.38.1", - "react-popper": "^2.3.0", "react-redux": "^8.0.5", "react-router-dom": "^6.4.4", "react-signature-pad-wrapper": "^3.3.1", diff --git a/site/source/design-system/field/DateField.tsx b/site/source/design-system/field/DateField.tsx index 74d299929..cd8d1bba6 100644 --- a/site/source/design-system/field/DateField.tsx +++ b/site/source/design-system/field/DateField.tsx @@ -1,34 +1,29 @@ import 'react-day-picker/dist/style.css' +import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react-dom' import { format as formatDate, isValid, parse } from 'date-fns' import { enUS, fr } from 'date-fns/locale' import FocusTrap from 'focus-trap-react' import { useCallback, useEffect, useRef, useState } from 'react' import { useId } from 'react-aria' import { DayPicker, useInput } from 'react-day-picker' -import { Trans, useTranslation } from 'react-i18next' -import { usePopper } from 'react-popper' +import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { useOnClickOutside } from '@/hooks/useClickOutside' import { Button } from '../buttons' import { Emoji } from '../emoji' -import { Spacing } from '../layout' import { Body } from '../typography/paragraphs' import TextField from './TextField' interface DateFieldProps { defaultSelected?: Date - inputValue?: string onChange?: (value?: string) => void placeholder?: string label?: string 'aria-label'?: string 'aria-labelby'?: string - - autoFocus?: boolean - isRequired?: boolean } export default function DateField(props: DateFieldProps) { @@ -40,15 +35,7 @@ export default function DateField(props: DateFieldProps) { const [isChangeOnce, setIsChangeOnce] = useState(false) const [selected, setSelected] = useState() - const [isPopperOpen, setIsPopperOpen] = useState(false) - - const popperRef = useRef(null) - const dayPickerRef = useRef(null) - const buttonRef = useRef(null) - const containerRef = useRef(null) - const [popperElement, setPopperElement] = useState( - null - ) + const [isOpen, setIsOpen] = useState(false) const id = useId() @@ -66,19 +53,27 @@ export default function DateField(props: DateFieldProps) { inputProps.value as string ) - const popper = usePopper(popperRef.current, popperElement, { - placement: 'bottom-end', + const { x, y, strategy, refs } = useFloating({ + open: isOpen, + placement: 'bottom', + middleware: [offset(8), flip()], + whileElementsMounted: autoUpdate, }) - useOnClickOutside(dayPickerRef, () => setIsPopperOpen(false)) + useOnClickOutside(refs.floating, () => setIsOpen(false)) - const closePopper = () => { - setIsPopperOpen(false) - buttonRef?.current?.focus() - } + const close = useCallback(() => { + setIsOpen((open) => { + if (open) { + refs.reference?.current?.focus() + } + + return false + }) + }, [refs.reference]) const handleInputChange = (value: string) => { - setIsPopperOpen(false) + setIsOpen(false) setIsChangeOnce(true) setInputValue(value) const date = parse(value, format, new Date()) @@ -96,7 +91,7 @@ export default function DateField(props: DateFieldProps) { } const handleButtonPress = () => { - setIsPopperOpen((open) => !open) + setIsOpen((open) => !open) } const handleDaySelect = useCallback( @@ -105,14 +100,14 @@ export default function DateField(props: DateFieldProps) { if (date) { const value = formatDate(date, format) setInputValue(value) - closePopper() + close() onChange?.(value) } else { setInputValue('') onChange?.() } }, - [onChange] + [close, onChange] ) const oldDefaultSelected = useRef(defaultSelected) @@ -127,11 +122,15 @@ export default function DateField(props: DateFieldProps) { }, [defaultSelected, handleDaySelect]) return ( -
- +
+ { @@ -157,13 +156,13 @@ export default function DateField(props: DateFieldProps) { } /> - {isPopperOpen && ( + {isOpen && ( { if (e.key === 'Escape') { - closePopper() + close() } }} - aria-label="Calendrier de selection de date" + aria-label="Calendrier de sélection de date" > -
- - - -
+ + t('design-system.date-picker.month', 'Mois'), + labelYearDropdown: () => + t('design-system.date-picker.year', 'Année'), + labelNext: () => + t('design-system.date-picker.next-month', 'Mois suivant'), + labelPrevious: () => + t('design-system.date-picker.prev-month', 'Mois précédent'), + }} + locale={language === 'fr' ? fr : enUS} + footer={ +
+ +
+ } + />
)} @@ -237,20 +251,23 @@ const StyledBody = styled(Body)` ? theme.colors.extended.grey[700] : theme.colors.extended.grey[200]}; box-shadow: ${({ theme }) => - theme.darkMode ? theme.elevationsDarkMode[2] : theme.elevations[2]}; + theme.darkMode ? theme.elevationsDarkMode[3] : theme.elevations[3]}; ` const Wrapper = styled.div` width: fit-content; position: relative; + & input { + height: 3.5rem; + } ` const StyledButton = styled(Button)` + position: absolute; max-width: 55px; right: 0; top: 0; - margin: ${({ theme }) => theme.spacings.xs}; - position: absolute; + margin: 0.7rem; ` type OnlyAriaType = { diff --git a/site/source/design-system/tooltip/Tooltip.tsx b/site/source/design-system/tooltip/Tooltip.tsx index 65be22fd7..6326950d2 100644 --- a/site/source/design-system/tooltip/Tooltip.tsx +++ b/site/source/design-system/tooltip/Tooltip.tsx @@ -5,6 +5,7 @@ import 'react-tooltip/dist/react-tooltip.css' import styled from 'styled-components' +// TODO: Replace react-tooltip with @floating-ui/react-dom for more control (see DateField.tsx for example) export const Tooltip = ({ children, tooltip, diff --git a/yarn.lock b/yarn.lock index f568d9cbf..7618fd9dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3998,6 +3998,13 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.2.6": + version: 1.2.6 + resolution: "@floating-ui/core@npm:1.2.6" + checksum: e4aa96c435277f1720d4bc939e17a79b1e1eebd589c20b622d3c646a5273590ff889b8c6e126f7be61873cf8c4d7db7d418895986ea19b8b0d0530de32504c3a + languageName: node + linkType: hard + "@floating-ui/dom@npm:1.1.1": version: 1.1.1 resolution: "@floating-ui/dom@npm:1.1.1" @@ -4007,6 +4014,27 @@ __metadata: languageName: node linkType: hard +"@floating-ui/dom@npm:^1.2.1": + version: 1.2.6 + resolution: "@floating-ui/dom@npm:1.2.6" + dependencies: + "@floating-ui/core": ^1.2.6 + checksum: 2226c6c244b96ae75ab14cc35bb119c8d7b83a85e2ff04e9d9800cffdb17faf4a7cf82db741dd045242ced56e31c8a08e33c8c512c972309a934d83b1f410441 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^1.3.0": + version: 1.3.0 + resolution: "@floating-ui/react-dom@npm:1.3.0" + dependencies: + "@floating-ui/dom": ^1.2.1 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: ce0ad3e3bbe43cfd15a6a0d5cccede02175c845862bfab52027995ab99c6b29630180dc7d146f76ebb34730f90a6ab9bf193c8984fe8d7f56062308e4ca98f77 + languageName: node + linkType: hard + "@formatjs/ecma402-abstract@npm:1.14.3": version: 1.14.3 resolution: "@formatjs/ecma402-abstract@npm:1.14.3" @@ -23920,13 +23948,6 @@ __metadata: languageName: node linkType: hard -"react-fast-compare@npm:^3.0.1": - version: 3.2.1 - resolution: "react-fast-compare@npm:3.2.1" - checksum: 209b4dc3a9cc79c074a26ec020459efd8be279accaca612db2edb8ada2a28849ea51cf3d246fc0fafb344949b93a63a43798b6c1787559b0a128571883fe6859 - languageName: node - linkType: hard - "react-helmet-async@npm:^1.3.0": version: 1.3.0 resolution: "react-helmet-async@npm:1.3.0" @@ -24068,20 +24089,6 @@ __metadata: languageName: node linkType: hard -"react-popper@npm:^2.3.0": - version: 2.3.0 - resolution: "react-popper@npm:2.3.0" - dependencies: - react-fast-compare: ^3.0.1 - warning: ^4.0.2 - peerDependencies: - "@popperjs/core": ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - checksum: 837111c98738011c69b3069a464ea5bdcbf487105b6148e8faf90cb7337e134edb1b98b8824322941c378756cca30a15c18c25f558e53b85ed5762fa0dc8e6b2 - languageName: node - linkType: hard - "react-redux@npm:^8.0.5": version: 8.0.5 resolution: "react-redux@npm:8.0.5" @@ -25590,6 +25597,7 @@ __metadata: "@atomik-color/component": ^1.0.17 "@atomik-color/core": ^1.0.13 "@axe-core/react": ^4.5.2 + "@floating-ui/react-dom": ^1.3.0 "@internationalized/number": ^3.2.0 "@leeoniya/ufuzzy": ^1.0.2 "@popperjs/core": ^2.11.7 @@ -25670,7 +25678,6 @@ __metadata: react-i18next: ^12.1.5 react-instantsearch: ^6.38.1 react-instantsearch-dom: ^6.38.1 - react-popper: ^2.3.0 react-redux: ^8.0.5 react-router-dom: ^6.4.4 react-signature-pad-wrapper: ^3.3.1 @@ -28269,15 +28276,6 @@ __metadata: languageName: node linkType: hard -"warning@npm:^4.0.2": - version: 4.0.3 - resolution: "warning@npm:4.0.3" - dependencies: - loose-envify: ^1.0.0 - checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c - languageName: node - linkType: hard - "watchpack@npm:^2.2.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0"