Skip to content

Commit

Permalink
feat: update Popover styles (#3032)
Browse files Browse the repository at this point in the history
  • Loading branch information
shleewhite committed Feb 23, 2023
1 parent 3c89fd8 commit 5f5790d
Show file tree
Hide file tree
Showing 7 changed files with 612 additions and 128 deletions.
11 changes: 11 additions & 0 deletions .changeset/witty-avocados-play.md
@@ -0,0 +1,11 @@
---
'@twilio-paste/popover': minor
'@twilio-paste/core': minor
---

[Popover] Add new props:

- initialFocusRef: focuses a ref when the Popover opens
- width: sets the width of the Popover, up to size50.

Update styles to align with new Paste Twilio theme
34 changes: 33 additions & 1 deletion packages/paste-core/components/popover/__tests__/index.spec.tsx
Expand Up @@ -5,7 +5,7 @@ import {Theme} from '@twilio-paste/theme';
import {CustomizationProvider} from '@twilio-paste/customization';
import {Text} from '@twilio-paste/text';

import {PopoverTop, StateHookExample, BadgePopover} from '../stories/index.stories';
import {PopoverTop, StateHookExample, BadgePopover, InitialFocus} from '../stories/index.stories';
import {Popover, PopoverContainer, PopoverButton} from '../src';

describe('Popover', () => {
Expand Down Expand Up @@ -39,6 +39,38 @@ describe('Popover', () => {
expect(renderedPopover.getAttribute('role')).toEqual('dialog');
});

it('should focus the close button when the popover opens', async () => {
render(
<Theme.Provider theme="default">
<PopoverTop />
</Theme.Provider>
);

const renderedPopoverButton = screen.getByRole('button');

await waitFor(() => {
userEvent.click(renderedPopoverButton);
});

expect(document.activeElement).toEqual(screen.getByRole('button', {name: 'Close popover'}));
});

it('should focus the initialFocusRef when the popver opens', async () => {
render(
<Theme.Provider theme="default">
<InitialFocus />
</Theme.Provider>
);

const renderedPopoverButton = screen.getByRole('button');

await waitFor(() => {
userEvent.click(renderedPopoverButton);
});

expect(document.activeElement).toEqual(screen.getByRole('button', {name: 'Click me'}));
});

it('should render a popover and show/hide on external button click', async () => {
render(
<Theme.Provider theme="default">
Expand Down
2 changes: 1 addition & 1 deletion packages/paste-core/components/popover/package.json
Expand Up @@ -3,7 +3,7 @@
"version": "10.0.2",
"category": "interaction",
"status": "production",
"description": "A Popover is a page overlay triggered by a click that displays additional interactive content.",
"description": "A Popover is a page overlay triggered by a button that displays additional interactive content.",
"author": "Twilio Inc.",
"license": "MIT",
"main:dev": "src/index.tsx",
Expand Down
27 changes: 21 additions & 6 deletions packages/paste-core/components/popover/src/Popover.tsx
Expand Up @@ -7,20 +7,22 @@ import {CloseIcon} from '@twilio-paste/icons/esm/CloseIcon';
import {StyledBase} from '@twilio-paste/theme';
import {NonModalDialogPrimitive} from '@twilio-paste/non-modal-dialog-primitive';
import {ScreenReaderOnly} from '@twilio-paste/screen-reader-only';
import type {ResponsiveValue} from '@twilio-paste/styling-library';

import {PopoverArrow} from './PopoverArrow';
import {PopoverContext} from './PopoverContext';

const StyledPopover = React.forwardRef<HTMLDivElement, BoxProps>(({style, ...props}, ref) => {
const StyledPopover = React.forwardRef<HTMLDivElement, BoxProps>(({style, width, ...props}, ref) => {
return (
<Box
{...safelySpreadBoxProps(props)}
width={width}
backgroundColor="colorBackgroundBody"
borderStyle="solid"
borderWidth="borderWidth10"
borderColor="colorBorderWeaker"
borderRadius="borderRadius20"
boxShadow="shadowCard"
borderRadius="borderRadius30"
boxShadow="shadowLow"
maxWidth="size50"
zIndex="zIndex80"
_focus={{outline: 'none'}}
Expand All @@ -32,22 +34,33 @@ const StyledPopover = React.forwardRef<HTMLDivElement, BoxProps>(({style, ...pro

StyledPopover.displayName = 'StyledPopover';

type WidthOptions = 'size10' | 'size20' | 'size30' | 'size40' | 'size50';

export interface PopoverProps extends Pick<BoxProps, 'element'> {
'aria-label': string;
children: React.ReactNode;
i18nDismissLabel?: string;
width?: ResponsiveValue<WidthOptions>;
initialFocusRef?: React.RefObject<any>;
}

const Popover = React.forwardRef<HTMLDivElement, PopoverProps>(
({children, element = 'POPOVER', i18nDismissLabel = 'Close popover', ...props}, ref) => {
({children, element = 'POPOVER', i18nDismissLabel = 'Close popover', initialFocusRef, ...props}, ref) => {
const popover = React.useContext(PopoverContext);

React.useEffect(() => {
if (popover.visible && initialFocusRef) {
initialFocusRef.current?.focus();
}
}, [popover.visible, initialFocusRef]);

return (
<NonModalDialogPrimitive {...(popover as any)} {...props} as={StyledPopover} ref={ref} preventBodyScroll={false}>
{/* import Paste Theme Based Styles due to portal positioning. */}
<StyledBase>
<PopoverArrow {...(popover as any)} />
<Box element={element} paddingX="space80" paddingY="space70">
<Box position="absolute" right={8} top={8}>
<Box element={element} padding="space90">
<Box position="absolute" right={16} top={16}>
<Button
element={`${element}_CLOSE_BUTTON`}
variant="secondary_icon"
Expand All @@ -73,6 +86,8 @@ Popover.propTypes = {
children: PropTypes.node.isRequired,
element: PropTypes.string,
i18nDismissLabel: PropTypes.string,
initialFocusRef: PropTypes.object as any,
width: PropTypes.oneOf(['size10', 'size20', 'size30', 'size40', 'size50'] as WidthOptions[]),
};

Popover.displayName = 'Popover';
Expand Down
75 changes: 70 additions & 5 deletions packages/paste-core/components/popover/stories/index.stories.tsx
Expand Up @@ -39,23 +39,88 @@ export const Default = (): JSX.Element => {
);
};

export const SmallerSize = (): JSX.Element => {
export const Sizes: React.FC = () => {
return (
<Box height="300px" display="grid" gridTemplateColumns="1fr 1fr" gridTemplateRows="auto auto auto">
<Box>
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width="size50">
<Text as="span">width is &apos;size50&apos;</Text>
</Popover>
</PopoverContainer>
</Box>
<Box>
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width="size40">
<Text as="span">width is &apos;size40&apos;</Text>
</Popover>
</PopoverContainer>
</Box>
<Box>
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width="size30">
<Text as="span">width is &apos;size30&apos;</Text>
</Popover>
</PopoverContainer>
</Box>
<Box>
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width="size20">
<Text as="span">width is &apos;size20&apos;</Text>
</Popover>
</PopoverContainer>
</Box>
<Box>
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width="size10">
<Text as="span">width is &apos;size10&apos;</Text>
</Popover>
</PopoverContainer>
</Box>
</Box>
);
};

export const InitialFocus: React.FC = () => {
const uniqueBaseID = useUID();
const buttonRef = React.createRef<HTMLButtonElement>();

return (
<Box height="300px">
<PopoverContainer baseId={uniqueBaseID} visible>
<PopoverContainer baseId={uniqueBaseID}>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover">
<Box width="size30">
<Popover aria-label="Popover" initialFocusRef={buttonRef}>
<Box display="flex" flexDirection="column" rowGap="space70">
<Text as="span">This is the Twilio styled popover that you can use in all your applications.</Text>
<Box>
<Button variant="primary" size="small" ref={buttonRef}>
Click me
</Button>
</Box>
</Box>
</Popover>
</PopoverContainer>
</Box>
);
};

export const WideContent = (): JSX.Element => {
export const ResponsiveWidth: StoryFn = () => {
return (
<PopoverContainer baseId={useUID()} visible>
<PopoverButton variant="primary">Open popover</PopoverButton>
<Popover aria-label="Popover" width={['size20', 'size40']}>
<Text as="span">Responsive width Popover</Text>
</Popover>
</PopoverContainer>
);
};

export const WideContent: React.FC = () => {
const date1ID = useUID();
const time1ID = useUID();
const date2ID = useUID();
Expand Down

0 comments on commit 5f5790d

Please sign in to comment.