Fix select options width and select click behavior. Fix snackbar timeout

This commit is contained in:
Daniel Lautzenheiser 2023-04-17 14:08:23 -04:00
parent 25e9d04f82
commit 213b2336b6
3 changed files with 73 additions and 3 deletions

View File

@ -1,12 +1,14 @@
import SelectUnstyled from '@mui/base/SelectUnstyled'; import SelectUnstyled from '@mui/base/SelectUnstyled';
import { KeyboardArrowDown as KeyboardArrowDownIcon } from '@styled-icons/material/KeyboardArrowDown'; 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 classNames from '@staticcms/core/lib/util/classNames.util';
import { isNotEmpty } from '@staticcms/core/lib/util/string.util'; import { isNotEmpty } from '@staticcms/core/lib/util/string.util';
import Option from './Option'; import Option from './Option';
import type { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, Ref } from 'react'; import type { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, Ref } from 'react';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
export interface Option { export interface Option {
label: string; label: string;
@ -58,6 +60,23 @@ const Select = forwardRef(
[onChange, value], [onChange, value],
); );
const { width } = useElementSize<HTMLButtonElement>(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 ( return (
<div className="relative w-full"> <div className="relative w-full">
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
@ -99,6 +118,7 @@ const Select = forwardRef(
slotProps={{ slotProps={{
root: { root: {
ref, ref,
onClick: handleButtonClick,
className: classNames( className: classNames(
` `
flex flex
@ -122,7 +142,6 @@ const Select = forwardRef(
}, },
popper: { popper: {
className: ` className: `
absolute
max-h-60 max-h-60
overflow-auto overflow-auto
rounded-md rounded-md
@ -138,12 +157,15 @@ const Select = forwardRef(
z-50 z-50
dark:bg-slate-700 dark:bg-slate-700
`, `,
style: { width },
disablePortal: false, disablePortal: false,
}, },
}} }}
value={value} value={value}
disabled={disabled} disabled={disabled}
onChange={handleChange} onChange={handleChange}
listboxOpen={open}
onListboxOpenChange={handleOpenChange}
data-testid="select-input" data-testid="select-input"
> >
{!Array.isArray(value) && !required ? ( {!Array.isArray(value) && !required ? (

View File

@ -46,7 +46,7 @@ const Snackbars: FC<TranslateProps> = ({ t }) => {
<Snackbar <Snackbar
key={messageInfo ? messageInfo.id : undefined} key={messageInfo ? messageInfo.id : undefined}
open={open} open={open}
autoHideDuration={600000} autoHideDuration={6000}
onClose={handleClose} onClose={handleClose}
TransitionProps={{ onExited: handleExited }} TransitionProps={{ onExited: handleExited }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}

View File

@ -0,0 +1,48 @@
import { useCallback, useEffect, useState } from 'react';
import { useWindowEvent } from '../util/window.util';
import type { Ref } from 'react';
interface Size {
width: number;
height: number;
}
function useElementSize<T extends HTMLElement = HTMLDivElement>(
providedRef: Ref<T | null> = 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<T | null>(null);
const [size, setSize] = useState<Size>({
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;