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.