fix: Various improvements to select input, closes #4528
This commit is contained in:
@@ -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 />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user