From 2a46f90d038d5f842abedd2dc27a048b7b987e7b Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Sat, 15 Mar 2025 21:33:37 +0000 Subject: [PATCH 01/10] feat(coachmark): create coachmark web component wrapper --- package.json | 2 +- .../Coachmark/Coachmark.constants.ts | 15 - .../Coachmark/Coachmark.stories.args.ts | 98 +----- .../Coachmark/Coachmark.stories.docs.mdx | 7 +- .../Coachmark/Coachmark.stories.tsx | 129 +------ src/components/Coachmark/Coachmark.style.scss | 60 ---- src/components/Coachmark/Coachmark.tsx | 48 +-- src/components/Coachmark/Coachmark.types.ts | 45 +-- .../Coachmark/Coachmark.unit.test.tsx | 241 +++---------- .../Coachmark/Coachmark.unit.test.tsx.snap | 328 ------------------ src/components/Coachmark/WithHeader.tsx | 46 --- src/components/Coachmark/WithoutHeader.tsx | 41 --- src/components/Coachmark/index.ts | 3 - yarn.lock | 10 +- 14 files changed, 99 insertions(+), 974 deletions(-) delete mode 100644 src/components/Coachmark/Coachmark.constants.ts delete mode 100644 src/components/Coachmark/Coachmark.style.scss delete mode 100644 src/components/Coachmark/Coachmark.unit.test.tsx.snap delete mode 100644 src/components/Coachmark/WithHeader.tsx delete mode 100644 src/components/Coachmark/WithoutHeader.tsx diff --git a/package.json b/package.json index 16a61a3e66..c4f06c737e 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "dependencies": { "@babel/runtime": "^7.0.0", "@momentum-design/animations": "^0.0.4", - "@momentum-design/components": "0.33.2", + "@momentum-design/components": "0.36.0", "@momentum-design/fonts": "0.0.8", "@momentum-design/icons": "0.10.0", "@momentum-design/tokens": "0.5.0", diff --git a/src/components/Coachmark/Coachmark.constants.ts b/src/components/Coachmark/Coachmark.constants.ts deleted file mode 100644 index cbaa8bdc76..0000000000 --- a/src/components/Coachmark/Coachmark.constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -const CLASS_PREFIX = 'md-coachmark'; - -const DEFAULTS = {}; - -const STYLE = { - container: `${CLASS_PREFIX}-container`, - content: `${CLASS_PREFIX}-content`, - controls: `${CLASS_PREFIX}-controls`, - header: `${CLASS_PREFIX}-header`, - image: `${CLASS_PREFIX}-image`, - title: `${CLASS_PREFIX}-title`, - wrapper: `${CLASS_PREFIX}-wrapper`, -}; - -export { CLASS_PREFIX, DEFAULTS, STYLE }; diff --git a/src/components/Coachmark/Coachmark.stories.args.ts b/src/components/Coachmark/Coachmark.stories.args.ts index d952c331bd..b96605f27b 100644 --- a/src/components/Coachmark/Coachmark.stories.args.ts +++ b/src/components/Coachmark/Coachmark.stories.args.ts @@ -1,101 +1,16 @@ -import { commonStyles, extendArgTypes } from '../../storybook/helper.stories.argtypes'; - -import { popoverArgTypes } from '../Popover/Popover.stories.args'; +import { commonStyles } from '../../storybook/helper.stories.argtypes'; const coachmarkArgTypes = { - actions: { - description: 'Action buttons to use with this component.', - control: { type: 'none' }, - table: { - type: { - summary: 'ReactElement | Array>', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, + // NOTE: Below is an example. See [Storybook argTypes documentation]{@link https://storybook.js.org/docs/react/api/argtypes}. children: { - description: 'Content of this component.', - control: { type: 'text' }, - table: { - type: { - summary: 'ReactNode', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - onDismiss: { - description: 'Event handler that is triggered when the user dismisses this component.', - control: { type: 'none' }, - table: { - type: { - summary: '() => void', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - icon: { - description: 'Icon to display to the left of the title.', - control: { type: 'text' }, - table: { - type: { - summary: 'string', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - image: { - description: 'Image to display on this component.', - control: { type: 'none' }, - table: { - type: { - summary: 'HTMLImageElement', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - isVisible: { - description: 'Whether or not this component should be visible.', - control: { type: 'boolean' }, - table: { - type: { - summary: 'boolean', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - target: { - description: 'The element this component should target when rendering its location.', - control: { type: 'none' }, - table: { - type: { - summary: 'ReactElement', - }, - defaultValue: { - summary: 'undefined', - }, - }, - }, - title: { - description: 'Title to display on this component.', - control: { type: 'text' }, + description: 'Provides the child nodes for this element.', // NOTE: Description of this prop. + control: { type: 'text' }, // NOTE: Control type for this prop. table: { type: { - summary: 'string', + summary: 'ReactNode', // NOTE: Explicit type of this prop. }, defaultValue: { - summary: 'undefined', + summary: 'undefined', // NOTE: Default value for this prop. }, }, }, @@ -105,6 +20,5 @@ export { coachmarkArgTypes }; export default { ...commonStyles, - ...extendArgTypes('Popover', popoverArgTypes, ['interactive', 'trigger']), ...coachmarkArgTypes, }; diff --git a/src/components/Coachmark/Coachmark.stories.docs.mdx b/src/components/Coachmark/Coachmark.stories.docs.mdx index 32fd14ffaa..33577637e1 100644 --- a/src/components/Coachmark/Coachmark.stories.docs.mdx +++ b/src/components/Coachmark/Coachmark.stories.docs.mdx @@ -1,6 +1 @@ -The `` component. - -This component should be used with experience-driven flows, such as the "First Time Experience" -[FTE] for a feature. The visibility of each `` must be controlled by the `isVisible` -prop, and can be dismissed by changing this prop to `false` or if the user clicks the `dismiss` -button. The `dismiss` button triggers the `onDismiss()` event handler. +The `` component. \ No newline at end of file diff --git a/src/components/Coachmark/Coachmark.stories.tsx b/src/components/Coachmark/Coachmark.stories.tsx index c84696e8db..2f14434d47 100644 --- a/src/components/Coachmark/Coachmark.stories.tsx +++ b/src/components/Coachmark/Coachmark.stories.tsx @@ -1,15 +1,12 @@ -import React, { useState } from 'react'; -import { Story } from '@storybook/react'; - import { DocumentationPage } from '../../storybook/helper.stories.docs'; import StyleDocs from '../../storybook/docs.stories.style.mdx'; +import { Story } from '@storybook/react'; import Coachmark, { CoachmarkProps } from './'; -import ButtonSimple from '../ButtonSimple'; -import ButtonHyperlink from '../ButtonHyperlink'; -import ButtonPill from '../ButtonPill'; import argTypes from './Coachmark.stories.args'; import Documentation from './Coachmark.stories.docs.mdx'; +import ButtonPill from '../ButtonPill'; +import React from 'react'; export default { title: 'Momentum UI/Coachmark', @@ -22,115 +19,21 @@ export default { }, }; -const Template: Story = (args: CoachmarkProps) => { - return ( - Target Element} - {...args} - /> - ); -}; - -const Example = Template.bind({}); +const Example: Story = (args: CoachmarkProps) => ( +
+ document.getElementById(args.id)?.showPopover()}>open + document.getElementById(args.id)?.hidePopover()}>close +
Trigger
+ +
+); Example.argTypes = { ...argTypes }; - Example.args = { - children: 'Example', -}; - -const TemplateCommon: Story = () => { - const [index, setIndex] = useState(); - - const next = () => { - setIndex(index + 1); - }; - - const start = () => { - setIndex(1); - }; - - const reset = () => { - setIndex(0); - }; - - interface LocalProps { - targetIndex: number; - content?: boolean; - icon?: boolean; - image?: boolean; - title?: boolean; - trigger: string; - } - - const CommonCoachmark = ({ content, icon, image, targetIndex, title, trigger }: LocalProps) => ( - - Button - , - - Button - , - Hyperlink, - ]} - icon={icon ? 'placeholder' : undefined} - image={ - image && ( - example - ) - } - isVisible={index === targetIndex} - title={title && 'Example Title'} - triggerComponent={
{trigger}
} - onDismiss={next} - > - {content && ( -
Example Coachmark {targetIndex}. Close this Coachmark to continue the experience.
- )} -
- ); - - return ( -
- - - - - - - - - - Start Experience - Reset Experience -
- ); + id: 'coachmark', + triggerID: 'trigger', + 'close-button-aria-label': 'Close', + children: 'This is a coachmark', }; -const Common = TemplateCommon.bind({}); - -Common.argTypes = Object.entries({ ...argTypes }).reduce((types, entry) => { - const [key, value] = entry; - const mutable = { ...types }; - - mutable[key] = { ...value }; - - mutable[key].control = { type: 'none' }; - - return mutable; -}, {}); - -Common.args = {}; - -export { Example, Common }; +export { Example }; diff --git a/src/components/Coachmark/Coachmark.style.scss b/src/components/Coachmark/Coachmark.style.scss deleted file mode 100644 index 9cb0b514cb..0000000000 --- a/src/components/Coachmark/Coachmark.style.scss +++ /dev/null @@ -1,60 +0,0 @@ -.md-coachmark-wrapper { - display: block; - width: 19.125rem; - - > .md-coachmark-container { - margin: 0.25rem; - - &[data-header='true'] { - > :not(:first-child) { - margin-top: 0.75rem; - } - - > .md-coachmark-header { - align-items: center; - display: flex; - width: 100%; - - > .md-icon-wrapper { - height: 2rem; - width: 2rem; - } - - > .md-icon-wrapper::part(icon) { - width: 1.125rem; - height: 1.125rem; - } - - > .md-coachmark-title { - flex-grow: 1; - } - } - - img { - border-radius: 0.5rem; - min-height: 4rem; - width: 17.625rem; - } - } - - &[data-header='false'] { - > div:first-child { - flex-grow: 1; - overflow-wrap: break-word; - - > .md-coachmark-content { - margin-bottom: 0.5rem; - overflow-wrap: break-word; - } - } - - display: flex; - flex-grow: 1; - - > .md-coachmark-controls { - justify-self: flex-end; - flex-shrink: 0; - } - } - } -} diff --git a/src/components/Coachmark/Coachmark.tsx b/src/components/Coachmark/Coachmark.tsx index 5d201124ed..562a8ea0e5 100644 --- a/src/components/Coachmark/Coachmark.tsx +++ b/src/components/Coachmark/Coachmark.tsx @@ -1,55 +1,13 @@ -import React, { FC, useCallback, useEffect, useState } from 'react'; -import classnames from 'classnames'; +import React, { FC } from 'react'; -import Popover, { PopoverInstance } from '../Popover'; - -import WithHeader from './WithHeader'; -import WithoutHeader from './WithoutHeader'; -import { STYLE } from './Coachmark.constants'; import { Props } from './Coachmark.types'; -import './Coachmark.style.scss'; +import { Coachmark as MdcCoachmark } from '@momentum-design/components/dist/react'; /** * The Coachmark component. */ const Coachmark: FC = (props: Props) => { - const { actions, className, children, icon, image, onDismiss, isVisible, title, ...otherProps } = - props; - - const [instance, setInstance] = useState(undefined); - - const handleDismiss = useCallback(() => { - if (instance?.state?.isVisible) { - instance.hide(); - onDismiss?.(); - } - }, [instance]); - - useEffect(() => { - if (instance?.show && isVisible) { - instance.show(); - } - - if (instance?.state?.isVisible && !isVisible) { - instance.hide(); - } - }, [instance, isVisible]); - - return ( - - {title || image || icon ? ( - - ) : ( - - )} - - ); + return ; }; export default Coachmark; diff --git a/src/components/Coachmark/Coachmark.types.ts b/src/components/Coachmark/Coachmark.types.ts index a5b0815149..9477852261 100644 --- a/src/components/Coachmark/Coachmark.types.ts +++ b/src/components/Coachmark/Coachmark.types.ts @@ -1,47 +1,34 @@ -import type { ReactElement, ReactNode } from 'react'; +import { CSSProperties, ReactNode } from 'react'; +import { AriaLabelRequired } from 'src/utils/a11y'; -import { ButtonSimpleProps } from '../ButtonSimple'; -import { PopoverProps } from '../Popover'; -import { InferredIconName } from '../Icon/Icon.types'; - -export interface CoachmarkWithoutHeaderProps { +export type Props = { /** - * Actions associated with this Coachmark. + * Child components of this Coachmark. */ - actions?: ReactElement | Array>; + children: ReactNode; /** - * Content to display on this Coachmark. + * Custom class for overriding this component's CSS. */ - children: ReactNode; + className?: string; /** - * Event handler to handle dismissing the Coachmark. + * Custom style for overriding this component's CSS. */ - onDismiss?: () => void; -} + style?: CSSProperties; -export interface CoachmarkWithHeaderProps extends CoachmarkWithoutHeaderProps { /** - * Icon to display to the left of the title. + * ID of the coachmark. */ - icon?: InferredIconName; + id: string; /** - * Image associated with this Coachmark. + * ID of the element that triggers the coachmark. */ - image?: ReactElement; + triggerID: string; /** - * Title of this Coachmark. + * Arial label for the close button. */ - title?: string; -} - -export type Props = PopoverProps & - CoachmarkWithHeaderProps & { - /** - * Whether or not this Coachmark is to be visible. - */ - isVisible?: boolean; - }; + 'close-button-aria-label'?: string; +} & AriaLabelRequired; diff --git a/src/components/Coachmark/Coachmark.unit.test.tsx b/src/components/Coachmark/Coachmark.unit.test.tsx index b02377a716..ec4de236b4 100644 --- a/src/components/Coachmark/Coachmark.unit.test.tsx +++ b/src/components/Coachmark/Coachmark.unit.test.tsx @@ -1,242 +1,103 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import '@testing-library/jest-dom'; - -import ButtonPill from '../ButtonPill'; +import { mount } from 'enzyme'; import Coachmark from './'; -jest.mock('uuid', () => { - return { - v4: () => '1', - }; -}); - describe('', () => { - const iconName = 'placeholder'; - const children = 'Children'; - describe('snapshot', () => { - it('should match snapshot without header', async () => { + it('should match snapshot', () => { expect.assertions(1); - const { container } = render( - Button A, - Button B, - ]} - isVisible - triggerComponent={
} - > - {children} - - ); - - await screen.findByText(children); + const container = mount(); expect(container).toMatchSnapshot(); }); - it('should match snapshot with header', async () => { + it('should match snapshot with className', () => { expect.assertions(1); - const { container } = render( - Button A, - Button B, - ]} - icon={iconName} - image={ - example - } - isVisible - title="Title" - triggerComponent={
} - > - {children} - - ); - - await screen.findByText(children); + const className = 'example-class'; + + const container = mount(); expect(container).toMatchSnapshot(); }); - }); - - describe('attributes', () => { - it('should allow actions', async () => { - expect.assertions(2); - const user = userEvent.setup(); - - const onPressMock = jest.fn(); - - render( - - Button A - , - ]} - isVisible - triggerComponent={
} - > - {children} - - ); - - await screen.findByText(children); - const button = screen.getByRole('button', { name: 'Button A' }); + it('should match snapshot with id', () => { + expect.assertions(1); - expect(button).toBeVisible(); + const id = 'example-id'; - await user.click(button); + const container = mount(); - expect(onPressMock).toHaveBeenCalledTimes(1); + expect(container).toMatchSnapshot(); }); - it('should allow icon', async () => { + it('should match snapshot with style', () => { expect.assertions(1); - render( - }> - {children} - - ); - - await screen.findByText(children); + const style = { color: 'pink' }; - const icon = screen.getByTestId(iconName); + const container = mount(); - expect(icon).toBeVisible(); + expect(container).toMatchSnapshot(); }); - it('should allow image', async () => { - expect.assertions(2); - - const alt = 'example'; - const src = - 'https://www.firstbenefits.org/wp-content/uploads/2017/10/placeholder-1024x1024.png'; - - render( - } triggerComponent={
}> - {children} - - ); + /* ...additional snapshot tests... */ + }); - await screen.findByText(children); + describe('attributes', () => { + it('should have its wrapper class', () => { + expect.assertions(1); - const image = screen.getByAltText(alt); + const element = mount() + .find(Coachmark) + .getDOMNode(); - expect(image).toBeVisible(); - expect(image.src).toBe(src); + expect(element.classList.contains(CONSTANTS.STYLE.wrapper)).toBe(true); }); - it('should allow dismiss via keypress', async () => { - expect.assertions(3); - const user = userEvent.setup(); - - const mockDismiss = jest.fn(); - - render( - }> - {children} - - ); - - await screen.findByText(children); - - const dismissButton = screen.getByRole('button', { name: 'dismiss' }); - - expect(dismissButton).toBeVisible(); + it('should have provided class when className is provided', () => { + expect.assertions(1); - await user.tab(); - await user.keyboard('{enter}'); + const className = 'example-class'; - await waitFor(() => { - expect(screen.queryByText(children)).not.toBeInTheDocument(); - }); + const element = mount() + .find(Coachmark) + .getDOMNode(); - expect(mockDismiss).toHaveBeenCalledTimes(1); + expect(element.classList.contains(className)).toBe(true); }); - it('should allow dismiss via click', async () => { - expect.assertions(3); - const user = userEvent.setup(); - - const mockDismiss = jest.fn(); - - render( - }> - {children} - - ); - - await screen.findByText(children); - - const dismissButton = screen.getByRole('button', { name: 'dismiss' }); - - expect(dismissButton).toBeVisible(); + it('should have provided id when id is provided', () => { + expect.assertions(1); - await user.click(dismissButton); + const id = 'example-id'; - await waitFor(() => { - expect(screen.queryByText(children)).not.toBeInTheDocument(); - }); + const element = mount() + .find(Coachmark) + .getDOMNode(); - expect(mockDismiss).toHaveBeenCalledTimes(1); + expect(element.id).toBe(id); }); - it('should respond to isVisible prop', async () => { - expect.assertions(3); - - const { rerender } = render( - }> - {children} - - ); - - await screen.findByText(children); - - expect(screen.getByText(children)).toBeVisible(); - - rerender(}>{children}); - - await waitFor(() => { - expect(screen.queryByText(children)).not.toBeInTheDocument(); - }); + it('should have provided style when style is provided', () => { + expect.assertions(1); - rerender( - }> - {children} - - ); + const style = { color: 'pink' }; + const styleString = 'color: pink;'; - await screen.findByText(children); + const element = mount() + .find(Coachmark) + .getDOMNode(); - expect(screen.getByText(children)).toBeVisible(); + expect(element.getAttribute('style')).toBe(styleString); }); - it('should allow title', async () => { - expect.assertions(1); - - const title = 'Title'; - - render( - }> - {children} - - ); - - await screen.findByText(children); + /* ...additional attribute tests... */ + }); - expect(screen.getByText(title)).toBeVisible(); - }); + describe('actions', () => { + /* ...action tests... */ }); }); diff --git a/src/components/Coachmark/Coachmark.unit.test.tsx.snap b/src/components/Coachmark/Coachmark.unit.test.tsx.snap deleted file mode 100644 index a11c88af70..0000000000 --- a/src/components/Coachmark/Coachmark.unit.test.tsx.snap +++ /dev/null @@ -1,328 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` snapshot should match snapshot with header 1`] = ` -
-
-
-
- -`; - -exports[` snapshot should match snapshot without header 1`] = ` -
-
-
-
- -`; diff --git a/src/components/Coachmark/WithHeader.tsx b/src/components/Coachmark/WithHeader.tsx deleted file mode 100644 index 2f410e26d5..0000000000 --- a/src/components/Coachmark/WithHeader.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { FC } from 'react'; - -import ButtonControl, { BUTTON_CONTROL_CONSTANTS } from '../ButtonControl'; -import ButtonGroup from '../ButtonGroup'; -import Icon, { ICON_CONSTANTS } from '../Icon'; -import Text, { TEXT_CONSTANTS } from '../Text'; - -import { STYLE } from './Coachmark.constants'; -import { CoachmarkWithHeaderProps } from './Coachmark.types'; -import './Coachmark.style.scss'; - -/** - * The Coachmark content component when displayed with a header. - */ -const CoachmarkWithHeader: FC = (props: CoachmarkWithHeaderProps) => { - const { actions, children, icon, image, onDismiss, title } = props; - - return ( -
-
- {icon && ( - - )} - - {title} - - - - -
- {image &&
{image}
} - {children &&
{children}
} - {actions && ( - - {actions} - - )} -
- ); -}; - -export default CoachmarkWithHeader; diff --git a/src/components/Coachmark/WithoutHeader.tsx b/src/components/Coachmark/WithoutHeader.tsx deleted file mode 100644 index 65e9af4aa7..0000000000 --- a/src/components/Coachmark/WithoutHeader.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { FC } from 'react'; - -import ButtonControl, { BUTTON_CONTROL_CONSTANTS } from '../ButtonControl'; -import ButtonGroup from '../ButtonGroup'; - -import { STYLE } from './Coachmark.constants'; -import { CoachmarkWithoutHeaderProps } from './Coachmark.types'; -import './Coachmark.style.scss'; - -/** - * The Coachmark container component when displayed without a header. - */ -const CoarchmarkWithoutHeader: FC = ( - props: CoachmarkWithoutHeaderProps -) => { - const { actions, children, onDismiss } = props; - - return ( - <> -
-
- {children &&
{children}
} - {actions && ( - - {actions} - - )} -
- - - -
- - ); -}; - -export default CoarchmarkWithoutHeader; diff --git a/src/components/Coachmark/index.ts b/src/components/Coachmark/index.ts index 7040cc9ead..9854b12924 100644 --- a/src/components/Coachmark/index.ts +++ b/src/components/Coachmark/index.ts @@ -1,9 +1,6 @@ import { default as Coachmark } from './Coachmark'; -import * as CONSTANTS from './Coachmark.constants'; import { Props } from './Coachmark.types'; -export { CONSTANTS as COACHMARK_CONSTANTS }; - export type CoachmarkProps = Props; export default Coachmark; diff --git a/yarn.lock b/yarn.lock index 5f2c55db7e..6179e1f73f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2902,9 +2902,9 @@ __metadata: languageName: node linkType: hard -"@momentum-design/components@npm:0.33.2": - version: 0.33.2 - resolution: "@momentum-design/components@npm:0.33.2" +"@momentum-design/components@npm:0.36.0": + version: 0.36.0 + resolution: "@momentum-design/components@npm:0.36.0" dependencies: "@floating-ui/dom": ^1.6.12 "@lit/context": ^1.1.2 @@ -2915,7 +2915,7 @@ __metadata: "@tanstack/lit-virtual": ^3.11.3 lit: ^3.2.0 uuid: ^11.0.5 - checksum: f1e4ccff59c3d95b22326da21fe3b767fc9fed54ced89dea817ac4eaec59a3f750e83ff9f0e6993740b900ee6cbcf9af72af4f1fa0b2963edc67aef413049157 + checksum: f14354fd53c9784a7c9eb846c71244e937c92ea8a54b98efdff8dab72df073feeaa2cb00567a51f4d653f590e8df5ebe53ca0ea68bd6bcf86fc9735700abcb68 languageName: node linkType: hard @@ -2984,7 +2984,7 @@ __metadata: "@commitlint/config-conventional": ^12.0.1 "@hot-loader/react-dom": ~16.8.0 "@momentum-design/animations": ^0.0.4 - "@momentum-design/components": 0.33.2 + "@momentum-design/components": 0.36.0 "@momentum-design/fonts": 0.0.8 "@momentum-design/icons": 0.10.0 "@momentum-design/tokens": 0.5.0 From f19155eac582ce31b9d21db19d87cf0724b19309 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 10:46:14 +0000 Subject: [PATCH 02/10] feat(coachmark): create component --- package.json | 2 +- .../Coachmark/Coachmark.stories.tsx | 14 +++- src/components/Coachmark/Coachmark.tsx | 2 +- src/components/Coachmark/Coachmark.types.ts | 11 +++ .../Coachmark/Coachmark.unit.test.tsx | 77 ++++++------------- .../Coachmark/Coachmark.unit.test.tsx.snap | 66 ++++++++++++++++ yarn.lock | 10 +-- 7 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 src/components/Coachmark/Coachmark.unit.test.tsx.snap diff --git a/package.json b/package.json index c4f06c737e..558b2f125d 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "dependencies": { "@babel/runtime": "^7.0.0", "@momentum-design/animations": "^0.0.4", - "@momentum-design/components": "0.36.0", + "@momentum-design/components": "0.36.1", "@momentum-design/fonts": "0.0.8", "@momentum-design/icons": "0.10.0", "@momentum-design/tokens": "0.5.0", diff --git a/src/components/Coachmark/Coachmark.stories.tsx b/src/components/Coachmark/Coachmark.stories.tsx index 2f14434d47..3283165667 100644 --- a/src/components/Coachmark/Coachmark.stories.tsx +++ b/src/components/Coachmark/Coachmark.stories.tsx @@ -7,6 +7,7 @@ import argTypes from './Coachmark.stories.args'; import Documentation from './Coachmark.stories.docs.mdx'; import ButtonPill from '../ButtonPill'; import React from 'react'; +import Text from '../Text'; export default { title: 'Momentum UI/Coachmark', @@ -20,7 +21,7 @@ export default { }; const Example: Story = (args: CoachmarkProps) => ( -
+
document.getElementById(args.id)?.showPopover()}>open document.getElementById(args.id)?.hidePopover()}>close
Trigger
@@ -33,7 +34,16 @@ Example.args = { id: 'coachmark', triggerID: 'trigger', 'close-button-aria-label': 'Close', - children: 'This is a coachmark', + children: ( +
+ + Coachmark title + + + Some content in here, blah blah. + +
+ ), }; export { Example }; diff --git a/src/components/Coachmark/Coachmark.tsx b/src/components/Coachmark/Coachmark.tsx index 562a8ea0e5..5a9d72b4ab 100644 --- a/src/components/Coachmark/Coachmark.tsx +++ b/src/components/Coachmark/Coachmark.tsx @@ -7,7 +7,7 @@ import { Coachmark as MdcCoachmark } from '@momentum-design/components/dist/reac * The Coachmark component. */ const Coachmark: FC = (props: Props) => { - return ; + return ; }; export default Coachmark; diff --git a/src/components/Coachmark/Coachmark.types.ts b/src/components/Coachmark/Coachmark.types.ts index 9477852261..52b8d0e3a6 100644 --- a/src/components/Coachmark/Coachmark.types.ts +++ b/src/components/Coachmark/Coachmark.types.ts @@ -1,3 +1,4 @@ +import { PopoverPlacement } from '@momentum-design/components'; import { CSSProperties, ReactNode } from 'react'; import { AriaLabelRequired } from 'src/utils/a11y'; @@ -27,8 +28,18 @@ export type Props = { */ triggerID: string; + /** + * Whether the close button should be shown. + */ + 'close-button'?: boolean; + /** * Arial label for the close button. */ 'close-button-aria-label'?: string; + + /** + * Placement of the coachmark. + */ + placement?: PopoverPlacement; } & AriaLabelRequired; diff --git a/src/components/Coachmark/Coachmark.unit.test.tsx b/src/components/Coachmark/Coachmark.unit.test.tsx index ec4de236b4..1c3b6b1d9f 100644 --- a/src/components/Coachmark/Coachmark.unit.test.tsx +++ b/src/components/Coachmark/Coachmark.unit.test.tsx @@ -1,14 +1,31 @@ import React from 'react'; import { mount } from 'enzyme'; -import Coachmark from './'; +import Coachmark, { CoachmarkProps } from './'; + +const defaultProps = { + children: 'This is a coachmark', + 'close-button-aria-label': 'Close', + id: 'coachmark', + triggerID: 'trigger', + 'aria-label': 'Coachmark', +}; describe('', () => { + const setup = (props: CoachmarkProps) => { + return mount( +
+
trigger
+ +
+ ); + }; + describe('snapshot', () => { it('should match snapshot', () => { expect.assertions(1); - const container = mount(); + const container = setup(defaultProps); expect(container).toMatchSnapshot(); }); @@ -18,86 +35,36 @@ describe('', () => { const className = 'example-class'; - const container = mount(); - - expect(container).toMatchSnapshot(); - }); - - it('should match snapshot with id', () => { - expect.assertions(1); - - const id = 'example-id'; - - const container = mount(); + const container = setup({ ...defaultProps, className }); expect(container).toMatchSnapshot(); }); - - it('should match snapshot with style', () => { - expect.assertions(1); - - const style = { color: 'pink' }; - - const container = mount(); - - expect(container).toMatchSnapshot(); - }); - - /* ...additional snapshot tests... */ }); describe('attributes', () => { - it('should have its wrapper class', () => { - expect.assertions(1); - - const element = mount() - .find(Coachmark) - .getDOMNode(); - - expect(element.classList.contains(CONSTANTS.STYLE.wrapper)).toBe(true); - }); - it('should have provided class when className is provided', () => { expect.assertions(1); const className = 'example-class'; - const element = mount() + const element = setup({ ...defaultProps, className }) .find(Coachmark) .getDOMNode(); expect(element.classList.contains(className)).toBe(true); }); - it('should have provided id when id is provided', () => { - expect.assertions(1); - - const id = 'example-id'; - - const element = mount() - .find(Coachmark) - .getDOMNode(); - - expect(element.id).toBe(id); - }); - it('should have provided style when style is provided', () => { expect.assertions(1); const style = { color: 'pink' }; const styleString = 'color: pink;'; - const element = mount() + const element = setup({ ...defaultProps, style }) .find(Coachmark) .getDOMNode(); expect(element.getAttribute('style')).toBe(styleString); }); - - /* ...additional attribute tests... */ - }); - - describe('actions', () => { - /* ...action tests... */ }); }); diff --git a/src/components/Coachmark/Coachmark.unit.test.tsx.snap b/src/components/Coachmark/Coachmark.unit.test.tsx.snap new file mode 100644 index 0000000000..eb7500afca --- /dev/null +++ b/src/components/Coachmark/Coachmark.unit.test.tsx.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` snapshot should match snapshot 1`] = ` +
+
+ trigger +
+ + + + This is a coachmark + + + +
+`; + +exports[` snapshot should match snapshot with className 1`] = ` +
+
+ trigger +
+ + + + This is a coachmark + + + +
+`; diff --git a/yarn.lock b/yarn.lock index 6179e1f73f..a65cde8d6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2902,9 +2902,9 @@ __metadata: languageName: node linkType: hard -"@momentum-design/components@npm:0.36.0": - version: 0.36.0 - resolution: "@momentum-design/components@npm:0.36.0" +"@momentum-design/components@npm:0.36.1": + version: 0.36.1 + resolution: "@momentum-design/components@npm:0.36.1" dependencies: "@floating-ui/dom": ^1.6.12 "@lit/context": ^1.1.2 @@ -2915,7 +2915,7 @@ __metadata: "@tanstack/lit-virtual": ^3.11.3 lit: ^3.2.0 uuid: ^11.0.5 - checksum: f14354fd53c9784a7c9eb846c71244e937c92ea8a54b98efdff8dab72df073feeaa2cb00567a51f4d653f590e8df5ebe53ca0ea68bd6bcf86fc9735700abcb68 + checksum: 2ee3217e92e6a0b125c208537add14c9d1147753b9accfed6574c6c547e3e40637d05f161f34637b81d9b5316acee0f00be8e8665b73de9ed50d2a03fdcefec6 languageName: node linkType: hard @@ -2984,7 +2984,7 @@ __metadata: "@commitlint/config-conventional": ^12.0.1 "@hot-loader/react-dom": ~16.8.0 "@momentum-design/animations": ^0.0.4 - "@momentum-design/components": 0.36.0 + "@momentum-design/components": 0.36.1 "@momentum-design/fonts": 0.0.8 "@momentum-design/icons": 0.10.0 "@momentum-design/tokens": 0.5.0 From a423d4b6191ad0ff48d190846bb23d72ee523f95 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 14:59:59 +0000 Subject: [PATCH 03/10] feat: bump --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 558b2f125d..de5c789e90 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "dependencies": { "@babel/runtime": "^7.0.0", "@momentum-design/animations": "^0.0.4", - "@momentum-design/components": "0.36.1", + "@momentum-design/components": "0.36.4", "@momentum-design/fonts": "0.0.8", "@momentum-design/icons": "0.10.0", "@momentum-design/tokens": "0.5.0", diff --git a/yarn.lock b/yarn.lock index a65cde8d6e..1f22058c44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2902,9 +2902,9 @@ __metadata: languageName: node linkType: hard -"@momentum-design/components@npm:0.36.1": - version: 0.36.1 - resolution: "@momentum-design/components@npm:0.36.1" +"@momentum-design/components@npm:0.36.4": + version: 0.36.4 + resolution: "@momentum-design/components@npm:0.36.4" dependencies: "@floating-ui/dom": ^1.6.12 "@lit/context": ^1.1.2 @@ -2915,7 +2915,7 @@ __metadata: "@tanstack/lit-virtual": ^3.11.3 lit: ^3.2.0 uuid: ^11.0.5 - checksum: 2ee3217e92e6a0b125c208537add14c9d1147753b9accfed6574c6c547e3e40637d05f161f34637b81d9b5316acee0f00be8e8665b73de9ed50d2a03fdcefec6 + checksum: 428539af2d2a558d7353a8687d0c08260611beedb1afcb555a5a79b9d097426695cce7305f414676dc1bfbba3c354a0a9e154eae99487be880f8fd49b67c6783 languageName: node linkType: hard @@ -2984,7 +2984,7 @@ __metadata: "@commitlint/config-conventional": ^12.0.1 "@hot-loader/react-dom": ~16.8.0 "@momentum-design/animations": ^0.0.4 - "@momentum-design/components": 0.36.1 + "@momentum-design/components": 0.36.4 "@momentum-design/fonts": 0.0.8 "@momentum-design/icons": 0.10.0 "@momentum-design/tokens": 0.5.0 From 5d4c6f22310220b7eae01de75745b1620784847f Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 15:06:24 +0000 Subject: [PATCH 04/10] fix: test --- src/components/Coachmark/Coachmark.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Coachmark/Coachmark.types.ts b/src/components/Coachmark/Coachmark.types.ts index 52b8d0e3a6..246b7c7c58 100644 --- a/src/components/Coachmark/Coachmark.types.ts +++ b/src/components/Coachmark/Coachmark.types.ts @@ -1,6 +1,6 @@ import { PopoverPlacement } from '@momentum-design/components'; import { CSSProperties, ReactNode } from 'react'; -import { AriaLabelRequired } from 'src/utils/a11y'; +import { AriaLabelRequired } from '../../utils/a11y'; export type Props = { /** From 18ef1df60c0281bf58e9372934d6377b6cd69e85 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 16:46:41 +0000 Subject: [PATCH 05/10] fix: shadow --- src/components/Coachmark/Coachmark.stories.args.ts | 6 +----- src/components/ThemeProvider/ThemeProvider.constants.ts | 1 + src/components/ThemeProvider/ThemeProvider.tsx | 3 ++- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/Coachmark/Coachmark.stories.args.ts b/src/components/Coachmark/Coachmark.stories.args.ts index b96605f27b..6bd783e386 100644 --- a/src/components/Coachmark/Coachmark.stories.args.ts +++ b/src/components/Coachmark/Coachmark.stories.args.ts @@ -1,17 +1,13 @@ import { commonStyles } from '../../storybook/helper.stories.argtypes'; const coachmarkArgTypes = { - // NOTE: Below is an example. See [Storybook argTypes documentation]{@link https://storybook.js.org/docs/react/api/argtypes}. children: { - description: 'Provides the child nodes for this element.', // NOTE: Description of this prop. + description: 'Content of the coachmark.', // NOTE: Description of this prop. control: { type: 'text' }, // NOTE: Control type for this prop. table: { type: { summary: 'ReactNode', // NOTE: Explicit type of this prop. }, - defaultValue: { - summary: 'undefined', // NOTE: Default value for this prop. - }, }, }, }; diff --git a/src/components/ThemeProvider/ThemeProvider.constants.ts b/src/components/ThemeProvider/ThemeProvider.constants.ts index 7a29fc6f5d..bd34adc174 100644 --- a/src/components/ThemeProvider/ThemeProvider.constants.ts +++ b/src/components/ThemeProvider/ThemeProvider.constants.ts @@ -7,6 +7,7 @@ const STYLE = { globals: `${CLASS_PREFIX}-globals`, // momentum-design typography class to be set typography: 'mds-typography', + elevation: 'mds-elevation', }; // Some themes are disabled until tokens are properly imported. diff --git a/src/components/ThemeProvider/ThemeProvider.tsx b/src/components/ThemeProvider/ThemeProvider.tsx index 8ec275a15e..73523eb334 100644 --- a/src/components/ThemeProvider/ThemeProvider.tsx +++ b/src/components/ThemeProvider/ThemeProvider.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; import '@momentum-ui/design-tokens/dist/index.css'; // TODO: we should not use core colors, only theme colors - to be removed: import '@momentum-design/tokens/dist/css/core/complete.css'; +import '@momentum-design/tokens/dist/css/elevation/complete.css'; // import Momentum fonts and typography defaults: import '@momentum-design/fonts/dist/css/fonts.css'; @@ -49,7 +50,7 @@ const ThemeProvider: FC = ({ children, id, style, theme }: Props) => { return (
From 5ebf812198a4c6c773df60d73f4e5d1a6d7fad2e Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 17:04:20 +0000 Subject: [PATCH 06/10] fix: snapshot tests --- src/legacy/Lightbox/tests/index.spec.js.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/legacy/Lightbox/tests/index.spec.js.snap b/src/legacy/Lightbox/tests/index.spec.js.snap index 4822323f67..346f520dfe 100644 --- a/src/legacy/Lightbox/tests/index.spec.js.snap +++ b/src/legacy/Lightbox/tests/index.spec.js.snap @@ -70,7 +70,7 @@ exports[`tests for should match SnapShot 1`] = ` tabindex="-1" >
should match SnapShot 1`] = ` theme="darkWebex" > From f9e7b4145814f358d434de819713d4cfda107b38 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Mon, 17 Mar 2025 17:29:20 +0000 Subject: [PATCH 07/10] fix: snapshot tests --- .../ThemeProvider/ThemeProvider.unit.test.tsx.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ThemeProvider/ThemeProvider.unit.test.tsx.snap b/src/components/ThemeProvider/ThemeProvider.unit.test.tsx.snap index d82d083476..90ab4a9f7e 100644 --- a/src/components/ThemeProvider/ThemeProvider.unit.test.tsx.snap +++ b/src/components/ThemeProvider/ThemeProvider.unit.test.tsx.snap @@ -5,11 +5,11 @@ exports[` snapshot should match snapshot 1`] = ` theme="darkBronzeWebex" > @@ -35,11 +35,11 @@ exports[` snapshot should match snapshot with style 1`] = ` } > From ccdecf4426606efd7aed46b8ed8a84e3e5625a65 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Tue, 18 Mar 2025 01:07:29 +0000 Subject: [PATCH 08/10] fix: forgot arg types --- .../Coachmark/Coachmark.stories.args.ts | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/components/Coachmark/Coachmark.stories.args.ts b/src/components/Coachmark/Coachmark.stories.args.ts index 6bd783e386..fa88801fb8 100644 --- a/src/components/Coachmark/Coachmark.stories.args.ts +++ b/src/components/Coachmark/Coachmark.stories.args.ts @@ -2,11 +2,53 @@ import { commonStyles } from '../../storybook/helper.stories.argtypes'; const coachmarkArgTypes = { children: { - description: 'Content of the coachmark.', // NOTE: Description of this prop. - control: { type: 'text' }, // NOTE: Control type for this prop. + description: 'Content of the coachmark.', + control: { type: 'text' }, table: { type: { - summary: 'ReactNode', // NOTE: Explicit type of this prop. + summary: 'ReactNode', + }, + }, + }, + triggerID: { + description: 'ID of the element that triggers the coachmark.', + control: { type: 'text' }, + table: { + type: { + summary: 'string', + }, + }, + }, + 'close-button': { + description: 'Whether the close button should be shown.', + control: { type: 'boolean' }, + table: { + type: { + summary: 'boolean', + }, + defaultValue: { + summary: true, + }, + }, + }, + 'close-button-aria-label': { + description: 'Arial label for the close button.', + control: { type: 'text' }, + table: { + type: { + summary: 'string', + }, + }, + }, + placement: { + description: 'Placement of the coachmark.', + control: { type: 'text' }, + table: { + type: { + summary: 'string', + }, + defaultValue: { + summary: 'bottom', }, }, }, From 8f332432e4dc6f90c8e137b0d105985980edf9d9 Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Tue, 18 Mar 2025 15:02:45 +0000 Subject: [PATCH 09/10] feat: bump --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index de5c789e90..9bc5c4a55b 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "dependencies": { "@babel/runtime": "^7.0.0", "@momentum-design/animations": "^0.0.4", - "@momentum-design/components": "0.36.4", + "@momentum-design/components": "0.39.1", "@momentum-design/fonts": "0.0.8", "@momentum-design/icons": "0.10.0", "@momentum-design/tokens": "0.5.0", diff --git a/yarn.lock b/yarn.lock index 1f22058c44..73c1c8a5d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2902,9 +2902,9 @@ __metadata: languageName: node linkType: hard -"@momentum-design/components@npm:0.36.4": - version: 0.36.4 - resolution: "@momentum-design/components@npm:0.36.4" +"@momentum-design/components@npm:0.39.1": + version: 0.39.1 + resolution: "@momentum-design/components@npm:0.39.1" dependencies: "@floating-ui/dom": ^1.6.12 "@lit/context": ^1.1.2 @@ -2915,7 +2915,7 @@ __metadata: "@tanstack/lit-virtual": ^3.11.3 lit: ^3.2.0 uuid: ^11.0.5 - checksum: 428539af2d2a558d7353a8687d0c08260611beedb1afcb555a5a79b9d097426695cce7305f414676dc1bfbba3c354a0a9e154eae99487be880f8fd49b67c6783 + checksum: 697a1839c6488f631b0e889824ebf09dcdf97ee8ef58b80789c0bf5a529ff205aa6cf700a5990a76f7cfa0825ef02ad7b679b896d33b86dafe2f2b045751f5b7 languageName: node linkType: hard @@ -2984,7 +2984,7 @@ __metadata: "@commitlint/config-conventional": ^12.0.1 "@hot-loader/react-dom": ~16.8.0 "@momentum-design/animations": ^0.0.4 - "@momentum-design/components": 0.36.4 + "@momentum-design/components": 0.39.1 "@momentum-design/fonts": 0.0.8 "@momentum-design/icons": 0.10.0 "@momentum-design/tokens": 0.5.0 From fd88d4f1ab115b22035db464c4a56db455d32bad Mon Sep 17 00:00:00 2001 From: Gabriel Lee Date: Tue, 18 Mar 2025 16:41:57 +0000 Subject: [PATCH 10/10] feat: add focus back to trigger prop --- src/components/Coachmark/Coachmark.stories.args.ts | 12 ++++++++++++ src/components/Coachmark/Coachmark.stories.tsx | 2 +- src/components/Coachmark/Coachmark.types.ts | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/Coachmark/Coachmark.stories.args.ts b/src/components/Coachmark/Coachmark.stories.args.ts index fa88801fb8..06141743c1 100644 --- a/src/components/Coachmark/Coachmark.stories.args.ts +++ b/src/components/Coachmark/Coachmark.stories.args.ts @@ -52,6 +52,18 @@ const coachmarkArgTypes = { }, }, }, + 'focus-back-to-trigger': { + description: 'Whether the focus should shift to trigger on hide.', + control: { type: 'boolean' }, + table: { + type: { + summary: 'boolean', + }, + defaultValue: { + summary: false, + }, + }, + }, }; export { coachmarkArgTypes }; diff --git a/src/components/Coachmark/Coachmark.stories.tsx b/src/components/Coachmark/Coachmark.stories.tsx index 3283165667..be4090db34 100644 --- a/src/components/Coachmark/Coachmark.stories.tsx +++ b/src/components/Coachmark/Coachmark.stories.tsx @@ -24,7 +24,7 @@ const Example: Story = (args: CoachmarkProps) => (
document.getElementById(args.id)?.showPopover()}>open document.getElementById(args.id)?.hidePopover()}>close -
Trigger
+ Trigger (dummy)
); diff --git a/src/components/Coachmark/Coachmark.types.ts b/src/components/Coachmark/Coachmark.types.ts index 246b7c7c58..f6db2cf135 100644 --- a/src/components/Coachmark/Coachmark.types.ts +++ b/src/components/Coachmark/Coachmark.types.ts @@ -42,4 +42,9 @@ export type Props = { * Placement of the coachmark. */ placement?: PopoverPlacement; + + /** + * Focus back to trigger on hide. + */ + 'focus-back-to-trigger'?: boolean; } & AriaLabelRequired;