Popover

The Popover component is responsible for rendering the visual layout and styles of popovers. To ensure proper positioning, it should be used alongside the usePopover hook, which manages the placement and positioning logic.

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { Popover } from '@adaptavant/eds-core';

Basic Usage

Initialize the Popover using the usePopover hook, which returns an object with four key elements. Among these, getReferenceProps and getPopoverProps can be used to assign ref objects to the Trigger and Popover elements, ensuring they are properly linked and positioned.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'bottom-start',
	});

	const { getPopoverProps, getReferenceProps } = popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const animals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
	];

	/**
	* Generates the IDs for the popover's trigger and popover panel.
	*
	* These IDs are generated together because they are linked using
	* `aria-controls` and `aria-labelledby` for accessibility. The `baseId`
	* prefix ensures uniqueness, allowing multiple popovers to coexist on the
	* same page without ID conflicts.
	*/
	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	// When the Popover is opened, focus should be transfered to popover
	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Toggle popover
			</Button>

			{isOpen && <Popover
				aria-labelledby={triggerId}
				as="ul"
				id={popoverId}
				ref={mergeRefs(autoFocusOnOpen, popRef)}
				role="listbox" // add appropriate roles
				style={popoverStyles}
				tabIndex={-1}
			>
				{animals.map((animal) => {
					return (
						<Box
							as="li"
							className="flex items-center gap-1 p-2 text-body-12"
							key={animal.id}
						>
							<Emoji>{animal.emoji}</Emoji>
							<Text className="text-body-12">{animal.value}</Text>
						</Box>
					);
				})}
			</Popover>}
		</>
	);

Always Mounted

The Popover can either be rendered on-demand, as shown in the example above, or always mounted in the DOM but hidden via CSS, depending on your use case. To keep the Popover always mounted but hidden when closed, set isPopoverAlwaysMounted to true. By default, this value is set to false.

Additionally, ensure that isOpen is set to true when the Popover is visible and false when it's hidden.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'right-start',
		isOpen: isOpen,
		isPopoverAlwaysMounted: true,
	});

	const { getPopoverProps, getReferenceProps } = popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const animals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
	];

	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Toggle popover
			</Button>
			
			<Popover
				aria-labelledby={triggerId}
				as="ul"
				id={popoverId}
				ref={mergeRefs(autoFocusOnOpen, popRef)}
				role="listbox"
				style={isOpen ? popoverStyles: {display: 'none'}}
				tabIndex={-1}
			>
				{animals.map((animal) => {
					return (
						<Box
							as="li"
							className="flex items-center gap-1 p-2 text-body-12"
							key={animal.id}
						>
							<Emoji>{animal.emoji}</Emoji>
							<Text className="text-body-12">{animal.value}</Text>
						</Box>
					);
				})}
			</Popover>
		</>
	);

Close On Outer Click

Typically, we want to close the Popover when a user clicks outside of it — meaning clicks that are not on the trigger or the Popover element itself. This can be managed using the popoverRef and referenceRef returned by usePopover.

To handle this, use the useClickAway custom hook available in @adaptavant/eds-core/utils. It accepts a callback function that will be executed when an outside click is detected, along with an array of refs to exclude from triggering the close action.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'top-start',
	});

	const { popoverRef, referenceRef, getPopoverProps, getReferenceProps } =
		popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const memoizedRefs = React.useMemo(
		() => [popoverRef, referenceRef],
		[popoverRef, referenceRef]
	);

	// custom hook that detects clicks outside specified elements and invokes a callback 
	useClickAway({
		refs: memoizedRefs, // Array of refs to elements. A click outside of any of these elements will trigger the onClickAway callback
		onClickAway: () => {
			if (isOpen) toggleOpen();
		},
	});

	const animals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
	];

	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Toggle popover
			</Button>

			{isOpen && (
				<Popover
					aria-labelledby={triggerId}
					as="ul"
					id={popoverId}
					ref={mergeRefs(autoFocusOnOpen, popRef)}
					role="listbox"
					style={popoverStyles}
					tabIndex={-1}
				>
					{animals.map((animal) => {
						return (
							<Box
								as="li"
								className="flex items-center gap-1 p-2 text-body-12"
								key={animal.id}
							>
								<Emoji>{animal.emoji}</Emoji>
								<Text className="text-body-12">{animal.value}</Text>
							</Box>
						);
					})}
				</Popover>
			)}
		</>
	);

