<Stack alignItems='center' justifyContent='center' className='p-4' style={{ height: '200px' }}>
    <Popover
        trigger={<Button inactive>Element</Button>}
        open={true}
        direction="r"
        header="Popover Header"
        width="xs"
        padding="s"
    >
        <BodyText size="small">Popover content.</BodyText>
    </Popover>
</Stack>

Sizes

Popovers have a set of fixed width options, ranging from extra small (192px) to a large (640px). This is similar in how the Modal's width operates. There is also an option for the popover to extend to 100% the width of the element it is wrapped around.

const Example1 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack justifyContent="center">
            <Popover
                portal={true}
                header="Popover Header"
                headerAlign="space-between"
                trigger={<Button onClick={toggleOpen}>Extra Small</Button>}
                footer={<BodyText size="small">Popover Footer</BodyText>}
                open={open}
                direction="t"
                width="xs"
            >
                <BodyText size="small">Popover content.</BodyText>
                <BodyText size="small">Second line of text.</BodyText>
            </Popover>
        </Stack>
    );
};
render (Example1);
const Example2 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack justifyContent="center">
            <Popover
                portal={true}
                trigger={<Button onClick={toggleOpen}>Small</Button>}
                header="Popover Header"
                headerAlign="space-between"
                footer={<BodyText size="small">Popover Footer</BodyText>}
                open={open}
                direction="t"
                width="s"
            >
                <BodyText size="small">Popover content.</BodyText>
                <BodyText size="small">Second line of text.</BodyText>
            </Popover>
        </Stack>
    );
};
render (Example2);
const Example3 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
<Stack justifyContent="center">
    <Popover
        portal={true}
        trigger={<Button onClick={toggleOpen}>Medium</Button>}
        header="Popover Header"
        headerAlign="space-between"
        footer={<BodyText size="small">Popover Footer</BodyText>}
        open={open}
        direction="t"
        width="m"
    >
        <BodyText size="small">Popover content.</BodyText>
        <BodyText size="small">Second line of text.</BodyText>
    </Popover>
</Stack>
    );
};
render (Example3);
const Example4 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
<Stack justifyContent="center">
    <Popover
        portal={true}
        trigger={<Button onClick={toggleOpen}>Large</Button>}
        header="Popover Header"
        headerAlign="space-between"
        footer={<BodyText size="small">Popover Footer</BodyText>}
        open={open}
        direction="t"
        width="l"
    >
        <BodyText size="small">Popover content.</BodyText>
        <BodyText size="small">Second line of text.</BodyText>
    </Popover>
</Stack>
    );
};
render (Example4);
const Example5 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
<Popover
    portal={true}
    trigger={
        <Card onClick={toggleOpen}>
            Match the width of the wrapped element (click to open)
        </Card>
    }
    sharp
    open={open}
    direction="b"
    width="100"
    padding="s"
>
    <BodyText size="small">Popover content.</BodyText>
    <BodyText size="small">Second line of text.</BodyText>
</Popover>
    );
};
render (Example5);

Padding

There are three configurable padding options for the overall modal: small, medium, and large. These options are independent of the width properties.

Small

Made for small, simple dropdowns.

const Example6 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
<div style={{ height: '160px' }}>
    <Popover
        trigger={<Button inactive iconName='more_vert' />}
        open={open}
        sharp
        direction="br"
        header="Popover Header"
        width="auto"
        padding="s"
    >
        <BodyText size="small">Popover content.</BodyText>
        <BodyText size="small">Second line of text.</BodyText>
    </Popover>
</div>
    );
};
render (Example6);

Medium

Default — for most use cases. Complex dropdowns, small–medium sized interactive overlays on screen.

