fix: Various improvements to select input, closes #4528

This commit is contained in:
Tom Moor
2022-12-04 10:57:09 -05:00
parent 13db16283a
commit cd29cd3aec
2 changed files with 26 additions and 37 deletions

View File

@@ -8,11 +8,11 @@ import {
import { CheckmarkIcon } from "outline-icons"; import { CheckmarkIcon } from "outline-icons";
import * as React from "react"; import * as React from "react";
import { VisuallyHidden } from "reakit/VisuallyHidden"; import { VisuallyHidden } from "reakit/VisuallyHidden";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import Button, { Inner } from "~/components/Button"; import Button, { Inner } from "~/components/Button";
import Text from "~/components/Text"; import Text from "~/components/Text";
import useMenuHeight from "~/hooks/useMenuHeight"; import useMenuHeight from "~/hooks/useMenuHeight";
import useMobile from "~/hooks/useMobile";
import { fadeAndScaleIn } from "~/styles/animations"; import { fadeAndScaleIn } from "~/styles/animations";
import { import {
Position, Position,
@@ -78,16 +78,25 @@ const InputSelect = (props: Props) => {
disabled, disabled,
}); });
const isMobile = useMobile();
const previousValue = React.useRef<string | null>(value); const previousValue = React.useRef<string | null>(value);
const contentRef = React.useRef<HTMLDivElement>(null);
const selectedRef = React.useRef<HTMLDivElement>(null); const selectedRef = React.useRef<HTMLDivElement>(null);
const buttonRef = React.useRef<HTMLButtonElement>(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 minWidth = buttonRef.current?.offsetWidth || 0;
const maxHeight = useMenuHeight( const margin = 8;
const menuMaxHeight = useMenuHeight(
select.visible, 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 wrappedLabel = <LabelText>{label}</LabelText>;
const selectedValueIndex = options.findIndex( const selectedValueIndex = options.findIndex(
(option) => option.value === select.selectedValue (option) => option.value === select.selectedValue
@@ -106,26 +115,15 @@ const InputSelect = (props: Props) => {
load(); load();
}, [onChange, select.selectedValue]); }, [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(() => { React.useLayoutEffect(() => {
if (select.visible) { if (select.visible) {
const offset = Math.round( requestAnimationFrame(() => {
(selectedRef.current?.getBoundingClientRect().top || 0) - if (contentRef.current) {
(contentRef.current?.getBoundingClientRect().top || 0) contentRef.current.scrollTop = selectedValueIndex * 32;
); }
setOffset(offset); });
} }
}, [select.visible]); }, [select.visible, selectedValueIndex]);
return ( return (
<> <>
@@ -158,17 +156,9 @@ const InputSelect = (props: Props) => {
placement: Placement; placement: Placement;
} }
) => { ) => {
if (!props.style) { const topAnchor = props.style?.top === "0";
props.style = {};
}
const topAnchor = props.style.top === "0";
const rightAnchor = props.placement === "bottom-end"; 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 ( return (
<Positioner {...props}> <Positioner {...props}>
<Background <Background
@@ -218,7 +208,7 @@ const InputSelect = (props: Props) => {
{note} {note}
</Text> </Text>
)} )}
{select.visible && <Backdrop />} {select.visible && isMobile && <Backdrop />}
</> </>
); );
}; };

View File

@@ -4,25 +4,24 @@ import useWindowSize from "~/hooks/useWindowSize";
const useMenuHeight = ( const useMenuHeight = (
visible: void | boolean, 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 [maxHeight, setMaxHeight] = React.useState<number | undefined>();
const isMobile = useMobile(); const isMobile = useMobile();
const { height: windowHeight } = useWindowSize(); const { height: windowHeight } = useWindowSize();
React.useEffect(() => { React.useEffect(() => {
const padding = 8;
if (visible && !isMobile) { if (visible && !isMobile) {
setMaxHeight( setMaxHeight(
unstable_disclosureRef?.current unstable_disclosureRef?.current
? windowHeight - ? windowHeight -
unstable_disclosureRef.current.getBoundingClientRect().bottom - unstable_disclosureRef.current.getBoundingClientRect().bottom -
padding margin
: undefined : undefined
); );
} }
}, [visible, unstable_disclosureRef, windowHeight, isMobile]); }, [visible, unstable_disclosureRef, windowHeight, margin, isMobile]);
return maxHeight; return maxHeight;
}; };