Close On Inner Click

In addition to the example above, you may want to close the Popover after interactions within it, like clicking Submit or Reset buttons. To achieve this, you can pass the toggle function and invoke it onClick to toggle the Popover's visibility.

See the toggleOpen function below, which is invoked in onClick callback for a list item.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'top-end',
		isPopoverAlwaysMounted: false,
	});

	const { popoverRef, referenceRef, getPopoverProps, getReferenceProps } =
		popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const memoizedRefs = React.useMemo(
		() => [popoverRef, referenceRef],
		[popoverRef, referenceRef]
	);

	useClickAway({
		refs: memoizedRefs,
		onClickAway: () => {
			if (isOpen) toggleOpen();
		},
	});

	const moreAnimals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
		{ id: '5', value: 'Giraffe', emoji: '🦒' },
		{ id: '6', value: 'Hippo', emoji: '🦛' },
		{ id: '7', value: 'Rhino', emoji: '🦏' },
		{ id: '8', value: 'Panda', emoji: '🐼' },
		{ id: '9', value: 'Kangaroo', emoji: '🦘' },
		{ id: '10', value: 'Penguin', emoji: '🐧' },
		{ id: '11', value: 'Polar Bear', emoji: '🐻' },
		{ id: '12', value: 'Dolphin', emoji: '🐬' },
	];

	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Select Option
			</Button>

			{isOpen && (
				<Popover
					aria-labelledby={triggerId}
					as="ul"
					className="overflow-y-auto"
					id={popoverId}
					ref={mergeRefs(autoFocusOnOpen, popRef)}
					role="listbox"
					style={popoverStyles}
					tabIndex={-1}
				>
					{moreAnimals.map((animal) => {
						return (
							<Box
								as="li"
								className="flex items-center gap-1 p-2 text-body-12 cursor-pointer"
								key={animal.id}
								onClick={() => {
									// replace this with your custom action
									alert('selected item is ' + animal.value);
									// calling toggleOpen function makes popover unmount
									toggleOpen();
								}}
							>
								<Emoji>{animal.emoji}</Emoji>
								<Text className="text-body-12">{animal.value}</Text>
							</Box>
						);
					})}
				</Popover>
			)}
		</>
	);

Nested Popover