const Example7 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
<Stack alignItems="center" style={{ height: '320px' }}>
    <Popover
        trigger={<Button inactive iconName='more_vert' />}
        header={
            <Stack justifyContent="space-between" alignItems="center" className="w-100">
                <Headline size="small">Popover Header</Headline>
                <BodyText size="small" className="c-neutral-90">#12345</BodyText>
            </Stack>
        }
        headerAlign="space-between"
        footer={
            <ButtonGroup>
                <Button xsmall>Cancel</Button>
                <Button xsmall primary>Save</Button>
            </ButtonGroup>
        }
        footerAlign="right"
        open={open}
        direction="r"
        width="s"
        padding="m"
    >
        <TagGroup style={{ marginLeft: "-4px", marginTop: "-4px" }}>
            <Tag>A Tag</Tag>
            <Tag>Another Tag</Tag>
            <Tag>Maintence</Tag>
        </TagGroup>
        <BodyText size="small" className="m-t-1">To be in the same Business Unit group, Business Units must have the same arrival windows. Please select which business units to remove and/or adjust your arrival windows in settings.</BodyText>
    </Popover>
</Stack>
    );
};
render (Example7);

Large

Similiar in nature to a modal, these are for larger flows.

const Example8 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack alignItems="center" style={{ height: '420px' }}>
            <Popover
                trigger={<Button inactive iconName='warning' />}
                header={
                    <Stack justifyContent="space-between" alignItems="center" className="w-100">
                        <Headline>Fix Inconsistent Arrival Windows</Headline>
                        <BodyText className="c-neutral-90">#12345</BodyText>
                    </Stack>
                }
                headerAlign="space-between"
                footer={
                    <ButtonGroup>
                        <Button small>Cancel</Button>
                        <Button small negative>Remove Selected</Button>
                    </ButtonGroup>
                }
                footerAlign="right"
                open={open}
                direction="r"
                width="m"
                padding="l"
            >
                <BodyText subdued>To be in the same Business Unit group, Business Units must have the same arrival windows. Please select which business units to remove and/or adjust your arrival windows in <Link primary>settings</Link>.</BodyText>

                <Form className="m-t-3">
                    <Form.Group grouped>
                        <Form.Radio
                            label={<label><strong>Rebel Alliance</strong><BodyText el="div" size="small" className="m-b-half">The Good Guys</BodyText></label>}
                            value="1"
                        />
                        <Form.Radio
                            label={<label><strong>Galactic Empire</strong><BodyText el="div" size="small" className="m-b-half">The Bad Guys</BodyText></label>}
                            value="2"
                        />
                    </Form.Group>
                </Form>
            </Popover>
        </Stack>
    );
};
render (Example8);

Header & Footer Sections

Popovers have optional sections for a header and footer. This can be useful when the body section is a scrollable area.

Body Sections

The body section is where most or all of the content of a popover exists. It exists between the optional popover header and footer.

Line Divider

A divider can be used to separate out sections within a popover. By default, dividers extend the full width of the popover, and do not provide any built in padding above or below it.

const Example9 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack alignItems='center' justifyContent='center' spacing='4' className='p-4' style={{ height: '200px' }}>
            <Popover
                trigger={<Button inactive>Element</Button>}
                open={true}
                direction="r"
                width="xs"
                padding="s"
            >
                <Form className="w-100 m-b-2">
                    <Form.Input placeholder="Message Jane Doe" label="label" className="w-100" />
                </Form>
                <Popover.Divider />
                <Form className="w-100 m-y-2">
                    <Form.Input placeholder="Message Jane Doe" label="label" className="w-100" />
                </Form>
                <Popover.Divider />
                <Form className="w-100 m-t-2">
                    <Form.Input placeholder="Message Jane Doe" label="label" className="w-100" />
                </Form>
            </Popover>
        </Stack>
    );
};
render (Example9);

Full-width Image

A full-width image can be applied to the header section of a popover.

