From cd8ab7e35b918e2b7fdc44c5c6848fcbc1c07367 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:47:16 +0100 Subject: [PATCH 01/11] [ARGG-1157][BpkTooltip]: Migrate tooltip to floating-ui --- .../{examples.js => examples.tsx} | 27 +- .../{stories.js => stories.ts} | 6 +- packages/bpk-component-tooltip/index.ts | 8 +- .../src/BpkTooltip-test.tsx | 43 ++-- .../src/BpkTooltip.module.scss | 100 +------- .../bpk-component-tooltip/src/BpkTooltip.tsx | 142 +++++++++-- .../src/BpkTooltipPortal-test.tsx | 94 ------- .../src/BpkTooltipPortal.tsx | 230 ------------------ .../__snapshots__/BpkTooltip-test.tsx.snap | 93 ------- .../BpkTooltipPortal-test.tsx.snap | 138 ----------- .../src/accessibility-test.tsx | 11 +- 11 files changed, 158 insertions(+), 734 deletions(-) rename examples/bpk-component-tooltip/{examples.js => examples.tsx} (88%) rename examples/bpk-component-tooltip/{stories.js => stories.ts} (91%) delete mode 100644 packages/bpk-component-tooltip/src/BpkTooltipPortal-test.tsx delete mode 100644 packages/bpk-component-tooltip/src/BpkTooltipPortal.tsx delete mode 100644 packages/bpk-component-tooltip/src/__snapshots__/BpkTooltip-test.tsx.snap delete mode 100644 packages/bpk-component-tooltip/src/__snapshots__/BpkTooltipPortal-test.tsx.snap diff --git a/examples/bpk-component-tooltip/examples.js b/examples/bpk-component-tooltip/examples.tsx similarity index 88% rename from examples/bpk-component-tooltip/examples.js rename to examples/bpk-component-tooltip/examples.tsx index 9bdd003567..9887f9f7ce 100644 --- a/examples/bpk-component-tooltip/examples.js +++ b/examples/bpk-component-tooltip/examples.tsx @@ -16,8 +16,6 @@ * limitations under the License. */ -/* @flow strict */ - import { useRef, forwardRef } from 'react'; import { @@ -41,7 +39,7 @@ const Heading = withDefaultProps(BpkText, { tagName: 'h4', }); -const HeadingComponent = forwardRef((props: { children: Node }, ref) => ( +const HeadingComponent = forwardRef((props, ref) => (
{props.children}
@@ -148,28 +146,6 @@ const LinkExample = () => { ); }; -const PopperModifiersExample = () => { - const target = useRef(null); - - return ( -
- BER} - popperModifiers={[ - { - name: 'flip', - options: { enabled: false }, - }, - ]} - > - Berlin Brandenburg Airport - -
- ); -}; - const FocusExample = () => { const targetRef1 = useRef(null); const targetRef2 = useRef(null); @@ -219,6 +195,5 @@ export { SideExample, NoPaddingExample, LinkExample, - PopperModifiersExample, FocusExample, }; diff --git a/examples/bpk-component-tooltip/stories.js b/examples/bpk-component-tooltip/stories.ts similarity index 91% rename from examples/bpk-component-tooltip/stories.js rename to examples/bpk-component-tooltip/stories.ts index 3e682fabfa..ee4f0ba495 100644 --- a/examples/bpk-component-tooltip/stories.js +++ b/examples/bpk-component-tooltip/stories.ts @@ -16,8 +16,7 @@ * limitations under the License. */ - -import BpkTooltip from '../../packages/bpk-component-tooltip/src/BpkTooltipPortal'; +import BpkTooltip from '../../packages/bpk-component-tooltip/src/BpkTooltip'; import { DefaultExample, @@ -25,7 +24,6 @@ import { SideExample, NoPaddingExample, LinkExample, - PopperModifiersExample, FocusExample, } from './examples'; @@ -42,6 +40,4 @@ export const WithoutPadding = NoPaddingExample; export const OnALink = LinkExample; -export const PopperModifiers = PopperModifiersExample; - export const Focus = FocusExample; diff --git a/packages/bpk-component-tooltip/index.ts b/packages/bpk-component-tooltip/index.ts index 831c008acd..385c5617d4 100644 --- a/packages/bpk-component-tooltip/index.ts +++ b/packages/bpk-component-tooltip/index.ts @@ -16,12 +16,12 @@ * limitations under the License. */ -import BpkTooltipPortal from './src/BpkTooltipPortal'; +import BpkTooltip from './src/BpkTooltip'; import { TOOLTIP_TYPES } from './src/constants'; -import type { Props } from './src/BpkTooltipPortal'; +import type { Props } from './src/BpkTooltip'; -export type BpkTooltipPortalProps = Props; +export type BpkTooltipProps = Props; -export default BpkTooltipPortal; +export default BpkTooltip; export { TOOLTIP_TYPES }; diff --git a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx index 7db4020494..ed5ce54319 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx @@ -16,47 +16,48 @@ * limitations under the License. */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; import BpkTooltip from './BpkTooltip'; import { TOOLTIP_TYPES } from './constants'; describe('BpkTooltip', () => { it('should render correctly', () => { - const { asFragment } = render( - My tooltip content, + const target = (My tooltip target); + + render( + My tooltip content, ); - expect(asFragment()).toMatchSnapshot(); + expect(screen.getByText('My tooltip content')).toBeVisible(); }); it('should render correctly with type=dark', () => { - const { asFragment } = render( - + const target = (My tooltip target); + + const { container } = render( + My tooltip content , ); - - expect(asFragment()).toMatchSnapshot(); + + expect(screen.getByText('My tooltip content')).toBeVisible(); + expect(container.querySelector('.bpk-tooltip__inner--dark')).not.toBeNull(); + expect(container.querySelector('.bpk-tooltip__arrow--dark')).not.toBeNull(); }); it('should render correctly with "padded" attribute equal to false', () => { - const { asFragment } = render( - - My tooltip content - , - ); - - expect(asFragment()).toMatchSnapshot(); - }); + const target = (My tooltip target); - it('should render correctly with "className" attribute', () => { - const { asFragment } = render( - + const { container } = render( + My tooltip content , ); - - expect(asFragment()).toMatchSnapshot(); + + expect(screen.getByText('My tooltip content')).toBeVisible(); + expect(container.querySelector('.bpk-tooltip__inner--padded')).toBeNull(); }); + }); diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.module.scss b/packages/bpk-component-tooltip/src/BpkTooltip.module.scss index d2a10fde81..d0d2109127 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.module.scss +++ b/packages/bpk-component-tooltip/src/BpkTooltip.module.scss @@ -18,126 +18,42 @@ @use '../../unstable__bpk-mixins/tokens'; @use '../../unstable__bpk-mixins/layers'; +@use '../../unstable__bpk-mixins/radii'; +@use '../../unstable__bpk-mixins/shadows'; $arrow-size: tokens.bpk-spacing-md(); -$arrow-inset-margin: -1 * (tokens.$bpk-one-pixel-rem * 3); -$light-arrow-border-color: tokens.$bpk-line-day; $dark-background-color: tokens.$bpk-surface-contrast-day; -.bpk-tooltip-portal { +.bpk-tooltip--container { z-index: tokens.$bpk-zindex-tooltip; - - &--target { - display: inline-block; - } } .bpk-tooltip { - transition: - opacity tokens.$bpk-duration-sm ease-in-out, - transform tokens.$bpk-duration-sm ease-in-out; + transition: opacity tokens.$bpk-duration-sm ease-in-out; outline: 0; opacity: 1; + @include layers.bpk-layer; + &--appear { opacity: 0; - - .bpk-tooltip-portal[data-popper-placement='top'] - &:not(.bpk-tooltip--appear-active) { - transform: translate(0, -1rem); - transition: none; - } - - .bpk-tooltip-portal[data-popper-placement='right'] - &:not(.bpk-tooltip--appear-active) { - transform: translate(1rem, 0); - transition: none; - } - - .bpk-tooltip-portal[data-popper-placement='bottom'] - &:not(.bpk-tooltip--appear-active) { - transform: translate(0, 1rem); - transition: none; - } - - .bpk-tooltip-portal[data-popper-placement='left'] - &:not(.bpk-tooltip--appear-active) { - transform: translate(-1rem, 0); - transition: none; - } } &--appear-active { - transform: translate(0, 0); - transition: - opacity tokens.$bpk-duration-sm ease-in-out, - transform tokens.$bpk-duration-sm ease-in-out; opacity: 1; } &__arrow { - position: absolute; width: $arrow-size; height: $arrow-size; - transform: rotate(45deg); - border: tokens.$bpk-one-pixel-rem solid transparent; - background: inherit; - background-color: tokens.$bpk-surface-default-day; - - .bpk-tooltip-portal[data-popper-placement='top'] & { - bottom: $arrow-inset-margin; - border-right-color: $light-arrow-border-color; - border-bottom-color: $light-arrow-border-color; - } - - .bpk-tooltip-portal[data-popper-placement='right'] & { - left: $arrow-inset-margin; - border-bottom-color: $light-arrow-border-color; - border-left-color: $light-arrow-border-color; - } - - .bpk-tooltip-portal[data-popper-placement='bottom'] & { - top: $arrow-inset-margin; - border-top-color: $light-arrow-border-color; - border-left-color: $light-arrow-border-color; - } - - .bpk-tooltip-portal[data-popper-placement='left'] & { - right: $arrow-inset-margin; - border-top-color: $light-arrow-border-color; - border-right-color: $light-arrow-border-color; - } + fill: tokens.$bpk-surface-default-day; &--dark { - background-color: $dark-background-color; - - .bpk-tooltip-portal[data-popper-placement='top'] & { - border-right-color: $dark-background-color; - border-bottom-color: $dark-background-color; - } - - .bpk-tooltip-portal[data-popper-placement='right'] & { - border-bottom-color: $dark-background-color; - border-left-color: $dark-background-color; - } - - .bpk-tooltip-portal[data-popper-placement='bottom'] & { - border-top-color: $dark-background-color; - border-left-color: $dark-background-color; - } - - .bpk-tooltip-portal[data-popper-placement='left'] & { - border-top-color: $dark-background-color; - border-right-color: $dark-background-color; - } + fill: $dark-background-color; } } &__inner { - overflow: hidden; - - @include layers.bpk-layer; - &--padded { padding: tokens.bpk-spacing-md() tokens.bpk-spacing-base(); } diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index 63fceed3d1..7773138bd8 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -22,33 +22,104 @@ This is the component for the tooltip that is displayed to users. The actual component that developers create (i.e. the default export from this package) is BpkTooltipPortal. */ -import type { ReactNode } from 'react'; +import type { ReactNode, ReactElement } from 'react'; +import { cloneElement, useRef , useState } from 'react'; + +import { + arrow, + flip, + FloatingArrow, + offset, + shift, + useFloating, + useHover, + useInteractions, +} from '@floating-ui/react'; + +import { surfaceHighlightDay } from '@skyscanner/bpk-foundations-web/tokens/base.es6'; import { TransitionInitialMount, cssModules } from '../../bpk-react-utils'; import { ARROW_ID, TOOLTIP_TYPES } from './constants'; +import type { Placement } from '@floating-ui/react'; + + + import STYLES from './BpkTooltip.module.scss'; + const getClassName = cssModules(STYLES); -export type TooltipProps = { +// The stroke width is used to set the border width of the arrow. +const strokeWidth = 1; + +export type Props = { id: string; children: ReactNode | string; type?: (typeof TOOLTIP_TYPES)[keyof typeof TOOLTIP_TYPES]; padded?: boolean; - className?: string | null; + target: ReactElement; + ariaLabel: string; + hideOnTouchDevices?: boolean; + placement?: Placement; + isOpen?: boolean; +}; + +// This function is to ensure the arrow alignment when used on the top and bottom +// doesn't look clipped away from the tooltip. This is due to our use of box-shadows that makes it look floating away, +// so we need to compensate slightly to make it look as one. +const getArrowAlignment = (placement: Placement) => { + if (placement.includes('bottom')) { + return { bottom: '98%' }; + } if (placement.includes('top')) { + return { top: '98%' }; + } + return undefined; + }; const BpkTooltip = ({ + ariaLabel, children, - className = null, + hideOnTouchDevices = true, id, + isOpen = false, padded = true, + placement = 'bottom', + target, type = TOOLTIP_TYPES.light, ...rest -}: TooltipProps) => { - const classNames = getClassName('bpk-tooltip', className); +}: Props) => { + const [isOpenState, setIsOpenState] = useState(isOpen); + const arrowRef = useRef(null); + + const { context, floatingStyles, refs } = useFloating({ + open: isOpenState, + onOpenChange: setIsOpenState, + placement, + middleware: [ + offset(8), + flip({ crossAxis: true }), + shift(), + arrow({ element: arrowRef }), + ], + }); + + const hover = useHover(context, { + mouseOnly: true, + }); + + const { getFloatingProps, getReferenceProps } = useInteractions([hover]); + + const targetWithAccessibilityProps = cloneElement(target, { + ...getReferenceProps(), + ref: refs.setReference, + tabIndex: '0', + 'aria-label': ariaLabel, + }); + + const classNames = getClassName('bpk-tooltip'); const innerClassNames = getClassName( 'bpk-tooltip__inner', @@ -62,27 +133,44 @@ const BpkTooltip = ({ ); return ( - - - + <> + {targetWithAccessibilityProps} + {isOpenState && ( +
+ + + +
+ )} + ); }; diff --git a/packages/bpk-component-tooltip/src/BpkTooltipPortal-test.tsx b/packages/bpk-component-tooltip/src/BpkTooltipPortal-test.tsx deleted file mode 100644 index cf9f4bd056..0000000000 --- a/packages/bpk-component-tooltip/src/BpkTooltipPortal-test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Backpack - Skyscanner's Design System - * - * Copyright 2016 Skyscanner Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { render } from '@testing-library/react'; - -import { colorPanjin } from '@skyscanner/bpk-foundations-web/tokens/base.es6'; - -jest.mock('../../bpk-react-utils', () => { - const original = jest.requireActual('../../bpk-react-utils'); - - return { - ...original, - Portal: 'Portal', - }; -}); - -// eslint-disable-next-line import/first -import BpkTooltipPortal from './BpkTooltipPortal'; - -describe('BpkTooltipPortal', () => { - it('should render correctly', () => { - const { asFragment } = render( - target} - > - My tooltip content - , - ); - - expect(asFragment()).toMatchSnapshot(); - }); - - it('should render correctly with a custom portal className', () => { - const { asFragment } = render( - target} - portalClassName="my-custom-class" - > - My tooltip content - , - ); - - expect(asFragment()).toMatchSnapshot(); - }); - - it('should render correctly with a custom tooltip className', () => { - const { asFragment } = render( - target} - className="my-custom-class" - > - My tooltip content - , - ); - - expect(asFragment()).toMatchSnapshot(); - }); - - it('should render correctly with custom portal style', () => { - const { asFragment } = render( - target} - portalStyle={{ color: colorPanjin }} - > - My tooltip content - , - ); - - expect(asFragment()).toMatchSnapshot(); - }); -}); diff --git a/packages/bpk-component-tooltip/src/BpkTooltipPortal.tsx b/packages/bpk-component-tooltip/src/BpkTooltipPortal.tsx deleted file mode 100644 index 9580e59daa..0000000000 --- a/packages/bpk-component-tooltip/src/BpkTooltipPortal.tsx +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Backpack - Skyscanner's Design System - * - * Copyright 2016 Skyscanner Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { cloneElement, Component } from 'react'; -import type { ReactNode, ReactElement } from 'react'; - -import { createPopper } from '@popperjs/core'; - -import { Portal, cssModules } from '../../bpk-react-utils'; - -import BpkTooltip from './BpkTooltip'; -import { TOOLTIP_TYPES } from './constants'; - -import type { TooltipProps } from './BpkTooltip'; - -import STYLES from './BpkTooltip.module.scss'; - -const getClassName = cssModules(STYLES); - -const hasTouchSupport = () => - !!( - typeof window !== 'undefined' && - ('ontouchstart' in window || navigator.maxTouchPoints > 0) - ); - -export type Props = TooltipProps & { - /** - * Tooltips are invisible to assistive technologies such as screen readers. - * To improve accessibility, `ariaLabel` is required to describe the content of the tooltip to assistive technologies. - * The label will be used on the `target` element, so any existing `aria-label` attached to `target` will be overridden. - */ - ariaLabel: string; - /** - * "target" should be a DOM element with a "ref" attached to it. - */ - target: ReactElement; - children: ReactNode | string; - placement?: 'top' | 'right' | 'bottom' | 'left' | 'auto'; - hideOnTouchDevices?: boolean; - portalStyle?: object; - portalClassName?: string; - renderTarget: null | (() => null | HTMLElement); - /** - * Please refer to the [documentation](https://popper.js.org/docs/v2/modifiers/) for the underlying positioning library "Popper.js". - * You can achieve various behaviours such as allowing the tooltip to overflow the viewport etc. - */ - popperModifiers?: object[]; -}; - -type State = { - isOpen: boolean; -}; - -class BpkTooltipPortal extends Component { - popper?: ReturnType | null; - - targetRef?: Element | null; - - static defaultProps = { - // Disabling as the rule doesn't work when types are defined in a different file - /* eslint-disable react/default-props-match-prop-types */ - className: null, - padded: true, - type: TOOLTIP_TYPES.light, - /* eslint-enable */ - placement: 'bottom', - hideOnTouchDevices: true, - portalStyle: null, - portalClassName: null, - renderTarget: null, - popperModifiers: null, - }; - - constructor(props: Props) { - super(props); - - this.state = { - isOpen: false, - }; - - this.popper = null; - this.targetRef = null; - } - - componentDidMount() { - if (this.targetRef) { - const ref = this.targetRef; - - ref.addEventListener('focusin', this.openTooltip); - ref.addEventListener('focusout', this.closeTooltip); - ref.addEventListener('mouseenter', this.openTooltip); - ref.addEventListener('mouseleave', this.closeTooltip); - } - } - - componentWillUnmount() { - if (this.targetRef) { - const ref = this.targetRef; - - ref.addEventListener('focusin', this.openTooltip); - ref.addEventListener('focusout', this.closeTooltip); - ref.removeEventListener('mouseenter', this.openTooltip); - ref.removeEventListener('mouseleave', this.closeTooltip); - } - } - - onOpen = ( - tooltipElement: HTMLElement, - targetElement?: HTMLElement | null | undefined, - ) => { - // The default modifiers for the popper - // Note that GPU acceleration should be disabled otherwise Popper will use `translate3d` - // which can cause blurriness in Safari and Chrome. - const stdModifiers = [ - { - name: 'computeStyles', - options: { - gpuAcceleration: false, - }, - }, - { - name: 'offset', - options: { - offset: [0, 8], - }, - }, - ]; - - this.popper = createPopper(targetElement as HTMLElement, tooltipElement, { - placement: this.props.placement, - modifiers: this.props.popperModifiers - ? [...this.props.popperModifiers, ...stdModifiers] - : stdModifiers, - }); - - this.popper.update(); - }; - - beforeClose = (done: () => void | null) => { - if (this.popper) { - this.popper.destroy(); - this.popper = null; - } - - done(); - }; - - openTooltip = () => { - this.setState({ - isOpen: true, - }); - }; - - closeTooltip = () => { - this.setState({ - isOpen: false, - }); - }; - - render() { - const { - ariaLabel, - children, - hideOnTouchDevices, - padded, - placement, - popperModifiers, - portalClassName, - portalStyle, - renderTarget, - target, - ...rest - } = this.props; - - const classNames = [getClassName('bpk-tooltip-portal')]; - const renderPortal = !hasTouchSupport() || !hideOnTouchDevices; - - const targetWithAccessibilityProps = cloneElement(target, { - tabIndex: '0', - 'aria-label': ariaLabel, - }); - - if (portalClassName) { - classNames.push(portalClassName); - } - - return ( - <> - {targetWithAccessibilityProps} - {renderPortal && ( - { - this.targetRef = targetRef; - }} - isOpen={this.state.isOpen} - onOpen={this.onOpen} - onClose={this.closeTooltip} - style={portalStyle} - renderTarget={renderTarget} - // TODO: className to be removed - // eslint-disable-next-line @skyscanner/rules/forbid-component-props - className={classNames.join(' ')} - > - - {children} - - - )} - - ); - } -} - -export default BpkTooltipPortal; diff --git a/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltip-test.tsx.snap b/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltip-test.tsx.snap deleted file mode 100644 index 570c61fc85..0000000000 --- a/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltip-test.tsx.snap +++ /dev/null @@ -1,93 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BpkTooltip should render correctly 1`] = ` - - - -`; - -exports[`BpkTooltip should render correctly with "className" attribute 1`] = ` - - - -`; - -exports[`BpkTooltip should render correctly with "padded" attribute equal to false 1`] = ` - - - -`; - -exports[`BpkTooltip should render correctly with type=dark 1`] = ` - - - -`; diff --git a/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltipPortal-test.tsx.snap b/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltipPortal-test.tsx.snap deleted file mode 100644 index ced89df834..0000000000 --- a/packages/bpk-component-tooltip/src/__snapshots__/BpkTooltipPortal-test.tsx.snap +++ /dev/null @@ -1,138 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BpkTooltipPortal should render correctly 1`] = ` - -
- target -
- - - -
-`; - -exports[`BpkTooltipPortal should render correctly with a custom portal className 1`] = ` - -
- target -
- - - -
-`; - -exports[`BpkTooltipPortal should render correctly with a custom tooltip className 1`] = ` - -
- target -
- - - -
-`; - -exports[`BpkTooltipPortal should render correctly with custom portal style 1`] = ` - -
- target -
- - - -
-`; diff --git a/packages/bpk-component-tooltip/src/accessibility-test.tsx b/packages/bpk-component-tooltip/src/accessibility-test.tsx index c540cdcc33..4c6e7b66c0 100644 --- a/packages/bpk-component-tooltip/src/accessibility-test.tsx +++ b/packages/bpk-component-tooltip/src/accessibility-test.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { axe } from 'jest-axe'; import BpkTooltip from './BpkTooltip'; @@ -24,11 +24,14 @@ import BpkTooltip from './BpkTooltip'; describe('BpkTooltip accessibility tests', () => { it('should not have programmatically-detectable accessibility issues', async () => { const { container } = render( - + My tooltip target

} isOpen> My tooltip content
, ); - const results = await axe(container); - expect(results).toHaveNoViolations(); + await waitFor(async () => { + const results = await axe(container); + expect(results).toHaveNoViolations(); + }) + }); }); From 9586a927da90550bd7c2584a0c9e1a28942c5748 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:58:36 +0100 Subject: [PATCH 02/11] Formatting and adding visual testing example --- examples/bpk-component-tooltip/examples.tsx | 19 ++++++++++ examples/bpk-component-tooltip/stories.ts | 9 +++-- .../src/BpkTooltip-test.tsx | 38 ++++++++++++++----- .../bpk-component-tooltip/src/BpkTooltip.tsx | 13 +++---- .../src/accessibility-test.tsx | 10 +++-- 5 files changed, 65 insertions(+), 24 deletions(-) diff --git a/examples/bpk-component-tooltip/examples.tsx b/examples/bpk-component-tooltip/examples.tsx index 9887f9f7ce..acee166852 100644 --- a/examples/bpk-component-tooltip/examples.tsx +++ b/examples/bpk-component-tooltip/examples.tsx @@ -189,6 +189,24 @@ const FocusExample = () => { ); }; +const VisualTestExample = () => { + const target = useRef(null); + + return ( +
+ YUL} + isOpen + > + Montréal-Trudeau International Airport + +
+ ); +}; + + export { DefaultExample, DarkExample, @@ -196,4 +214,5 @@ export { NoPaddingExample, LinkExample, FocusExample, + VisualTestExample }; diff --git a/examples/bpk-component-tooltip/stories.ts b/examples/bpk-component-tooltip/stories.ts index ee4f0ba495..c62e3d78b1 100644 --- a/examples/bpk-component-tooltip/stories.ts +++ b/examples/bpk-component-tooltip/stories.ts @@ -25,6 +25,7 @@ import { NoPaddingExample, LinkExample, FocusExample, + VisualTestExample } from './examples'; export default { @@ -35,9 +36,11 @@ export default { export const Default = DefaultExample; export const Dark = DarkExample; export const OnTheSide = SideExample; - export const WithoutPadding = NoPaddingExample; - export const OnALink = LinkExample; - export const Focus = FocusExample; +export const VisualTest = VisualTestExample; +export const VisualTestWithZoom = VisualTest.bind({}); +VisualTestWithZoom.args = { + zoomEnabled: true +}; diff --git a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx index ed5ce54319..34226024cc 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx @@ -24,40 +24,58 @@ import { TOOLTIP_TYPES } from './constants'; describe('BpkTooltip', () => { it('should render correctly', () => { - const target = (My tooltip target); + const target = My tooltip target; render( - My tooltip content, + + My tooltip content + , ); expect(screen.getByText('My tooltip content')).toBeVisible(); }); it('should render correctly with type=dark', () => { - const target = (My tooltip target); + const target = My tooltip target; const { container } = render( - + My tooltip content , ); - + expect(screen.getByText('My tooltip content')).toBeVisible(); expect(container.querySelector('.bpk-tooltip__inner--dark')).not.toBeNull(); expect(container.querySelector('.bpk-tooltip__arrow--dark')).not.toBeNull(); }); it('should render correctly with "padded" attribute equal to false', () => { - const target = (My tooltip target); + const target = My tooltip target; - const { container } = render( - + const { container } = render( + My tooltip content , ); - + expect(screen.getByText('My tooltip content')).toBeVisible(); expect(container.querySelector('.bpk-tooltip__inner--padded')).toBeNull(); }); - }); diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index 7773138bd8..f9ea5a9fe4 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -23,7 +23,7 @@ The actual component that developers create (i.e. the default export from this p */ import type { ReactNode, ReactElement } from 'react'; -import { cloneElement, useRef , useState } from 'react'; +import { cloneElement, useRef, useState } from 'react'; import { arrow, @@ -44,11 +44,8 @@ import { ARROW_ID, TOOLTIP_TYPES } from './constants'; import type { Placement } from '@floating-ui/react'; - - import STYLES from './BpkTooltip.module.scss'; - const getClassName = cssModules(STYLES); // The stroke width is used to set the border width of the arrow. @@ -72,11 +69,11 @@ export type Props = { const getArrowAlignment = (placement: Placement) => { if (placement.includes('bottom')) { return { bottom: '98%' }; - } if (placement.includes('top')) { + } + if (placement.includes('top')) { return { top: '98%' }; - } - return undefined; - + } + return undefined; }; const BpkTooltip = ({ diff --git a/packages/bpk-component-tooltip/src/accessibility-test.tsx b/packages/bpk-component-tooltip/src/accessibility-test.tsx index 4c6e7b66c0..31f8ce7b64 100644 --- a/packages/bpk-component-tooltip/src/accessibility-test.tsx +++ b/packages/bpk-component-tooltip/src/accessibility-test.tsx @@ -24,14 +24,18 @@ import BpkTooltip from './BpkTooltip'; describe('BpkTooltip accessibility tests', () => { it('should not have programmatically-detectable accessibility issues', async () => { const { container } = render( - My tooltip target

} isOpen> + My tooltip target

} + isOpen + > My tooltip content
, ); await waitFor(async () => { const results = await axe(container); expect(results).toHaveNoViolations(); - }) - + }); }); }); From 4f82791acc27c0a2125b6a711c0b1e18cf10962d Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:08:27 +0100 Subject: [PATCH 03/11] Returned comment to props for documentation --- packages/bpk-component-tooltip/src/BpkTooltip.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index f9ea5a9fe4..d995f3722f 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -52,12 +52,20 @@ const getClassName = cssModules(STYLES); const strokeWidth = 1; export type Props = { + /** + * Tooltips are invisible to assistive technologies such as screen readers. + * To improve accessibility, `ariaLabel` is required to describe the content of the tooltip to assistive technologies. + * The label will be used on the `target` element, so any existing `aria-label` attached to `target` will be overridden. + */ + ariaLabel: string; + /** + * "target" should be a DOM element with a "ref" attached to it. + */ + target: ReactElement; id: string; children: ReactNode | string; type?: (typeof TOOLTIP_TYPES)[keyof typeof TOOLTIP_TYPES]; padded?: boolean; - target: ReactElement; - ariaLabel: string; hideOnTouchDevices?: boolean; placement?: Placement; isOpen?: boolean; From 734a87fbb2a473246f0456c7e05e508b45cc500f Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:11:09 +0100 Subject: [PATCH 04/11] Fix type failures and fix unused prop --- .../bpk-component-tooltip/{stories.ts => stories.tsx} | 10 ++++++---- packages/bpk-component-tooltip/src/BpkTooltip.tsx | 10 ++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) rename examples/bpk-component-tooltip/{stories.ts => stories.tsx} (90%) diff --git a/examples/bpk-component-tooltip/stories.ts b/examples/bpk-component-tooltip/stories.tsx similarity index 90% rename from examples/bpk-component-tooltip/stories.ts rename to examples/bpk-component-tooltip/stories.tsx index c62e3d78b1..38919bf145 100644 --- a/examples/bpk-component-tooltip/stories.ts +++ b/examples/bpk-component-tooltip/stories.tsx @@ -25,7 +25,7 @@ import { NoPaddingExample, LinkExample, FocusExample, - VisualTestExample + VisualTestExample, } from './examples'; export default { @@ -40,7 +40,9 @@ export const WithoutPadding = NoPaddingExample; export const OnALink = LinkExample; export const Focus = FocusExample; export const VisualTest = VisualTestExample; -export const VisualTestWithZoom = VisualTest.bind({}); -VisualTestWithZoom.args = { - zoomEnabled: true +export const VisualTestWithZoom = { + render: VisualTest, + args: { + zoomEnabled: true, + }, }; diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index d995f3722f..d0b906970e 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -52,7 +52,7 @@ const getClassName = cssModules(STYLES); const strokeWidth = 1; export type Props = { - /** + /** * Tooltips are invisible to assistive technologies such as screen readers. * To improve accessibility, `ariaLabel` is required to describe the content of the tooltip to assistive technologies. * The label will be used on the `target` element, so any existing `aria-label` attached to `target` will be overridden. @@ -84,6 +84,12 @@ const getArrowAlignment = (placement: Placement) => { return undefined; }; +const hasTouchSupport = () => + !!( + typeof window !== 'undefined' && + ('ontouchstart' in window || navigator.maxTouchPoints > 0) + ); + const BpkTooltip = ({ ariaLabel, children, @@ -112,7 +118,7 @@ const BpkTooltip = ({ }); const hover = useHover(context, { - mouseOnly: true, + mouseOnly: !hasTouchSupport() || !hideOnTouchDevices, }); const { getFloatingProps, getReferenceProps } = useInteractions([hover]); From 1a61a8f04cdd1b9e3ba4cc2938c3ce370b5f5f29 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:36:15 +0100 Subject: [PATCH 05/11] Fixing a11y voiceover issues --- examples/bpk-component-tooltip/examples.tsx | 33 +++++++------------ .../bpk-component-tooltip/src/BpkTooltip.tsx | 1 - .../src/accessibility-test.tsx | 1 - 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/examples/bpk-component-tooltip/examples.tsx b/examples/bpk-component-tooltip/examples.tsx index acee166852..0850839b9a 100644 --- a/examples/bpk-component-tooltip/examples.tsx +++ b/examples/bpk-component-tooltip/examples.tsx @@ -16,11 +16,7 @@ * limitations under the License. */ -import { useRef, forwardRef } from 'react'; - -import { - colorMonteverde, -} from '@skyscanner/bpk-foundations-web/tokens/base.es6'; +import { useRef } from 'react'; import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text'; import BpkTooltip, { @@ -39,11 +35,6 @@ const Heading = withDefaultProps(BpkText, { tagName: 'h4', }); -const HeadingComponent = forwardRef((props, ref) => ( -
- {props.children} -
-)); const DefaultExample = () => { const target = useRef(null); @@ -53,7 +44,7 @@ const DefaultExample = () => { YUL} + target={YUL} > Montréal-Trudeau International Airport @@ -70,7 +61,7 @@ const DarkExample = () => { ariaLabel="Edinburgh Airport" type={TOOLTIP_TYPES.dark} id="my-tooltip" - target={EDI} + target={EDI} > Edinburgh Airport
@@ -86,7 +77,7 @@ const SideExample = () => { DAR} + target={DAR} placement="right" > Julius Nyerere International Airport, Dar es Salaam @@ -103,13 +94,12 @@ const NoPaddingExample = () => { SIN} + target={SIN} padded={false} >
{ One} + target={One} > Should be focused on first Two} + target={Two} > Should be focused on second Three} + target={Three} > Should be focused on third @@ -181,7 +171,7 @@ const FocusExample = () => { Five} + target={Five} > Should be focused on fifth @@ -197,7 +187,7 @@ const VisualTestExample = () => { YUL} + target={YUL} isOpen > Montréal-Trudeau International Airport @@ -206,7 +196,6 @@ const VisualTestExample = () => { ); }; - export { DefaultExample, DarkExample, @@ -214,5 +203,5 @@ export { NoPaddingExample, LinkExample, FocusExample, - VisualTestExample + VisualTestExample, }; diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index d0b906970e..ff9b002e88 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -163,7 +163,6 @@ const BpkTooltip = ({ tabIndex={-1} role="dialog" className={classNames} - aria-label={ariaLabel} {...rest} > { id="my-popover" ariaLabel="Tooltip" target={

My tooltip target

} - isOpen > My tooltip content
, From dd7b2ecb3850e59f9bc756f4a898023f758a882d Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:02:03 +0100 Subject: [PATCH 06/11] Testing adding timeout for stories to render --- examples/bpk-component-tooltip/stories.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/bpk-component-tooltip/stories.tsx b/examples/bpk-component-tooltip/stories.tsx index 38919bf145..2733e71974 100644 --- a/examples/bpk-component-tooltip/stories.tsx +++ b/examples/bpk-component-tooltip/stories.tsx @@ -39,10 +39,24 @@ export const OnTheSide = SideExample; export const WithoutPadding = NoPaddingExample; export const OnALink = LinkExample; export const Focus = FocusExample; -export const VisualTest = VisualTestExample; + +const VisualExample = VisualTestExample; +export const VisualTest = { + render: VisualExample, + parameters: { + percy: { + waitForTimeout: 10000 + } + } +}; export const VisualTestWithZoom = { - render: VisualTest, + render: VisualExample, args: { zoomEnabled: true, }, + parameters: { + percy: { + waitForTimeout: 10000 + } + } }; From 1ac716efd552a7cce48ef0d17c2f77ebef769a84 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:15:34 +0100 Subject: [PATCH 07/11] Fixing a11y voiceover issues and attaching to references in stories --- examples/bpk-component-tooltip/examples.tsx | 53 ++----------------- examples/bpk-component-tooltip/stories.tsx | 2 - .../bpk-component-tooltip/src/BpkTooltip.tsx | 12 +++-- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/examples/bpk-component-tooltip/examples.tsx b/examples/bpk-component-tooltip/examples.tsx index 0850839b9a..86dc18281f 100644 --- a/examples/bpk-component-tooltip/examples.tsx +++ b/examples/bpk-component-tooltip/examples.tsx @@ -44,7 +44,7 @@ const DefaultExample = () => { YUL} + target={
YUL
} > Montréal-Trudeau International Airport
@@ -61,7 +61,7 @@ const DarkExample = () => { ariaLabel="Edinburgh Airport" type={TOOLTIP_TYPES.dark} id="my-tooltip" - target={EDI} + target={
EDI
} > Edinburgh Airport @@ -77,7 +77,7 @@ const SideExample = () => { DAR} + target={
DAR
} placement="right" > Julius Nyerere International Airport, Dar es Salaam @@ -94,7 +94,7 @@ const NoPaddingExample = () => { SIN} + target={
SIN
} padded={false} >
{ ); }; -const FocusExample = () => { - const targetRef1 = useRef(null); - const targetRef2 = useRef(null); - const targetRef3 = useRef(null); - const targetRef5 = useRef(null); - - return ( -
- One} - > - Should be focused on first - - Two} - > - Should be focused on second - - Three} - > - Should be focused on third - - - Five} - > - Should be focused on fifth - -
- ); -}; const VisualTestExample = () => { const target = useRef(null); @@ -187,7 +145,7 @@ const VisualTestExample = () => { YUL} + target={
YUL
} isOpen > Montréal-Trudeau International Airport @@ -202,6 +160,5 @@ export { SideExample, NoPaddingExample, LinkExample, - FocusExample, VisualTestExample, }; diff --git a/examples/bpk-component-tooltip/stories.tsx b/examples/bpk-component-tooltip/stories.tsx index 2733e71974..b2e5805b2e 100644 --- a/examples/bpk-component-tooltip/stories.tsx +++ b/examples/bpk-component-tooltip/stories.tsx @@ -24,7 +24,6 @@ import { SideExample, NoPaddingExample, LinkExample, - FocusExample, VisualTestExample, } from './examples'; @@ -38,7 +37,6 @@ export const Dark = DarkExample; export const OnTheSide = SideExample; export const WithoutPadding = NoPaddingExample; export const OnALink = LinkExample; -export const Focus = FocusExample; const VisualExample = VisualTestExample; export const VisualTest = { diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index ff9b002e88..3955e66797 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -34,6 +34,7 @@ import { useFloating, useHover, useInteractions, + useRole, } from '@floating-ui/react'; import { surfaceHighlightDay } from '@skyscanner/bpk-foundations-web/tokens/base.es6'; @@ -120,14 +121,19 @@ const BpkTooltip = ({ const hover = useHover(context, { mouseOnly: !hasTouchSupport() || !hideOnTouchDevices, }); + const role = useRole(context, { role: 'tooltip' }); - const { getFloatingProps, getReferenceProps } = useInteractions([hover]); + const { getFloatingProps, getReferenceProps } = useInteractions([ + hover, + role, + ]); const targetWithAccessibilityProps = cloneElement(target, { - ...getReferenceProps(), - ref: refs.setReference, tabIndex: '0', 'aria-label': ariaLabel, + role: 'tooltip', + ref: refs.setReference, + ...getReferenceProps(), }); const classNames = getClassName('bpk-tooltip'); From 7b04057ce41c82314e8c99b3e50d58562a354c89 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:29:55 +0100 Subject: [PATCH 08/11] [ARGG-1157]: Update tooltip to attach to body outside of target elements --- .../src/BpkTooltip-test.tsx | 24 ++++--- .../bpk-component-tooltip/src/BpkTooltip.tsx | 63 ++++++++++--------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx index 34226024cc..30f17e09f1 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx @@ -16,13 +16,15 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { render, screen, act } from '@testing-library/react'; import '@testing-library/jest-dom'; +import userEvent from '@testing-library/user-event'; import BpkTooltip from './BpkTooltip'; import { TOOLTIP_TYPES } from './constants'; describe('BpkTooltip', () => { + it('should render correctly', () => { const target = My tooltip target; @@ -40,10 +42,10 @@ describe('BpkTooltip', () => { expect(screen.getByText('My tooltip content')).toBeVisible(); }); - it('should render correctly with type=dark', () => { + it('should render correctly with type=dark', async () => { const target = My tooltip target; - const { container } = render( + render( { , ); + await act(async () => { + await userEvent.hover(screen.getByText('My tooltip target')); + }); expect(screen.getByText('My tooltip content')).toBeVisible(); - expect(container.querySelector('.bpk-tooltip__inner--dark')).not.toBeNull(); - expect(container.querySelector('.bpk-tooltip__arrow--dark')).not.toBeNull(); + expect(screen.getByText('My tooltip content').className).toContain('bpk-tooltip__inner--dark'); }); - it('should render correctly with "padded" attribute equal to false', () => { + it('should render correctly with "padded" attribute equal to false', async () => { const target = My tooltip target; - const { container } = render( + render( { , ); + await act(async () => { + await userEvent.hover(screen.getByText('My tooltip target')); + }); + expect(screen.getByText('My tooltip content')).toBeVisible(); - expect(container.querySelector('.bpk-tooltip__inner--padded')).toBeNull(); + expect(screen.getByText('My tooltip content').className).not.toContain('bpk-tooltip__inner--padded'); }); }); diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index 3955e66797..d18a805c13 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -29,6 +29,7 @@ import { arrow, flip, FloatingArrow, + FloatingPortal, offset, shift, useFloating, @@ -153,38 +154,40 @@ const BpkTooltip = ({ <> {targetWithAccessibilityProps} {isOpenState && ( -
- +
- - -
+ +
+
+ )} ); From a486065e835da3cf870bcc88a5231ada1e3e91d2 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:46:09 +0100 Subject: [PATCH 09/11] Remove the autoflip to avoid problems and learnings from the popover --- .../bpk-component-tooltip/src/BpkTooltip.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index d18a805c13..08e025a2ce 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -23,11 +23,10 @@ The actual component that developers create (i.e. the default export from this p */ import type { ReactNode, ReactElement } from 'react'; -import { cloneElement, useRef, useState } from 'react'; +import { cloneElement, useRef, useState, useEffect } from 'react'; import { arrow, - flip, FloatingArrow, FloatingPortal, offset, @@ -107,16 +106,17 @@ const BpkTooltip = ({ const [isOpenState, setIsOpenState] = useState(isOpen); const arrowRef = useRef(null); + useEffect(() => { + if (!isOpen) { + setIsOpenState(false); + } + }, [isOpen]); + const { context, floatingStyles, refs } = useFloating({ open: isOpenState, onOpenChange: setIsOpenState, placement, - middleware: [ - offset(8), - flip({ crossAxis: true }), - shift(), - arrow({ element: arrowRef }), - ], + middleware: [offset(8), shift(), arrow({ element: arrowRef })], }); const hover = useHover(context, { From 10bb8e556374aeb377e9a484bba2aedbf2725af5 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:16:16 +0100 Subject: [PATCH 10/11] PR feedback addressing: Remove comment about ref and fix act warnings in tests --- examples/bpk-component-tooltip/examples.tsx | 223 +++++++++--------- .../src/BpkTooltip-test.tsx | 26 +- .../bpk-component-tooltip/src/BpkTooltip.tsx | 2 +- 3 files changed, 124 insertions(+), 127 deletions(-) diff --git a/examples/bpk-component-tooltip/examples.tsx b/examples/bpk-component-tooltip/examples.tsx index 86dc18281f..7aae8cdae6 100644 --- a/examples/bpk-component-tooltip/examples.tsx +++ b/examples/bpk-component-tooltip/examples.tsx @@ -35,124 +35,117 @@ const Heading = withDefaultProps(BpkText, { tagName: 'h4', }); - -const DefaultExample = () => { - const target = useRef(null); - - return ( -
- YUL
} - > - Montréal-Trudeau International Airport -
-
- ); -}; - -const DarkExample = () => { - const target = useRef(null); - - return ( -
- EDI
} - > - Edinburgh Airport -
-
- ); -}; - -const SideExample = () => { - const target = useRef(null); - - return ( -
- DAR
} - placement="right" - > - Julius Nyerere International Airport, Dar es Salaam -
- - ); -}; - -const NoPaddingExample = () => { - const target = useRef(null); - - return ( -
- SIN
} - padded={false} +const DefaultExample = () => ( +
+ + YUL +
+ } + > + Montréal-Trudeau International Airport +
+ +); + +const DarkExample = () => ( +
+ + EDI +
+ } + > + Edinburgh Airport +
+ +); + +const SideExample = () => ( +
+ + DAR +
+ } + placement="right" + > + Julius Nyerere International Airport, Dar es Salaam +
+ +); + +const NoPaddingExample = () => ( +
+ + SIN +
+ } + padded={false} + > +
-
+ +
+); + +const LinkExample = () => ( +
+ - Singapore Changi Airport + Hotels + + } + > + We do hotels too! + +
+); + +const VisualTestExample = () => ( +
+ + YUL
- -
- ); -}; - -const LinkExample = () => { - const target = useRef(null); - - return ( -
- - Hotels - - } - > - We do hotels too! - -
- ); -}; - - -const VisualTestExample = () => { - const target = useRef(null); - - return ( -
- YUL
} - isOpen - > - Montréal-Trudeau International Airport -
- - ); -}; + } + isOpen + > + Montréal-Trudeau International Airport +
+ +); export { DefaultExample, diff --git a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx index 30f17e09f1..644440d91e 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip-test.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import { render, screen, act } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; @@ -24,7 +24,6 @@ import BpkTooltip from './BpkTooltip'; import { TOOLTIP_TYPES } from './constants'; describe('BpkTooltip', () => { - it('should render correctly', () => { const target = My tooltip target; @@ -57,11 +56,14 @@ describe('BpkTooltip', () => {
, ); - await act(async () => { - await userEvent.hover(screen.getByText('My tooltip target')); + await userEvent.hover(screen.getByText('My tooltip target')); + + await waitFor(() => { + expect(screen.getByText('My tooltip content')).toBeVisible(); + expect(screen.getByText('My tooltip content').className).toContain( + 'bpk-tooltip__inner--dark', + ); }); - expect(screen.getByText('My tooltip content')).toBeVisible(); - expect(screen.getByText('My tooltip content').className).toContain('bpk-tooltip__inner--dark'); }); it('should render correctly with "padded" attribute equal to false', async () => { @@ -79,11 +81,13 @@ describe('BpkTooltip', () => { , ); - await act(async () => { - await userEvent.hover(screen.getByText('My tooltip target')); - }); + await userEvent.hover(screen.getByText('My tooltip target')); - expect(screen.getByText('My tooltip content')).toBeVisible(); - expect(screen.getByText('My tooltip content').className).not.toContain('bpk-tooltip__inner--padded'); + await waitFor(() => { + expect(screen.getByText('My tooltip content')).toBeVisible(); + expect(screen.getByText('My tooltip content').className).not.toContain( + 'bpk-tooltip__inner--padded', + ); + }); }); }); diff --git a/packages/bpk-component-tooltip/src/BpkTooltip.tsx b/packages/bpk-component-tooltip/src/BpkTooltip.tsx index 08e025a2ce..bf28ceb076 100644 --- a/packages/bpk-component-tooltip/src/BpkTooltip.tsx +++ b/packages/bpk-component-tooltip/src/BpkTooltip.tsx @@ -60,7 +60,7 @@ export type Props = { */ ariaLabel: string; /** - * "target" should be a DOM element with a "ref" attached to it. + * "target" should be a DOM element. */ target: ReactElement; id: string; From 38ac538154c37ca48babd2f2f37aa84d35991a51 Mon Sep 17 00:00:00 2001 From: Ollie Curtis <8831547+olliecurtis@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:57:01 +0100 Subject: [PATCH 11/11] Remove unused useRef import --- examples/bpk-component-tooltip/examples.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/bpk-component-tooltip/examples.tsx b/examples/bpk-component-tooltip/examples.tsx index 7aae8cdae6..2137f05b2c 100644 --- a/examples/bpk-component-tooltip/examples.tsx +++ b/examples/bpk-component-tooltip/examples.tsx @@ -16,8 +16,6 @@ * limitations under the License. */ -import { useRef } from 'react'; - import BpkText, { TEXT_STYLES } from '../../packages/bpk-component-text'; import BpkTooltip, { TOOLTIP_TYPES,