Help with React Aria, I trying to create custom Select component with help useOverlay hook #2122
Answered
by
devongovett
joseDaKing
asked this question in
Q&A
-
Hi, I am trying to implement a custom Select component and I am using the useOverlay, useOverlayPosition to place the internal ListBox component, but for some reason the ListBox is not in view. I followed useOverlay example the example but it did not work. Here is my code // Select Component
import React, { useRef } from "react";
import { useSelectState } from "react-stately";
import type { AriaSelectProps } from "@react-types/select";
import styled, { system } from "@xstyled/styled-components";
import { useSelect, HiddenSelect, mergeProps, useFocusRing, useButton, useOverlayTrigger, useOverlayPosition } from "react-aria";
import { StyledTextFieldBase, StyledBlockBase, StyledLabelBase, ListBoxBase, PopoverBase } from "./base";
import { IBlockProp, IFocusableProp, ISizeProp } from "../types";
import { focusStyleMixin } from "../styleMixins";
interface IStyledSelectProps extends IFocusableProp {
isEmpty: boolean;
}
const StyledSelect = styled(StyledTextFieldBase).attrs({ as: "button" })<IStyledSelectProps>`${props => {
let color: string | undefined;
if (props.isEmpty) {
color = "blue-gray-400";
}
if (props.isDisabled) {
color = "gray-400";
}
const { isActive, ...focusStyleMixinProps } = props;
return system.apply({
color,
textAlign: "left",
outline: { focus: "none" },
...focusStyleMixin(focusStyleMixinProps)
});
}}`;
interface ISelectItem {
value: string;
}
interface ISelectProps extends AriaSelectProps<ISelectItem>, IBlockProp, ISizeProp {
name?: string;
placeholder?: string;
}
interface ISelectSubComponents {
Item: typeof ListBoxBase.Item;
Label: typeof ListBoxBase.Label;
Description: typeof ListBoxBase.Description;
}
export const Select: React.FC<ISelectProps> & ISelectSubComponents = props => {
const state = useSelectState(props);
const selectRef = useRef<HTMLInputElement|null>(null);
const { labelProps, triggerProps, valueProps, menuProps } = useSelect(props, state, selectRef);
const { buttonProps } = useButton(triggerProps, selectRef);
const { isFocusVisible, focusProps } = useFocusRing(props);
const overlayRef = useRef<HTMLDivElement|null>(null);
const { overlayProps } = useOverlayTrigger({
type: "listbox"
}, state, selectRef);
const { overlayProps: positionProps } = useOverlayPosition({
overlayRef,
offset: 0,
targetRef: selectRef,
placement: "bottom",
isOpen: state.isOpen
});
const text = state.selectedItem?.textValue || "";
const isEmpty = text === "";
const placeholder = props.placeholder;
return (
<StyledBlockBase
axis="y"
block={props.block}>
{props.label &&
<StyledLabelBase
axis="y"
isDisabled={props.isDisabled}
{...labelProps}>
{props.label}
</StyledLabelBase>}
<HiddenSelect
state={state}
triggerRef={selectRef}
label={props.label}
name={props.name}/>
<StyledSelect
{...mergeProps(
focusProps,
buttonProps
)}
ref={selectRef}
isEmpty={isEmpty}
isActive={state.isOpen}
isFocused={isFocusVisible}
//@ts-ignore
size={props.size ?? "md"}>
<span {...valueProps}>
{text || placeholder}
</span>
</StyledSelect>
{state.isOpen && !props.isDisabled &&
<PopoverBase
{...mergeProps(
overlayProps,
positionProps
)}
ref={overlayRef}
isOpen={state.isOpen}
onClose={state.close}>
<ListBoxBase
{...menuProps}
state={state}/>
</PopoverBase>}
</StyledBlockBase>
);
}
Select.Item = ListBoxBase.Item;
Select.Label = ListBoxBase.Label;
Select.Description = ListBoxBase.Description;
--------------------------------------------------------------------
// PopoverBase component
import React from "react";
import { forwardRef } from "react";
import { DismissButton, FocusScope, mergeProps, OverlayContainer, useModal, useOverlay } from "react-aria";
interface IPopoverBaseProps extends React.HTMLAttributes<HTMLDivElement> {
isOpen: boolean;
onClose: () => void;
}
export const PopoverBase = forwardRef<HTMLElement, IPopoverBaseProps>((props, ref) => {
const { onClose, isOpen, ...htmlProps } = props;
const { overlayProps } = useOverlay({
onClose: onClose,
isOpen: isOpen,
isDismissable: true
}, ref as React.MutableRefObject<HTMLElement>);
const { modalProps } = useModal()
return (
<>
{isOpen &&
<OverlayContainer>
<FocusScope
autoFocus
restoreFocus>
<div
{...mergeProps(
overlayProps,
modalProps,
htmlProps
)}>
<DismissButton
onDismiss={onClose}/>
</div>
</FocusScope>
</OverlayContainer>}
</>
);
}) |
Beta Was this translation helpful? Give feedback.
Answered by
devongovett
Jul 17, 2021
Replies: 1 comment 3 replies
-
Would you mind setting this up in a code sandbox so we can more easily debug it for you? |
Beta Was this translation helpful? Give feedback.
3 replies
Answer selected by
snowystinger
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Would you mind setting this up in a code sandbox so we can more easily debug it for you?