const Example10 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack alignItems="center" className="example-full" style={{ height: '460px' }}>
            <style>{`
            .demo-shadow {
                    box-sizing: border-box;
                    position: absolute;
                    bottom: 0;
                    left: 0;
                    z-index: 2;
                    background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%);
                    width: calc(100% + 48px);
                    height: calc(100% + 32px);
                    display: flex;
                    align-items: flex-end;
                    padding: 16px 24px;
                    transition: .1s all ease;
                    opacity: 0.01;
                }
                .demo-shadow:hover {
                opacity: 1;
                }
                .demo-shadow:hover .Text { opacity: 1; }
            `}</style>
            <Popover
                trigger={<Button inactive iconName='more_vert' />}
                header={
                                <div className="d-f align-items-start" style={{ position: 'relative' }}>
                                    <div className="demo-shadow">
                                        <Headline className="c-white" size="small">Jane Doe</Headline>
                                    </div>
                                <img src="http://media.comicbook.com/2016/05/ig88-181139.jpg" alt="Stock Image" style={{maxWidth: '100%'}}/>
                        </div>
                }
                headerNoPadding
                open={open}
                direction="r"
                width="s"
                padding="m"
                className="of-hidden"
            >
                <BodyText size="small" subdued className="m-b-1" style={{marginTop: "-8px"}}>10:22 AM Local Time</BodyText>
                <Popover.Divider />
                <BodyText size="small" className="m-b-1 m-t-1">Clock In</BodyText>
                <BodyText size="small" className="m-b-1">Start Meal</BodyText>
                <BodyText size="small" className="m-b-1">Non-job timesheets</BodyText>
                <BodyText size="small" className="m-b-1">Non-job purchase orders</BodyText>
                <Popover.Divider />
                <Form className="w-100 p-t-2">
                    <Form.Input placeholder="Message Jane Doe" className="w-100" />
                </Form>
            </Popover>
        </Stack>
    );
};
render (Example10);

No Padding Sections

Overrides the popover's built in padding (via negative margins) for a particular section. This can be used to introduce a full-width background color to a section.

const Example11 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack className="example-full">
            <style>{`
            .demo-hover {
                    background: white;
                    transition: .1s all ease;
                }
                .demo-hover:hover {
                    background: #1360D9;
                    color: white;
                    cursor: pointer;
                }
            `}</style>
            <div style={{ height: '340px' }} />
            <Stack alignItems="flex-start" className="w-100 justify-content-between">
                <Popover
                    trigger={<Button inactive iconName='more_vert' />}
                    headerNoPadding
                    open={open}
                    header="Popover Header"
                    direction="br"
                    width="xs"
                    padding="m"
                    className="of-hidden"
                >
                    No padding on header area
                </Popover>
                <Popover
                    trigger={<Button inactive iconName='more_vert' />}
                    contentNoPadding
                    open={open}
                    header="Popover Header"
                    direction="bl"
                    width="xs"
                    padding="m"
                    className="of-hidden"
                >
                    No padding on content area
                </Popover>
            </Stack>
        </Stack>
    );
};
render (Example11);

Scrollable Sections

The body content of a popover can be given a fixed height and become scrollable. The header and footer content will remain fixed at the bottom.

const Example12 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <Stack alignItems="center" style={{ height: '520px' }}>
            <Popover
                header={
                    <Stack justifyContent="space-between" alignItems="center" className="w-100">
                        <Headline size="small">Popover Header</Headline>
                        <BodyText size="small" className="c-neutral-90">#12345</BodyText>
                    </Stack>
                }
                headerAlign="space-between"
                footer={
                    <ButtonGroup>
                        <Button small>Cancel</Button>
                        <Button small primary>Remove Selected</Button>
                    </ButtonGroup>
                }
                scrollHeight="320px"
                footerAlign="right"
                open={open}
                direction="r"
                width="m"
                padding="l"
            >
                    <BodyText>
                        Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
                        Paleo thundercats godard glossier +1, iceland anim.
                        Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
                        chips minim.
                        Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
                        ipsum fashion axe copper mug.
                        Fingerstache put a bird on it palo santo craft beer.
                    </BodyText>
                    <BodyText>
                        Adaptogen officia cred ut enamel pin.
                        Man bun fixie blue bottle minim proident franzen raw denim fanny pack, church-key edison bulb
                        butcher lumbersexual vaporware ethical YOLO.
                        Mlkshk elit austin succulents live-edge poke esse pork belly williamsburg consectetur
                        helvetica craft beer put a bird on it pop-up aliqua.
                        Viral irure synth laboris laborum.
                    </BodyText>
                    <BodyText>
                        Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
                        Paleo thundercats godard glossier +1, iceland anim.
                        Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
                        chips minim.
                        Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
                        ipsum fashion axe copper mug.
                        Fingerstache put a bird on it palo santo craft beer.
                    </BodyText>
            </Popover>
        </Stack>
    );
};
render (Example12);