The Popover can contain any element as its children, including another Dropdown or Popover, allowing you to easily create nested Popover components.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'right-end',
		isOpen: isOpen,
		isPopoverAlwaysMounted: true,
	});

	const { getPopoverProps, getReferenceProps } = popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const moreAnimals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
		{ id: '5', value: 'Giraffe', emoji: '🦒' },
		{ id: '6', value: 'Hippo', emoji: '🦛' },
		{ id: '7', value: 'Rhino', emoji: '🦏' },
		{ id: '8', value: 'Panda', emoji: '🐼' },
		{ id: '9', value: 'Kangaroo', emoji: '🦘' },
		{ id: '10', value: 'Penguin', emoji: '🐧' },
		{ id: '11', value: 'Polar Bear', emoji: '🐻' },
		{ id: '12', value: 'Dolphin', emoji: '🐬' },
	];

	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	// SelectMenu State
	const [selectedSelectMenuOption, setSelectedSelectMenuOption] = React.useState(moreAnimals[3]);

	// FilterMenu State
	const [selectedFilterMenuOption, setSelectedFilterMenuOption] = React.useState(moreAnimals[5]);

	const [searchTerm, setSearchTerm] = React.useState('');

	function onClear(){
		return setSearchTerm('');
	}

	function handleInputOnChange(event) {
		return setSearchTerm(event.target.value);
	}

	const filteredOptions = searchTerm === "" ? moreAnimals : moreAnimals.filter((animal) =>
		animal.value.toLowerCase().includes(searchTerm.toLowerCase())
	);


	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Toggle popover
			</Button>

			<Popover
				aria-labelledby={triggerId}
				as="ul"
				className="gap-2 rounded-8px"
				id={popoverId}
				ref={mergeRefs(autoFocusOnOpen, popRef)}
				role="listbox"
				style={isOpen ? popoverStyles : { display: 'none' }}
				tabIndex={-1}
			>
				<Text className="text-body-12 mb-2">Selecting animal from Filtermenu makes entire Popover hide</Text>
				<Field label="Select animal">
					<SelectMenu selectedOption={selectedSelectMenuOption} mobileFriendly={false}>
						<SelectMenuTrigger placeholder="Select">
							{selectedSelectMenuOption?.emoji}
						</SelectMenuTrigger>
						<SelectMenuPopover className="z-[15]">
							<SelectMenuListbox options={moreAnimals}>
								{(animal) => {
									return (
										<SelectMenuItem
											id={animal.id}
											isSelected={selectedSelectMenuOption?.id === animal.id}
											onClick={() => {
												setSelectedSelectMenuOption(animal);
											}}
											railEnd={<Emoji>{animal.emoji}</Emoji>}
										>
											{animal.value}
										</SelectMenuItem>
									);
								}}
							</SelectMenuListbox>
						</SelectMenuPopover>
					</SelectMenu>
				</Field>
				<Field label="Filter and select animal">
					<FilterMenu mobileFriendly={false}>
						<FilterMenuTrigger>
							{selectedFilterMenuOption?.emoji}
						</FilterMenuTrigger>
						<FilterMenuPopover className="z-[15]">
							<FilterMenuSearchField label="Search Items">
								<FilterMenuSearchInput
									onClear={onClear}
									onChange={handleInputOnChange}
									value={searchTerm}
									placeholder="Search..."
								/>
							</FilterMenuSearchField>
							<FilterMenuListbox 
								noResultsFallback={<Text className="text-secondary text-center text-body-12 py-4">
									No matching results
								</Text>}
								options={filteredOptions}
							>
								{(animal) => {
									return (
										<FilterMenuItem
											id={animal.id}
											isSelected={selectedFilterMenuOption?.id === animal.id}
											onClick={() => {
												setSelectedFilterMenuOption(animal);
												// close the popover on item select
												setTimeout(() => toggleOpen(), 50);
											}}
											railEnd={<Emoji>{animal.emoji}</Emoji>}
										>
											{animal.value}
										</FilterMenuItem>
									);
								}}
							</FilterMenuListbox>
						</FilterMenuPopover>
					</FilterMenu>
				</Field>
			</Popover>
		</>
	);

Note:

  • When you use any children element make sure you are transferring a autoFocus and tab key control properly to newly attached popover element so that it helps keyboard users to properly navigate.
  • Consumers should also ensure that accessibility controls are properly added to provide a smooth experience for voiceover, screen readers, and other assistive technologies to work as expected..

Portal

