diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca430ab0d1..51aa7c1f42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,28 +182,6 @@ jobs: name: playwright-avt-report path: .playwright - vrt-runner: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: '20.x' - cache: yarn - - name: Install - run: yarn - - name: Install browsers - run: yarn playwright install --with-deps - - name: Build project - run: yarn build - - name: Run VRT - working-directory: packages/core - env: - PERCY_TOKEN: web_d04495b0b413d61c2ea6b9118d1748b43f4fdd58d17ebe453ef8e0016b5766e4 - run: yarn percy storybook storybook-static - avt: if: ${{ always() }} runs-on: ubuntu-latest diff --git a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js index d9cf59f68f..485911ba7a 100644 --- a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js +++ b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.test.js @@ -22,12 +22,18 @@ const children = `hello, world (${uuidv4()})`; const dataTestId = uuidv4(); const className = `class-${uuidv4()}`; -const childrenContent = ( +const childrenContent = [ -); + />, + , +]; const renderCoachmarkWithOverlayElements = ( { ...rest } = {}, @@ -165,4 +171,22 @@ describe(componentName, () => { expect(screen.getByRole('img')).toBeInTheDocument(); }); + + it('calls onNext', async () => { + const user = userEvent.setup(); + const onNext = jest.fn(); + renderCoachmarkWithOverlayElements({ + 'data-testid': dataTestId, + onNext, + }); + const beaconOrButton = screen.getByRole('button', { + name: 'Show information', + }); + await act(() => user.click(beaconOrButton)); + const nextButton = screen.getByRole('button', { + name: 'Next', + }); + await act(() => user.click(nextButton)); + await expect(onNext).toHaveBeenCalled(); + }); }); diff --git a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx index 2db4b39812..710db555aa 100644 --- a/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx +++ b/packages/ibm-products/src/components/CoachmarkOverlayElements/CoachmarkOverlayElements.tsx @@ -77,6 +77,18 @@ export interface CoachmarkOverlayElementsProps { * The label for the Close button. */ closeButtonLabel?: string; + /** + * Callback called when clicking on the Next button. + */ + onNext?: () => void; + /** + * Callback called when clicking on the Previous button. + */ + onBack?: () => void; + /** + * Current step of the coachmarks. + */ + currentStep?: number; } // NOTE: the component SCSS is not imported here: it is rolled up separately. @@ -96,6 +108,9 @@ const defaults = { nextButtonText: 'Next', previousButtonLabel: 'Back', closeButtonLabel: 'Got it', + onNext: undefined, + onBack: undefined, + currentStep: 0, }; /** * Composable container to allow for the displaying of CoachmarkOverlayElement @@ -112,9 +127,12 @@ export let CoachmarkOverlayElements = React.forwardRef< isVisible = defaults.isVisible, media, renderMedia, + currentStep = defaults.currentStep, nextButtonText = defaults.nextButtonText, previousButtonLabel = defaults.previousButtonLabel, closeButtonLabel = defaults.closeButtonLabel, + onNext = defaults.onNext, + onBack = defaults.onBack, // Collect any other property values passed in. ...rest }, @@ -123,7 +141,7 @@ export let CoachmarkOverlayElements = React.forwardRef< const buttonFocusRef = useRef | undefined>(undefined); const scrollRef = useRef(undefined); const [scrollPosition, setScrollPosition] = useState(0); - const [currentProgStep, _setCurrentProgStep] = useState(0); + const [currentProgStep, _setCurrentProgStep] = useState(currentStep); const coachmark = useCoachmark(); const hasMedia = media || renderMedia; @@ -145,6 +163,16 @@ export let CoachmarkOverlayElements = React.forwardRef< [currentProgStep, renderMedia] ); + useEffect(() => { + // When current step is set by props + // scroll to the appropriate view on the carrousel + const targetStep = clamp(currentStep, progStepFloor, progStepCeil); + + scrollRef?.current?.scrollToView?.(targetStep); + // Avoid circular call to this hook + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentStep]); + useEffect(() => { // On mount, one of the two primary buttons ("next" or "close") // will be rendered and must have focus. (a11y) @@ -222,7 +250,6 @@ export let CoachmarkOverlayElements = React.forwardRef< ) : ( <> } onScroll={(scrollPercent) => { setScrollPosition(scrollPercent); @@ -248,6 +275,7 @@ export let CoachmarkOverlayElements = React.forwardRef< ); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); + onBack?.(); }} > {previousButtonLabel} @@ -268,6 +296,7 @@ export let CoachmarkOverlayElements = React.forwardRef< ); scrollRef?.current?.scrollToView?.(targetStep); setCurrentProgStep(targetStep); + onNext?.(); }} > {nextButtonText} @@ -320,6 +349,10 @@ CoachmarkOverlayElements.propTypes = { * The label for the Close button. */ closeButtonLabel: PropTypes.string, + /** + * Current step of the coachmarks + */ + currentStep: PropTypes.number, /** * The visibility of CoachmarkOverlayElements is * managed in the parent component. @@ -344,6 +377,14 @@ CoachmarkOverlayElements.propTypes = { * The label for the Next button. */ nextButtonText: PropTypes.string, + /** + * Optional callback called when clicking on the Previous button. + */ + onBack: PropTypes.func, + /** + * Optional callback called when clicking on the Next button. + */ + onNext: PropTypes.func, /** * The label for the Previous button. */