From 213b2336b622ee76d832198b6c86fdeb512bddbb Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Mon, 17 Apr 2023 14:08:23 -0400 Subject: [PATCH] Fix select options width and select click behavior. Fix snackbar timeout --- .../src/components/common/select/Select.tsx | 26 +++++++++- .../src/components/snackbar/Snackbars.tsx | 2 +- packages/core/src/lib/hooks/useElementSize.ts | 48 +++++++++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/lib/hooks/useElementSize.ts diff --git a/packages/core/src/components/common/select/Select.tsx b/packages/core/src/components/common/select/Select.tsx index bc197ce5..e0e6451b 100644 --- a/packages/core/src/components/common/select/Select.tsx +++ b/packages/core/src/components/common/select/Select.tsx @@ -1,12 +1,14 @@ import SelectUnstyled from '@mui/base/SelectUnstyled'; import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@styled-icons/material/KeyboardArrowDown'; -import React, { forwardRef, useCallback } from 'react'; +import React, { forwardRef, useCallback, useState } from 'react'; +import useElementSize from '@staticcms/core/lib/hooks/useElementSize'; import classNames from '@staticcms/core/lib/util/classNames.util'; import { isNotEmpty } from '@staticcms/core/lib/util/string.util'; import Option from './Option'; import type { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, Ref } from 'react'; +import useDebounce from '@staticcms/core/lib/hooks/useDebounce'; export interface Option { label: string; @@ -58,6 +60,23 @@ const Select = forwardRef( [onChange, value], ); + const { width } = useElementSize(ref); + + const [open, setOpen] = useState(false); + const debouncedOpen = useDebounce(open, 250); + const handleOpenChange = useCallback( + (newOpen: boolean) => { + if (debouncedOpen !== open) { + return; + } + + setOpen(newOpen); + }, + [debouncedOpen, open], + ); + + const handleButtonClick = useCallback(() => handleOpenChange(!open), [handleOpenChange, open]); + return (
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} @@ -99,6 +118,7 @@ const Select = forwardRef( slotProps={{ root: { ref, + onClick: handleButtonClick, className: classNames( ` flex @@ -122,7 +142,6 @@ const Select = forwardRef( }, popper: { className: ` - absolute max-h-60 overflow-auto rounded-md @@ -138,12 +157,15 @@ const Select = forwardRef( z-50 dark:bg-slate-700 `, + style: { width }, disablePortal: false, }, }} value={value} disabled={disabled} onChange={handleChange} + listboxOpen={open} + onListboxOpenChange={handleOpenChange} data-testid="select-input" > {!Array.isArray(value) && !required ? ( diff --git a/packages/core/src/components/snackbar/Snackbars.tsx b/packages/core/src/components/snackbar/Snackbars.tsx index 71ff5bee..0bff0292 100644 --- a/packages/core/src/components/snackbar/Snackbars.tsx +++ b/packages/core/src/components/snackbar/Snackbars.tsx @@ -46,7 +46,7 @@ const Snackbars: FC = ({ t }) => { ( + providedRef: Ref = null, +): Size { + // Mutable values like 'ref.current' aren't valid dependencies + // because mutating them doesn't re-render the component. + // Instead, we use a state as a ref to be reactive. + const [ref, setRef] = useState(null); + const [size, setSize] = useState({ + width: 0, + height: 0, + }); + + // Prevent too many rendering using useCallback + const handleSize = useCallback(() => { + setSize({ + width: ref?.offsetWidth || 0, + height: ref?.offsetHeight || 0, + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ref?.offsetHeight, ref?.offsetWidth]); + + useWindowEvent('resize', handleSize); + + useEffect(() => { + setRef(typeof providedRef !== 'function' ? providedRef?.current ?? null : null); + }, [providedRef]); + + useEffect(() => { + handleSize(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ref?.offsetHeight, ref?.offsetWidth]); + + return size; +} + +export default useElementSize;