Fix select options width and select click behavior. Fix snackbar timeout
This commit is contained in:
parent
25e9d04f82
commit
213b2336b6
@ -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 ? (
|
||||||
|
@ -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' }}
|
||||||
|
48
packages/core/src/lib/hooks/useElementSize.ts
Normal file
48
packages/core/src/lib/hooks/useElementSize.ts
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user