Directions

Popovers can appear in many directions relative to an element. Direction is manually controlled. The default is to appear above the element.

const Example13 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <div className="w-100 example">
            <style>{`
            .example .Button {
                    width: 130px;
                }
            `}</style>
        <Stack className="w-100 m-b-8" alignItems='flex-end' justifyContent='space-around' style={{ height: '240px' }}>
            <Popover
                trigger={<Button inactive>Top Left</Button>}
                header="Popover Header"
                headerAlign="space-between"
                footer={<BodyText size="small">Popover Footer</BodyText>}
                open={open}
                direction="tl"
                width="xs"
            >
                <BodyText size="small">Popover content.</BodyText>
                <BodyText size="small">Second line of text.</BodyText>
            </Popover>

            <Popover
                trigger={<Button inactive>Top</Button>}
                header="Popover Header"
                headerAlign="space-between"
                footer={<BodyText size="small">Popover Footer</BodyText>}
                open={open}
                direction="t"
                width="xs"
            >
                <BodyText size="small">Popover content.</BodyText>
                <BodyText size="small">Second line of text.</BodyText>
            </Popover>

            <Popover
                trigger={<Button inactive>Top Right</Button>}
                header="Popover Header"
                headerAlign="space-between"
                footer={<BodyText size="small">Popover Footer</BodyText>}
                open={open}
                direction="tr"
                width="xs"
            >
                <BodyText size="small">Popover content.</BodyText>
                <BodyText size="small">Second line of text.</BodyText>
            </Popover>

        </Stack>

            <Stack justifyContent="center" className="d-f w-100 justify-content-around" spacing={5}>
                <Stack direction='column' alignItems='center' justifyContent='space-around' style={{ height: '500px' }}>
                    <Popover
                        portal={true}
                        trigger={<Button inactive>Left Top</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="lt"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                    <Popover
                        portal={true}
                        trigger={<Button inactive>Left</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="l"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                    <Popover
                        portal={true}
                        trigger={<Button inactive>Left Bottom</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="lb"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                </Stack>

                <Stack direction='column' alignItems='center' justifyContent='space-around' style={{ height: '500px' }}>
                    <Popover
                        portal={true}
                        trigger={<Button inactive>Right Top</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="rt"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                    <Popover
                        portal={true}
                        trigger={<Button inactive>Right</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="r"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                    <Popover
                        portal={true}
                        trigger={<Button inactive>Right Bottom</Button>}
                        header="Popover Header"
                        headerAlign="space-between"
                        footer={<BodyText size="small">Popover Footer</BodyText>}
                        open={open}
                        direction="rb"
                        width="xs"
                    >
                        <BodyText size="small">Popover content.</BodyText>
                        <BodyText size="small">Second line of text.</BodyText>
                    </Popover>

                </Stack>
            </Stack>

            <Stack className="m-t-8" alignItems='flex-start' justifyContent='space-around' style={{ height: '240px' }}>
                <Popover
                    trigger={<Button inactive>Bottom Left</Button>}
                    header="Popover Header"
                    headerAlign="space-between"
                    footer={<BodyText size="small">Popover Footer</BodyText>}
                    open={open}
                    direction="bl"
                    width="xs"
                >
                    <BodyText size="small">Popover content.</BodyText>
                    <BodyText size="small">Second line of text.</BodyText>
                </Popover>

                <Popover
                    trigger={<Button inactive>Bottom</Button>}
                    header="Popover Header"
                    headerAlign="space-between"
                    footer={<BodyText size="small">Popover Footer</BodyText>}
                    open={open}
                    direction="b"
                    width="xs"
                >
                    <BodyText size="small">Popover content.</BodyText>
                    <BodyText size="small">Second line of text.</BodyText>
                </Popover>

                <Popover
                    trigger={<Button inactive>Bottom Right</Button>}
                    header="Popover Header"
                    headerAlign="space-between"
                    footer={<BodyText size="small">Popover Footer</BodyText>}
                    open={open}
                    direction="br"
                    width="xs"
                >
                    <BodyText size="small">Popover content.</BodyText>
                    <BodyText size="small">Second line of text.</BodyText>
                </Popover>
            </Stack>

        </div>
    );
};
render (Example13);

Open on Hover

The popover can also be triggered through hover. In this scenario, they behave similar to the tooltip, but with more potential information to display.

const Example14 = () => {
    const [open, setOpen] = React.useState(false);
    const toggleOpen = () => setOpen(!open);

    return (
        <span>
            <Popover
                trigger={<Button onMouseOver={toggleOpen} onMouseOut={toggleOpen} >Hover me!</Button>}
                open={open}
                direction="r"
                width="xs"
                el="span"
            >
                <BodyText size="small">Hello</BodyText>
            </Popover>
        </span>
    );
};
render (Example14);

Close on Body and Button Click

More complex open and close interactions are possible. For example, a popover can be opened with both a hover or a mouse click, and a popover can be closed by clicking outside body content.

const PopoverExample = (props) => {
    const [open, setOpen] = React.useState(props.open);

    React.useEffect(() => {
        document.addEventListener('mousedown', handleClick, false);

        // returned function will be called on component unmount
        return () => {
            document.removeEventListener('mousedown', handleClick, false);
        }
      }, []);

    const handleClick = (e) => {
        !this.node.contains(e.target) ? closePopover : false;
    }

    const openPopover = () => setOpen(true);
    const closePopover = () => setOpen(false);
    const toggleOpen = () => setOpen(!open);

    return (
        <span ref={node => this.node = node}>
            <Popover
                trigger={
                    <Button
                        onMouseOver={openPopover}
                        onClick={toggleOpen}
                    >
                        Hover or click me!
                    </Button>
                }
                open={open}
                direction="r"
                width="xs"
                el="span"
            >
                <BodyText size="small">Hello</BodyText>
            </Popover>
        </span>
    );
};
render (PopoverExample);

Open through a React Portal

Popovers are typically wrapped in another element. However they can be opened outside of the DOM hierarchy through a React portal.

const Example15 = () => {
    const [open, setOpen] = React.useState(true);
    const toggleOpen = () => setOpen(!open);

    return (
        <div>
            <Popover
                open={open}
                portal={true}
                direction="r"
                width="xs"
                el="span"
                trigger={<Button onClick={toggleOpen}>Click me</Button>}
            >
                <BodyText size="small">Hello</BodyText>
            </Popover>
        </div>
    );
};
render (Example15);

Best Practices

  • Popovers should be placed next to the element it is related to.
  • Popover information should not be critical to the page.
  • Can be used to suggest or guide users through a page.

Related Components

  • To interrupt the user flow with content, use a modal, dialog, or takeover.
  • For adding quick clarity to an element via hover, use a tooltip.
  • For popover with actionable menu, use a action menu.
  • For popover for input or dropdown, use a select.

Importing

import { Popover } from '@servicetitan/design-system';