Use the shouldUsePortal prop to render the Popover outside the parent component's DOM hierarchy. This approach helps avoid stacking issues and overflow, as the Popover will be mounted to the <Root> component via React Portal. By default, this prop is set to false.

	const [isOpen, setIsOpen] = React.useState(false);
	const toggleOpen = React.useCallback(() => {
		setIsOpen((prev) => !prev);
	}, []);

	const popover = usePopover({
		matchReferenceWidth: false,
		maxHeight: 200,
		maxWidth: 200,
		offset: 4,
		placement: 'top-start',
	});

	const { popoverRef, referenceRef, getPopoverProps, getReferenceProps } =
		popover;

	const triggerRef = getReferenceProps().ref;
	const popRef = getPopoverProps().ref;

	const popoverStyles = React.useMemo(
		() => getPopoverProps().style,
		[getPopoverProps]
	);

	const memoizedRefs = React.useMemo(
		() => [popoverRef, referenceRef],
		[popoverRef, referenceRef]
	);

	// custom hook that detects clicks outside specified elements and invokes a callback 
	useClickAway({
		refs: memoizedRefs, // Array of refs to elements. A click outside of any of these elements will trigger the onClickAway callback
		onClickAway: () => {
			if (isOpen) toggleOpen();
		},
	});

	const animals = [
		{ id: '1', value: 'Elephant', emoji: '🐘' },
		{ id: '2', value: 'Lion', emoji: '🦁' },
		{ id: '3', value: 'Tiger', emoji: '🐅' },
		{ id: '4', value: 'Zebra', emoji: '🦓' },
	];

	const id = React.useId();
	const baseId = `dropdown-${id}`;
	const popoverId= composeId(baseId, 'popover');
	const triggerId= composeId(baseId, 'trigger');

	const autoFocusOnOpen = React.useCallback(
		(node) => {
			if (node && isOpen) node.focus({ preventScroll: true });
		},
		[isOpen]
	);

	function mergeRefs(...refs) {
		return (value) => {
			refs.forEach((ref) => {
				if (typeof ref === 'function') {
					ref(value); // Call the callback ref with the value
				} else if (ref && 'current' in ref) {
					(ref).current = value; // Assign value to ref object
				}
			});
		};
	}

	return (
		<>
			<Button
				aria-controls={popoverId}
				aria-expanded={isOpen}
				aria-haspopup={true}
				className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
				id={triggerId}
				onClick={toggleOpen}
				ref={triggerRef}
				variant="neutralSecondary"
			>
				Toggle popover
			</Button>

			{isOpen && (
				<Popover
					aria-labelledby={triggerId}
					as="ul"
					id={popoverId}
					ref={mergeRefs(autoFocusOnOpen, popRef)}
					role="listbox"
					shouldUsePortal={true}
					style={popoverStyles}
					tabIndex={-1}
				>
					{animals.map((animal) => {
						return (
							<Box
								as="li"
								className="flex items-center gap-1 p-2 text-body-12"
								key={animal.id}
							>
								<Emoji>{animal.emoji}</Emoji>
								<Text className="text-body-12">{animal.value}</Text>
							</Box>
						);
					})}
			</Popover>
			)}
		</>
	);

Strategy

Use the strategy option in usePopover to control the positioning of the Popover element. By default, the strategy is set to absolute, changes it to fixed when the Popover trigger is inside a sticky or fixed element.

