fix: Various improvements to select input, closes #4528
This commit is contained in:
@@ -8,11 +8,11 @@ import {
|
||||
import { CheckmarkIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { VisuallyHidden } from "reakit/VisuallyHidden";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import styled, { css } from "styled-components";
|
||||
import Button, { Inner } from "~/components/Button";
|
||||
import Text from "~/components/Text";
|
||||
import useMenuHeight from "~/hooks/useMenuHeight";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import { fadeAndScaleIn } from "~/styles/animations";
|
||||
import {
|
||||
Position,
|
||||
@@ -78,16 +78,25 @@ const InputSelect = (props: Props) => {
|
||||
disabled,
|
||||
});
|
||||
|
||||
const isMobile = useMobile();
|
||||
const previousValue = React.useRef<string | null>(value);
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const selectedRef = React.useRef<HTMLDivElement>(null);
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const minWidth = buttonRef.current?.offsetWidth || 0;
|
||||
const maxHeight = useMenuHeight(
|
||||
const margin = 8;
|
||||
const menuMaxHeight = useMenuHeight(
|
||||
select.visible,
|
||||
select.unstable_disclosureRef
|
||||
select.unstable_disclosureRef,
|
||||
margin
|
||||
);
|
||||
const maxHeight = Math.min(
|
||||
menuMaxHeight ?? 0,
|
||||
window.innerHeight -
|
||||
(buttonRef.current?.getBoundingClientRect().bottom ?? 0) -
|
||||
margin
|
||||
);
|
||||
|
||||
const wrappedLabel = <LabelText>{label}</LabelText>;
|
||||
const selectedValueIndex = options.findIndex(
|
||||
(option) => option.value === select.selectedValue
|
||||
@@ -106,26 +115,15 @@ const InputSelect = (props: Props) => {
|
||||
load();
|
||||
}, [onChange, select.selectedValue]);
|
||||
|
||||
// Ensure selected option is visible when opening the input
|
||||
React.useEffect(() => {
|
||||
if (!select.animating && selectedRef.current) {
|
||||
scrollIntoView(selectedRef.current, {
|
||||
scrollMode: "if-needed",
|
||||
behavior: "auto",
|
||||
block: "start",
|
||||
});
|
||||
}
|
||||
}, [select.animating]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (select.visible) {
|
||||
const offset = Math.round(
|
||||
(selectedRef.current?.getBoundingClientRect().top || 0) -
|
||||
(contentRef.current?.getBoundingClientRect().top || 0)
|
||||
);
|
||||
setOffset(offset);
|
||||
requestAnimationFrame(() => {
|
||||
if (contentRef.current) {
|
||||
contentRef.current.scrollTop = selectedValueIndex * 32;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [select.visible]);
|
||||
}, [select.visible, selectedValueIndex]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -158,17 +156,9 @@ const InputSelect = (props: Props) => {
|
||||
placement: Placement;
|
||||
}
|
||||
) => {
|
||||
if (!props.style) {
|
||||
props.style = {};
|
||||
}
|
||||
const topAnchor = props.style.top === "0";
|
||||
const topAnchor = props.style?.top === "0";
|
||||
const rightAnchor = props.placement === "bottom-end";
|
||||
|
||||
// offset top of select to place selected item under the cursor
|
||||
if (selectedValueIndex !== -1) {
|
||||
props.style.top = `-${offset + 32}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Positioner {...props}>
|
||||
<Background
|
||||
@@ -218,7 +208,7 @@ const InputSelect = (props: Props) => {
|
||||
{note}
|
||||
</Text>
|
||||
)}
|
||||
{select.visible && <Backdrop />}
|
||||
{select.visible && isMobile && <Backdrop />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,25 +4,24 @@ import useWindowSize from "~/hooks/useWindowSize";
|
||||
|
||||
const useMenuHeight = (
|
||||
visible: void | boolean,
|
||||
unstable_disclosureRef?: React.RefObject<HTMLElement | null>
|
||||
unstable_disclosureRef?: React.RefObject<HTMLElement | null>,
|
||||
margin = 8
|
||||
) => {
|
||||
const [maxHeight, setMaxHeight] = React.useState<number | undefined>();
|
||||
const isMobile = useMobile();
|
||||
const { height: windowHeight } = useWindowSize();
|
||||
|
||||
React.useEffect(() => {
|
||||
const padding = 8;
|
||||
|
||||
if (visible && !isMobile) {
|
||||
setMaxHeight(
|
||||
unstable_disclosureRef?.current
|
||||
? windowHeight -
|
||||
unstable_disclosureRef.current.getBoundingClientRect().bottom -
|
||||
padding
|
||||
margin
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
}, [visible, unstable_disclosureRef, windowHeight, isMobile]);
|
||||
}, [visible, unstable_disclosureRef, windowHeight, margin, isMobile]);
|
||||
return maxHeight;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user