From db9f96657c8e3d9230ef17e1a0ab4658b6016dca Mon Sep 17 00:00:00 2001 From: Jade Pennig Date: Fri, 8 Oct 2021 10:17:22 -0500 Subject: [PATCH] feat(popover): add customization (#1915) * feat(popover): add customization, tests, and stories --- .changeset/warm-readers-decide.md | 6 + .../popover/__tests__/index.spec.tsx | 112 ++++++++++++++++++ .../components/popover/src/Popover.tsx | 26 ++-- .../components/popover/src/PopoverButton.tsx | 26 ++-- .../popover/stories/index.stories.tsx | 34 ++++++ 5 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 .changeset/warm-readers-decide.md diff --git a/.changeset/warm-readers-decide.md b/.changeset/warm-readers-decide.md new file mode 100644 index 0000000000..8f5a5613f4 --- /dev/null +++ b/.changeset/warm-readers-decide.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/popover': minor +'@twilio-paste/core': minor +--- + +[Popover] Enable component to respect element customizations set on the customization provider. Component now enables setting an element name on the underlying HTML element and checks the emotion theme object to determine whether it should merge in custom styles to the ones set by the component author. diff --git a/packages/paste-core/components/popover/__tests__/index.spec.tsx b/packages/paste-core/components/popover/__tests__/index.spec.tsx index 14736fa57b..127d687bd0 100644 --- a/packages/paste-core/components/popover/__tests__/index.spec.tsx +++ b/packages/paste-core/components/popover/__tests__/index.spec.tsx @@ -1,9 +1,15 @@ import * as React from 'react'; import {render, screen, waitFor} from '@testing-library/react'; +import {matchers} from 'jest-emotion'; import {Theme} from '@twilio-paste/theme'; +import {CustomizationProvider} from '@twilio-paste/customization'; +import {Text} from '@twilio-paste/text'; // @ts-ignore typescript doesn't like js imports import axe from '../../../../../.jest/axe-helper'; import {PopoverTop, StateHookExample} from '../stories/index.stories'; +import {Popover, PopoverContainer, PopoverButton} from '../src'; + +expect.extend(matchers); describe('Popover', () => { describe('Render', () => { @@ -78,4 +84,110 @@ describe('Popover', () => { expect(results).toHaveNoViolations(); }); }); + + describe('Customization', () => { + it('should set default data-paste-element attribute on Popover and customizable children and respect custom styles', (): void => { + render( + + + + Open popover + + + This is the Twilio styled popover that you can use in all your applications. + + + + ); + + const popoverComp = screen.getByTestId('popover'); + const popoverButton = screen.getByTestId('popover-button'); + + // presence of popover hooks + expect(popoverComp.querySelector('[data-paste-element="POPOVER"]')).toBeInTheDocument(); + expect(popoverComp.querySelector('[data-paste-element="POPOVER_CLOSE_BUTTON"]')).toBeInTheDocument(); + expect(popoverComp.querySelector('[data-paste-element="POPOVER_CLOSE_ICON"]')).toBeInTheDocument(); + expect(popoverButton).toHaveAttribute('data-paste-element', 'POPOVER_BUTTON'); + + // applied style rules + expect(popoverComp.querySelector('[data-paste-element="POPOVER"]')).toHaveStyleRule( + 'background-color', + 'rgb(244,244,246)' + ); + expect(popoverComp.querySelector('[data-paste-element="POPOVER_CLOSE_BUTTON"]')).toHaveStyleRule( + 'background-color', + 'rgb(18,28,45)' + ); + expect(popoverComp.querySelector('[data-paste-element="POPOVER_CLOSE_ICON"]')).toHaveStyleRule( + 'color', + 'rgb(255,255,255)' + ); + expect(popoverButton).toHaveStyleRule('background-color', 'rgb(6,3,58)'); + }); + + it('should set a custom element name and properly apply styles to Popover and customizable children', (): void => { + render( + + + + Open popover + + + This is the Twilio styled popover that you can use in all your applications. + + + + ); + + const popoverComp = screen.getByTestId('popover'); + const popoverButton = screen.getByTestId('popover-button'); + + expect(popoverComp.querySelector('[data-paste-element="MYPOPOVER"]')).toHaveStyleRule( + 'background-color', + 'rgb(244,244,246)' + ); + expect(popoverComp.querySelector('[data-paste-element="MYPOPOVER_CLOSE_BUTTON"]')).toHaveStyleRule( + 'background-color', + 'rgb(18,28,45)' + ); + expect(popoverComp.querySelector('[data-paste-element="MYPOPOVER_CLOSE_ICON"]')).toHaveStyleRule( + 'color', + 'rgb(255,255,255)' + ); + + expect(popoverButton).toHaveStyleRule('background-color', 'rgb(6,3,58)'); + }); + }); }); diff --git a/packages/paste-core/components/popover/src/Popover.tsx b/packages/paste-core/components/popover/src/Popover.tsx index 3489129ddd..6f26e3e046 100644 --- a/packages/paste-core/components/popover/src/Popover.tsx +++ b/packages/paste-core/components/popover/src/Popover.tsx @@ -28,21 +28,22 @@ const StyledPopover = React.forwardRef(({style, ...pro ); }); -export interface PopoverProps { +export interface PopoverProps extends Pick { 'aria-label': string; children: React.ReactNode; } -const Popover = React.forwardRef(({children, ...props}, ref) => { +const Popover = React.forwardRef(({children, element = 'POPOVER', ...props}, ref) => { const popover = React.useContext(PopoverContext); return ( {/* import Paste Theme Based Styles due to portal positioning. */} - + {children} @@ -61,12 +68,11 @@ const Popover = React.forwardRef(({children, ...pr ); }); -if (process.env.NODE_ENV === 'development') { - Popover.propTypes = { - 'aria-label': PropTypes.string.isRequired, - children: PropTypes.node.isRequired, - }; -} +Popover.propTypes = { + 'aria-label': PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + element: PropTypes.string, +}; Popover.displayName = 'Popover'; export {Popover}; diff --git a/packages/paste-core/components/popover/src/PopoverButton.tsx b/packages/paste-core/components/popover/src/PopoverButton.tsx index ecefff2fca..54191a6c36 100644 --- a/packages/paste-core/components/popover/src/PopoverButton.tsx +++ b/packages/paste-core/components/popover/src/PopoverButton.tsx @@ -1,34 +1,40 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import type {ButtonProps} from '@twilio-paste/button'; +import type {BoxProps} from '@twilio-paste/box'; import {Button} from '@twilio-paste/button'; import {NonModalDialogDisclosurePrimitive} from '@twilio-paste/non-modal-dialog-primitive'; import {PopoverContext} from './PopoverContext'; -export interface PopoverButtonProps extends ButtonProps { +export interface PopoverButtonProps extends ButtonProps, Pick { id?: string; children: React.ReactNode; toggle?: () => void; } const PopoverButton = React.forwardRef( - ({children, ...popoverButtonProps}, ref) => { + ({children, element = 'POPOVER_BUTTON', ...popoverButtonProps}, ref) => { const popover = React.useContext(PopoverContext); return ( - + {children} ); } ); -if (process.env.NODE_ENV === 'development') { - PopoverButton.propTypes = { - id: PropTypes.string, - children: PropTypes.node.isRequired, - toggle: PropTypes.func, - }; -} +PopoverButton.propTypes = { + id: PropTypes.string, + children: PropTypes.node.isRequired, + toggle: PropTypes.func, + element: PropTypes.string, +}; PopoverButton.displayName = 'PopoverButton'; export {PopoverButton}; diff --git a/packages/paste-core/components/popover/stories/index.stories.tsx b/packages/paste-core/components/popover/stories/index.stories.tsx index b3f5af68f9..d73c9245e1 100644 --- a/packages/paste-core/components/popover/stories/index.stories.tsx +++ b/packages/paste-core/components/popover/stories/index.stories.tsx @@ -3,6 +3,7 @@ import {Box} from '@twilio-paste/box'; import {Button} from '@twilio-paste/button'; import {Stack} from '@twilio-paste/stack'; import {Text} from '@twilio-paste/text'; +import {CustomizationProvider} from '@twilio-paste/customization'; import {usePopoverState, Popover, PopoverContainer, PopoverButton} from '../src'; // eslint-disable-next-line import/no-default-export @@ -98,3 +99,36 @@ export const StateHookExample: React.FC = () => { ); }; + +export const Customization: React.FC = () => { + return ( + + + + Open popover + + This is the Twilio styled popover that you can use in all your applications. + + + + + ); +};