This option leverages the @floating-ui library, which powers the Popover functionality.

    const [isOpen, setIsOpen] = React.useState(false);
    const toggleOpen = React.useCallback(() => {
        setIsOpen((prev) => !prev);
    }, []);

    const popover = usePopover({
        matchReferenceWidth: false,
        maxHeight: 200,
        maxWidth: 200,
        offset: 4,
        placement: 'top-end',
        isPopoverAlwaysMounted: false,
        strategy: 'fixed'
    });

    const { popoverRef, referenceRef, getPopoverProps, getReferenceProps } =
        popover;

    const triggerRef = getReferenceProps().ref;
    const popRef = getPopoverProps().ref;

    const popoverStyles = React.useMemo(
        () => getPopoverProps().style,
        [getPopoverProps]
    );

    const memoizedRefs = React.useMemo(
        () => [popoverRef, referenceRef],
        [popoverRef, referenceRef]
    );

    useClickAway({
        refs: memoizedRefs,
        onClickAway: () => {
            if (isOpen) toggleOpen();
        },
    });

    const moreAnimals = [
        { id: '1', value: 'Elephant', emoji: '🐘' },
        { id: '2', value: 'Lion', emoji: '🦁' },
        { id: '3', value: 'Tiger', emoji: '🐅' },
        { id: '4', value: 'Zebra', emoji: '🦓' },
        { id: '5', value: 'Giraffe', emoji: '🦒' },
        { id: '6', value: 'Hippo', emoji: '🦛' },
        { id: '7', value: 'Rhino', emoji: '🦏' },
        { id: '8', value: 'Panda', emoji: '🐼' },
        { id: '9', value: 'Kangaroo', emoji: '🦘' },
        { id: '10', value: 'Penguin', emoji: '🐧' },
        { id: '11', value: 'Polar Bear', emoji: '🐻' },
        { id: '12', value: 'Dolphin', emoji: '🐬' },
    ];

    const id = React.useId();
    const baseId = `dropdown-${id}`;
    const popoverId= composeId(baseId, 'popover');
    const triggerId= composeId(baseId, 'trigger');

    const [showFixedElement, setShowFixedElement] = React.useState(false);

    const onButtonClick = () => {
        setShowFixedElement((prevState) => !prevState)
    }
    
    const autoFocusOnOpen = React.useCallback(
        (node) => {
            if (node && isOpen) node.focus({ preventScroll: true });
        },
        [isOpen]
    );

    function mergeRefs(...refs) {
        return (value) => {
            refs.forEach((ref) => {
                if (typeof ref === 'function') {
                    ref(value); // Call the callback ref with the value
                } else if (ref && 'current' in ref) {
                    (ref).current = value; // Assign value to ref object
                }
            });
        };
    }

    return (
        <Stack className="w-full gap-4">
            <Button onClick={onButtonClick}>Show fixed element</Button>
            {showFixedElement ? (
                <div className="
                    animate-[snackbar-transition_0.3s_cubic-bezier(0.16,_1,_0.3,_1)]
                    bg-neutral-secondary
                    fixed
                    flex
                    items-center
                    justify-between
                    mx-2
                    p-4
                    right-0
                    rounded-8px
                    shadow-40
                    sm:right-8
                    sm:w-[360px]
                    top-8
                    w-[calc(100%-16px)]
                    z-10
                ">
                    <Button
                        aria-controls={popoverId}
                        aria-expanded={isOpen}
                        aria-haspopup={true}
                        className="aria-expanded:bg-neutral-pressed aria-expanded:border-transparent"
                        id={triggerId}
                        onClick={toggleOpen}
                        ref={triggerRef}
                        variant="neutralSecondary"
                    >
                        Select Option
                    </Button>

                    {isOpen && (
                        <Popover
                            aria-labelledby={triggerId}
                            as="ul"
                            className="overflow-y-auto"
                            id={popoverId}
                            ref={mergeRefs(autoFocusOnOpen, popRef)}
                            role="listbox"
                            shouldUsePortal={true}
                            style={popoverStyles}
			                tabIndex={-1}
                        >
                            {moreAnimals.map((animal) => {
                                return (
                                    <Box
                                        as="li"
                                        className="flex items-center gap-1 p-2 text-body-12 cursor-pointer"
                                        key={animal.id}
                                        onClick={() => {
                                            // replace this with your custom action
                                            alert('selected item is ' + animal.value);
                                            // calling toggleOpen function makes popover unmount
                                            toggleOpen();
                                        }}
                                    >
                                        <Emoji>{animal.emoji}</Emoji>
                                        <Text className="text-body-12">{animal.value}</Text>
                                    </Box>
                                );
                            })}
                        </Popover>
                    )}

                    <button 
                        className="
                            focus-visible:focus-ring
                            font-stronger
                            px-1
                            py-0.5
                            rounded-4px
                            text-body-12
                            text-primary
                            underline
                            underline-offset-2
                        "
                        onClick={onButtonClick}
                    >
                        Hide fixed element
                    </button>
                </div>
            ) : null}
        </Stack>
    );

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.