From 3b5b0235f20dff7995c1aca47d8da5862daae52c Mon Sep 17 00:00:00 2001 From: Andrew Steele Date: Tue, 3 Dec 2024 17:24:18 -0500 Subject: [PATCH 1/4] New va-alert-sign-in component --- .../stories/va-alert-sign-in.stories.jsx | 118 +++++++ packages/web-components/src/components.d.ts | 99 ++++++ .../va-alert-sign-in/VariantNames.ts | 9 + .../test/va-alert-sign-in.e2e.ts | 218 ++++++++++++ .../va-alert-sign-in/va-alert-sign-in.scss | 37 +++ .../va-alert-sign-in/va-alert-sign-in.tsx | 310 ++++++++++++++++++ 6 files changed, 791 insertions(+) create mode 100644 packages/storybook/stories/va-alert-sign-in.stories.jsx create mode 100644 packages/web-components/src/components/va-alert-sign-in/VariantNames.ts create mode 100644 packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts create mode 100644 packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.scss create mode 100644 packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx diff --git a/packages/storybook/stories/va-alert-sign-in.stories.jsx b/packages/storybook/stories/va-alert-sign-in.stories.jsx new file mode 100644 index 000000000..7bfc6b644 --- /dev/null +++ b/packages/storybook/stories/va-alert-sign-in.stories.jsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { getWebComponentDocs, propStructure, StoryDocs } from './wc-helpers'; +import { VariantNames } from '../../web-components/src/components/va-alert-sign-in/VariantNames'; + +const alertSignInDocs = getWebComponentDocs('va-alert-sign-in'); + +export default { + title: 'Components/Alert - Sign-in', + id: 'uswds/va-alert-sign-in', + parameters: { + componentSubtitle: 'va-alert-sign-in web component', + docs: { + page: () => , + }, + }, +}; + +const defaultArgs = { + 'variant': VariantNames.signInRequired, + 'disable-analytics': false, + 'visible': true, + 'time-limit': null, + 'no-sign-in-link': null, +}; + +const SignInButton = () => ; +const IdMeSignInButton = () => ( + +); +const IdMeVerifyButton = () => ( + +); +const LoginGovSignInButton = () => ( + +); +const LoginGovVerifyButton = () => ( + +); + +const SlotVariants = { + [VariantNames.signInRequired]: { + slotNames: ['SignInButton'], + buttons: [SignInButton], + }, + [VariantNames.signInOptional]: { + slotNames: ['SignInButton'], + buttons: [SignInButton], + }, + [VariantNames.signInEither]: { + slotNames: ['LoginGovSignInButton', 'IdMeSignInButton'], + buttons: [LoginGovSignInButton, IdMeSignInButton], + }, + [VariantNames.verifyIdMe]: { + slotNames: ['IdMeVerifyButton'], + buttons: [IdMeVerifyButton], + }, + [VariantNames.verifyLoginGov]: { + slotNames: ['LoginGovVerifyButton'], + buttons: [LoginGovVerifyButton], + }, +}; + +const Template = ({ + variant, + 'disable-analytics': disableAnalytics, + visible, + 'time-limit': timeLimit, + 'no-sign-in-link': noSignInLink, +}) => { + return ( + + {SlotVariants[variant].slotNames.map((name, i) => { + const ButtonToRender = SlotVariants[variant].buttons[i]; + return ( + + + + ); + })} + + ); +}; + +export const Default = Template.bind(null); +Default.args = { + ...defaultArgs, +}; +Default.argTypes = propStructure(alertSignInDocs); + +export const OptionalSignIn = Template.bind(null); +OptionalSignIn.args = { + ...defaultArgs, + variant: VariantNames.signInOptional, +}; + +export const VerifyWithIdMe = Template.bind(null); +VerifyWithIdMe.args = { + ...defaultArgs, + variant: VariantNames.verifyIdMe, +}; + +export const VerifyWithLoginGov = Template.bind(null); +VerifyWithLoginGov.args = { + ...defaultArgs, + variant: VariantNames.verifyLoginGov, +}; + +export const SignInWithAnotherAccount = Template.bind(null); +SignInWithAnotherAccount.args = { + ...defaultArgs, + variant: VariantNames.signInEither, +}; diff --git a/packages/web-components/src/components.d.ts b/packages/web-components/src/components.d.ts index 97a5a80a6..0eadb2ce7 100644 --- a/packages/web-components/src/components.d.ts +++ b/packages/web-components/src/components.d.ts @@ -5,7 +5,9 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { VariantNames } from "./components/va-alert-sign-in/VariantNames"; import { Breadcrumb } from "./components/va-breadcrumbs/va-breadcrumbs"; +export { VariantNames } from "./components/va-alert-sign-in/VariantNames"; export { Breadcrumb } from "./components/va-breadcrumbs/va-breadcrumbs"; export namespace Components { /** @@ -134,6 +136,33 @@ export namespace Components { */ "trigger": string; } + /** + * @componentName Alert - Sign In + * @maturityCategory caution + * @maturityLevel candidate + */ + interface VaAlertSignIn { + /** + * If `true`, doesn't fire the CustomEvent which can be used for analytics tracking. + */ + "disableAnalytics"?: boolean; + /** + * For the 'optional' variant, the link to the form to complete without signing in + */ + "noSignInLink"?: string; + /** + * For the 'optional' variant, how long the respondent has to submit their form + */ + "timeLimit"?: string; + /** + * Determines the text content and border/background color. + */ + "variant": VariantNames; + /** + * If `true`, the alert will be visible. + */ + "visible"?: boolean; + } /** * @componentName Back to top * @maturityCategory use @@ -1816,6 +1845,10 @@ export interface VaAlertExpandableCustomEvent extends CustomEvent { detail: T; target: HTMLVaAlertExpandableElement; } +export interface VaAlertSignInCustomEvent extends CustomEvent { + detail: T; + target: HTMLVaAlertSignInElement; +} export interface VaBannerCustomEvent extends CustomEvent { detail: T; target: HTMLVaBannerElement; @@ -2064,6 +2097,29 @@ declare global { prototype: HTMLVaAlertExpandableElement; new (): HTMLVaAlertExpandableElement; }; + interface HTMLVaAlertSignInElementEventMap { + "va-component-did-load": any; + "component-library-analytics": any; + } + /** + * @componentName Alert - Sign In + * @maturityCategory caution + * @maturityLevel candidate + */ + interface HTMLVaAlertSignInElement extends Components.VaAlertSignIn, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLVaAlertSignInElement, ev: VaAlertSignInCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLVaAlertSignInElement, ev: VaAlertSignInCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLVaAlertSignInElement: { + prototype: HTMLVaAlertSignInElement; + new (): HTMLVaAlertSignInElement; + }; /** * @componentName Back to top * @maturityCategory use @@ -3015,6 +3071,7 @@ declare global { "va-additional-info": HTMLVaAdditionalInfoElement; "va-alert": HTMLVaAlertElement; "va-alert-expandable": HTMLVaAlertExpandableElement; + "va-alert-sign-in": HTMLVaAlertSignInElement; "va-back-to-top": HTMLVaBackToTopElement; "va-banner": HTMLVaBannerElement; "va-breadcrumbs": HTMLVaBreadcrumbsElement; @@ -3219,6 +3276,41 @@ declare namespace LocalJSX { */ "trigger": string; } + /** + * @componentName Alert - Sign In + * @maturityCategory caution + * @maturityLevel candidate + */ + interface VaAlertSignIn { + /** + * If `true`, doesn't fire the CustomEvent which can be used for analytics tracking. + */ + "disableAnalytics"?: boolean; + /** + * For the 'optional' variant, the link to the form to complete without signing in + */ + "noSignInLink"?: string; + /** + * The event used to track usage of the component. This is emitted when an anchor link is clicked and disableAnalytics is not true. + */ + "onComponent-library-analytics"?: (event: VaAlertSignInCustomEvent) => void; + /** + * Fires when the component has successfully finished rendering for the first time. + */ + "onVa-component-did-load"?: (event: VaAlertSignInCustomEvent) => void; + /** + * For the 'optional' variant, how long the respondent has to submit their form + */ + "timeLimit"?: string; + /** + * Determines the text content and border/background color. + */ + "variant"?: VariantNames; + /** + * If `true`, the alert will be visible. + */ + "visible"?: boolean; + } /** * @componentName Back to top * @maturityCategory use @@ -5111,6 +5203,7 @@ declare namespace LocalJSX { "va-additional-info": VaAdditionalInfo; "va-alert": VaAlert; "va-alert-expandable": VaAlertExpandable; + "va-alert-sign-in": VaAlertSignIn; "va-back-to-top": VaBackToTop; "va-banner": VaBanner; "va-breadcrumbs": VaBreadcrumbs; @@ -5193,6 +5286,12 @@ declare module "@stencil/core" { * @maturityLevel candidate */ "va-alert-expandable": LocalJSX.VaAlertExpandable & JSXBase.HTMLAttributes; + /** + * @componentName Alert - Sign In + * @maturityCategory caution + * @maturityLevel candidate + */ + "va-alert-sign-in": LocalJSX.VaAlertSignIn & JSXBase.HTMLAttributes; /** * @componentName Back to top * @maturityCategory use diff --git a/packages/web-components/src/components/va-alert-sign-in/VariantNames.ts b/packages/web-components/src/components/va-alert-sign-in/VariantNames.ts new file mode 100644 index 000000000..7cff6587e --- /dev/null +++ b/packages/web-components/src/components/va-alert-sign-in/VariantNames.ts @@ -0,0 +1,9 @@ +export enum VariantNames { + /* eslint-disable i18next/no-literal-string */ + signInRequired = 'signInRequired', + signInOptional = 'signInOptional', + verifyIdMe = 'verifyIdMe', + verifyLoginGov = 'verifyLoginGov', + signInEither = 'signInEither', + /* eslint-enable i18next/no-literal-string */ +} diff --git a/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts b/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts new file mode 100644 index 000000000..d98b1bd7c --- /dev/null +++ b/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts @@ -0,0 +1,218 @@ +import { newE2EPage } from '@stencil/core/testing'; +import { axeCheck } from '../../../testing/test-helpers'; +import { VariantNames } from '../VariantNames'; + +describe('va-alert-sign-in', () => { + it('renders', async () => { + const page = await newE2EPage(); + + await page.setContent(''); + const element = await page.find('va-alert-sign-in'); + + expect(element).toEqualHtml(` + + + + + + `); + }); + + it('renders an empty div with a "polite" aria-live tag when not visible', async () => { + const page = await newE2EPage(); + + await page.setContent( + '', + ); + const element = await page.find('va-alert-sign-in'); + + expect(element).toEqualHtml(` + + +
+
+
+ `); + }); + + it('passes an axe check', async () => { + const page = await newE2EPage(); + await page.setContent(``); + + await axeCheck(page); + }); + + it('fires an analytics event when a link is clicked', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const analyticsSpy = await page.spyOnEvent('component-library-analytics'); + + const link = await page.find('va-alert-sign-in >>> va-link'); + await link.click(); + + expect(analyticsSpy).toHaveReceivedEventDetail({ + action: 'linkClick', + componentName: 'va-alert-sign-in', + details: { + clickLabel: 'Learn about creating an account', + variant: VariantNames.signInRequired, + }, + }); + }); + + it('does not fire an analytics event when disableAnalytics is passed', async () => { + const page = await newE2EPage(); + await page.setContent( + '', + ); + + const analyticsSpy = await page.spyOnEvent('component-library-analytics'); + + const link = await page.find('va-alert-sign-in >>> va-link'); + await link.click(); + + expect(analyticsSpy).toHaveReceivedEventTimes(0); + }); + + it('should set variant to required if null', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.signInRequired}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to required if it is an empty string', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.signInRequired}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to required if value not in pre-defined list', async () => { + const page = await newE2EPage(); + await page.setContent( + '', + ); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.signInRequired}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to optional when specified', async () => { + const page = await newE2EPage(); + await page.setContent( + ``, + ); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.signInOptional}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to "either" when specified', async () => { + const page = await newE2EPage(); + await page.setContent( + ``, + ); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.signInEither}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to "Verify with ID.me" when specified', async () => { + const page = await newE2EPage(); + await page.setContent( + ``, + ); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.verifyIdMe}`, + ), + ).toBeTruthy(); + }); + + it('should set variant to "Verify with Login.gov" when specified', async () => { + const page = await newE2EPage(); + await page.setContent( + ``, + ); + + const element = await page.find('va-alert-sign-in >>> .usa-alert'); + + expect( + element.classList.contains( + `va-alert-sign-in--${VariantNames.verifyLoginGov}`, + ), + ).toBeTruthy(); + }); +}); diff --git a/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.scss b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.scss new file mode 100644 index 000000000..5eb755083 --- /dev/null +++ b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.scss @@ -0,0 +1,37 @@ +@forward 'settings'; +@use 'usa-alert/src/styles/usa-alert'; +@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/uswds-typography.css"; + +:host { + display: block; + position: relative; +} + +// Overriding USWDS' default icons +.usa-alert--info .usa-alert__body::before, .usa-alert--warning .usa-alert__body::before { + mask: none; + background: transparent; +} + +.va-alert-sign-in__lock-icon { + position: absolute; + top: 12px; + left: 24px; +} + +@media screen and (max-width: 64em) { + .va-alert-sign-in__lock-icon { + left: 12px; + } +} + +:host .headline { + font-size: 21.28px; /* 1.33rem */ + margin-top: 0; + margin-bottom: 8px; /* 0.5rem */ + font-family: var(--font-serif); +} + +:host ul { + padding-inline-start: 1.2em; +} \ No newline at end of file diff --git a/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx new file mode 100644 index 000000000..c68423416 --- /dev/null +++ b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx @@ -0,0 +1,310 @@ +import { + Component, + Element, + Event, + EventEmitter, + Host, + Prop, + h, +} from '@stencil/core'; +import classnames from 'classnames'; +import { VariantNames } from './VariantNames'; + +/** + * @componentName Alert - Sign In + * @maturityCategory caution + * @maturityLevel candidate + */ +@Component({ + tag: 'va-alert-sign-in', + styleUrl: 'va-alert-sign-in.scss', + shadow: true, +}) +export class VaAlertSignIn { + @Element() el!: any; + + /** + * Determines the text content and border/background color. + */ + @Prop() variant: VariantNames = VariantNames.signInRequired; + + /** + * If `true`, doesn't fire the CustomEvent which can be used for analytics tracking. + */ + @Prop() disableAnalytics?: boolean = false; + + /** + * If `true`, the alert will be visible. + */ + @Prop() visible?: boolean = true; + + /** + * For the 'optional' variant, how long the respondent has to submit their form + */ + @Prop() timeLimit?: string = '15 minutes'; + + /** + * For the 'optional' variant, the link to the form to complete without signing in + */ + @Prop() noSignInLink?: string; + + /** + * Fires when the component has successfully finished rendering for the first + * time. + */ + @Event({ + eventName: 'va-component-did-load', + composed: true, + bubbles: true, + }) + vaComponentDidLoad: EventEmitter; + + /** + * The event used to track usage of the component. This is emitted when an + * anchor link is clicked and disableAnalytics is not true. + */ + @Event({ + eventName: 'component-library-analytics', + composed: true, + bubbles: true, + }) + componentLibraryAnalytics: EventEmitter; + + private handleAlertBodyClick(e: MouseEvent): void { + if (!this.disableAnalytics) { + const target = e.target as HTMLElement; + + // If it's a link being clicked, dispatch an analytics event + if (target?.tagName === 'VA-LINK') { + const innerText = target.shadowRoot.querySelector('a').innerText; + const detail = { + componentName: 'va-alert-sign-in', + action: 'linkClick', + details: { + clickLabel: innerText, + variant: this.variant, + }, + }; + this.componentLibraryAnalytics.emit(detail); + } + } + } + + componentDidLoad() { + this.vaComponentDidLoad.emit(); + } + + render() { + const { visible } = this; + let { variant } = this; + + // Check that the provided variant (or null) matches a known variant name + if (!Object.values(VariantNames).includes(variant)) + variant = VariantNames.signInRequired; + + if (!visible) return
; + + const classes = classnames('usa-alert', `va-alert-sign-in--${variant}`, { + 'usa-alert--info': + variant === VariantNames.signInRequired || + variant === VariantNames.signInOptional, + 'usa-alert--warning': + variant !== VariantNames.signInRequired && + variant !== VariantNames.signInOptional, + }); + + /* eslint-disable i18next/no-literal-string */ + const RequiredVariant = () => ( + + ); + + const OptionalVariant = () => ( + + ); + + const IdMeVariant = () => ( + + ); + + const LoginGovVariant = () => ( + + ); + + const SignInEitherVariant = () => ( + + ); + + /* eslint-enable i18next/no-literal-string */ + + const BodyVariants = { + [VariantNames.signInEither]: SignInEitherVariant, + [VariantNames.signInOptional]: OptionalVariant, + [VariantNames.signInRequired]: RequiredVariant, + [VariantNames.verifyIdMe]: IdMeVariant, + [VariantNames.verifyLoginGov]: LoginGovVariant, + }; + const SignInBody = BodyVariants[variant]; + + return ( + +
+
+
+ + +
+
+
+
+ ); + } +} From 3418fc3bc66d4a6f8ee51f68c7a1dc4e39c49fad Mon Sep 17 00:00:00 2001 From: Andrew Steele Date: Thu, 5 Dec 2024 10:45:57 -0500 Subject: [PATCH 2/4] General improvements, including heading level customization --- .../stories/va-alert-sign-in.stories.jsx | 17 ++- packages/web-components/src/components.d.ts | 42 ++----- .../test/va-alert-sign-in.e2e.ts | 32 ++++- .../va-alert-sign-in/va-alert-sign-in.tsx | 112 +++++++++--------- 4 files changed, 109 insertions(+), 94 deletions(-) diff --git a/packages/storybook/stories/va-alert-sign-in.stories.jsx b/packages/storybook/stories/va-alert-sign-in.stories.jsx index 7bfc6b644..bebde0d1b 100644 --- a/packages/storybook/stories/va-alert-sign-in.stories.jsx +++ b/packages/storybook/stories/va-alert-sign-in.stories.jsx @@ -6,7 +6,7 @@ const alertSignInDocs = getWebComponentDocs('va-alert-sign-in'); export default { title: 'Components/Alert - Sign-in', - id: 'uswds/va-alert-sign-in', + id: 'components/va-alert-sign-in', parameters: { componentSubtitle: 'va-alert-sign-in web component', docs: { @@ -17,10 +17,11 @@ export default { const defaultArgs = { 'variant': VariantNames.signInRequired, - 'disable-analytics': false, + 'disable-analytics': true, 'visible': true, 'time-limit': null, 'no-sign-in-link': null, + 'heading-level': null, }; const SignInButton = () => ; @@ -66,6 +67,7 @@ const Template = ({ visible, 'time-limit': timeLimit, 'no-sign-in-link': noSignInLink, + 'heading-level': headingLevel, }) => { return ( {SlotVariants[variant].slotNames.map((name, i) => { const ButtonToRender = SlotVariants[variant].buttons[i]; @@ -93,10 +96,18 @@ Default.args = { }; Default.argTypes = propStructure(alertSignInDocs); +export const WithCustomHeadingLevel = Template.bind(null); +WithCustomHeadingLevel.args = { + ...defaultArgs, + 'heading-level': 3, +}; + export const OptionalSignIn = Template.bind(null); OptionalSignIn.args = { ...defaultArgs, - variant: VariantNames.signInOptional, + 'variant': VariantNames.signInOptional, + 'no-sign-in-link': 'https://example.com/', + 'time-limit': '20 minutes', }; export const VerifyWithIdMe = Template.bind(null); diff --git a/packages/web-components/src/components.d.ts b/packages/web-components/src/components.d.ts index 0eadb2ce7..6cb00228b 100644 --- a/packages/web-components/src/components.d.ts +++ b/packages/web-components/src/components.d.ts @@ -5,9 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { VariantNames } from "./components/va-alert-sign-in/VariantNames"; import { Breadcrumb } from "./components/va-breadcrumbs/va-breadcrumbs"; -export { VariantNames } from "./components/va-alert-sign-in/VariantNames"; export { Breadcrumb } from "./components/va-breadcrumbs/va-breadcrumbs"; export namespace Components { /** @@ -146,6 +144,10 @@ export namespace Components { * If `true`, doesn't fire the CustomEvent which can be used for analytics tracking. */ "disableAnalytics"?: boolean; + /** + * Header level for button wrapper. Must be between 1 and 6 + */ + "headingLevel"?: number; /** * For the 'optional' variant, the link to the form to complete without signing in */ @@ -155,9 +157,9 @@ export namespace Components { */ "timeLimit"?: string; /** - * Determines the text content and border/background color. + * Determines the text content and border/background color. Must be one of "signInRequired", "signInOptional", "signInEither", "verifyIdMe", or "verifyLoginGov". */ - "variant": VariantNames; + "variant"?: string; /** * If `true`, the alert will be visible. */ @@ -1845,10 +1847,6 @@ export interface VaAlertExpandableCustomEvent extends CustomEvent { detail: T; target: HTMLVaAlertExpandableElement; } -export interface VaAlertSignInCustomEvent extends CustomEvent { - detail: T; - target: HTMLVaAlertSignInElement; -} export interface VaBannerCustomEvent extends CustomEvent { detail: T; target: HTMLVaBannerElement; @@ -2097,24 +2095,12 @@ declare global { prototype: HTMLVaAlertExpandableElement; new (): HTMLVaAlertExpandableElement; }; - interface HTMLVaAlertSignInElementEventMap { - "va-component-did-load": any; - "component-library-analytics": any; - } /** * @componentName Alert - Sign In * @maturityCategory caution * @maturityLevel candidate */ interface HTMLVaAlertSignInElement extends Components.VaAlertSignIn, HTMLStencilElement { - addEventListener(type: K, listener: (this: HTMLVaAlertSignInElement, ev: VaAlertSignInCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; - addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLVaAlertSignInElement, ev: VaAlertSignInCustomEvent) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; - removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } var HTMLVaAlertSignInElement: { prototype: HTMLVaAlertSignInElement; @@ -3287,25 +3273,21 @@ declare namespace LocalJSX { */ "disableAnalytics"?: boolean; /** - * For the 'optional' variant, the link to the form to complete without signing in - */ - "noSignInLink"?: string; - /** - * The event used to track usage of the component. This is emitted when an anchor link is clicked and disableAnalytics is not true. + * Header level for button wrapper. Must be between 1 and 6 */ - "onComponent-library-analytics"?: (event: VaAlertSignInCustomEvent) => void; + "headingLevel"?: number; /** - * Fires when the component has successfully finished rendering for the first time. + * For the 'optional' variant, the link to the form to complete without signing in */ - "onVa-component-did-load"?: (event: VaAlertSignInCustomEvent) => void; + "noSignInLink"?: string; /** * For the 'optional' variant, how long the respondent has to submit their form */ "timeLimit"?: string; /** - * Determines the text content and border/background color. + * Determines the text content and border/background color. Must be one of "signInRequired", "signInOptional", "signInEither", "verifyIdMe", or "verifyLoginGov". */ - "variant"?: VariantNames; + "variant"?: string; /** * If `true`, the alert will be visible. */ diff --git a/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts b/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts index d98b1bd7c..ee536b686 100644 --- a/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts +++ b/packages/web-components/src/components/va-alert-sign-in/test/va-alert-sign-in.e2e.ts @@ -82,7 +82,7 @@ describe('va-alert-sign-in', () => { await axeCheck(page); }); - it('fires an analytics event when a link is clicked', async () => { + it.skip('fires an analytics event when a link is clicked', async () => { const page = await newE2EPage(); await page.setContent(''); @@ -101,7 +101,7 @@ describe('va-alert-sign-in', () => { }); }); - it('does not fire an analytics event when disableAnalytics is passed', async () => { + it.skip('does not fire an analytics event when disableAnalytics is passed', async () => { const page = await newE2EPage(); await page.setContent( '', @@ -115,7 +115,7 @@ describe('va-alert-sign-in', () => { expect(analyticsSpy).toHaveReceivedEventTimes(0); }); - it('should set variant to required if null', async () => { + it('should set variant to "required" if null', async () => { const page = await newE2EPage(); await page.setContent(''); @@ -128,7 +128,7 @@ describe('va-alert-sign-in', () => { ).toBeTruthy(); }); - it('should set variant to required if it is an empty string', async () => { + it('should set variant to "required" if it is an empty string', async () => { const page = await newE2EPage(); await page.setContent(''); @@ -141,7 +141,7 @@ describe('va-alert-sign-in', () => { ).toBeTruthy(); }); - it('should set variant to required if value not in pre-defined list', async () => { + it('should set variant to "required" if value not in pre-defined list', async () => { const page = await newE2EPage(); await page.setContent( '', @@ -156,7 +156,7 @@ describe('va-alert-sign-in', () => { ).toBeTruthy(); }); - it('should set variant to optional when specified', async () => { + it('should set variant to "optional" when specified', async () => { const page = await newE2EPage(); await page.setContent( ``, @@ -215,4 +215,24 @@ describe('va-alert-sign-in', () => { ), ).toBeTruthy(); }); + + it('should default the heading level to H2', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('va-alert-sign-in >>> h2'); + + expect(element).not.toBeNull(); + }); + + it('should set the heading level if provided', async () => { + const page = await newE2EPage(); + await page.setContent( + '', + ); + + const element = await page.find('va-alert-sign-in >>> h3'); + + expect(element).not.toBeNull(); + }); }); diff --git a/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx index c68423416..bface97f2 100644 --- a/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx +++ b/packages/web-components/src/components/va-alert-sign-in/va-alert-sign-in.tsx @@ -1,14 +1,15 @@ import { Component, Element, - Event, - EventEmitter, + // Event, + // EventEmitter, Host, Prop, h, } from '@stencil/core'; import classnames from 'classnames'; import { VariantNames } from './VariantNames'; +import { getHeaderLevel } from '../../utils/utils'; /** * @componentName Alert - Sign In @@ -24,14 +25,19 @@ export class VaAlertSignIn { @Element() el!: any; /** - * Determines the text content and border/background color. + * Determines the text content and border/background color. Must be one of "signInRequired", "signInOptional", "signInEither", "verifyIdMe", or "verifyLoginGov". */ - @Prop() variant: VariantNames = VariantNames.signInRequired; + @Prop() variant?: string = VariantNames.signInRequired; + + /** + * Header level for button wrapper. Must be between 1 and 6 + */ + @Prop() headingLevel?: number = 2; /** * If `true`, doesn't fire the CustomEvent which can be used for analytics tracking. */ - @Prop() disableAnalytics?: boolean = false; + @Prop() disableAnalytics?: boolean = true; /** * If `true`, the alert will be visible. @@ -48,62 +54,49 @@ export class VaAlertSignIn { */ @Prop() noSignInLink?: string; - /** - * Fires when the component has successfully finished rendering for the first - * time. - */ - @Event({ - eventName: 'va-component-did-load', - composed: true, - bubbles: true, - }) - vaComponentDidLoad: EventEmitter; - /** * The event used to track usage of the component. This is emitted when an * anchor link is clicked and disableAnalytics is not true. */ - @Event({ - eventName: 'component-library-analytics', - composed: true, - bubbles: true, - }) - componentLibraryAnalytics: EventEmitter; + // @Event({ + // eventName: 'component-library-analytics', + // composed: true, + // bubbles: true, + // }) + // componentLibraryAnalytics: EventEmitter; - private handleAlertBodyClick(e: MouseEvent): void { - if (!this.disableAnalytics) { - const target = e.target as HTMLElement; + // private handleAlertBodyClick(e: MouseEvent): void { + // if (!this.disableAnalytics) { + // const target = e.target as HTMLElement; - // If it's a link being clicked, dispatch an analytics event - if (target?.tagName === 'VA-LINK') { - const innerText = target.shadowRoot.querySelector('a').innerText; - const detail = { - componentName: 'va-alert-sign-in', - action: 'linkClick', - details: { - clickLabel: innerText, - variant: this.variant, - }, - }; - this.componentLibraryAnalytics.emit(detail); - } - } - } - - componentDidLoad() { - this.vaComponentDidLoad.emit(); - } + // // If it's a link being clicked, dispatch an analytics event + // if (target?.tagName === 'VA-LINK') { + // const innerText = target.shadowRoot.querySelector('a').innerText; + // const detail = { + // componentName: 'va-alert-sign-in', + // action: 'linkClick', + // details: { + // clickLabel: innerText, + // variant: this.variant, + // }, + // }; + // this.componentLibraryAnalytics.emit(detail); + // } + // } + // } render() { const { visible } = this; let { variant } = this; // Check that the provided variant (or null) matches a known variant name - if (!Object.values(VariantNames).includes(variant)) + if (!Object.values(VariantNames).includes(variant as VariantNames)) variant = VariantNames.signInRequired; + // Return an empty div if visible is set to false if (!visible) return
; + // Determine background and border colors const classes = classnames('usa-alert', `va-alert-sign-in--${variant}`, { 'usa-alert--info': variant === VariantNames.signInRequired || @@ -113,10 +106,15 @@ export class VaAlertSignIn { variant !== VariantNames.signInOptional, }); + // Create a header element + const HeaderLevel = getHeaderLevel(this.headingLevel); + /* eslint-disable i18next/no-literal-string */ const RequiredVariant = () => (