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 { 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<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 (
<div className="relative w-full">
{/* 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 ? (

View File

@ -46,7 +46,7 @@ const Snackbars: FC<TranslateProps> = ({ t }) => {
<Snackbar
key={messageInfo ? messageInfo.id : undefined}
open={open}
autoHideDuration={600000}
autoHideDuration={6000}
onClose={handleClose}
TransitionProps={{ onExited: handleExited }}
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;