-
Notifications
You must be signed in to change notification settings - Fork 20
Discovery: CDS copilot genereted Loader experiment #1664
base: main
Are you sure you want to change the base?
Changes from 4 commits
c8f2344
1ea4516
fb3461f
fc88955
2ab0f7a
e3087d6
15f9e7a
d66c0bc
c41de99
e6e610e
d40e518
8be886c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { useState } from 'react'; | ||
| import { getWebComponentDocs, propStructure, StoryDocs } from './wc-helpers'; | ||
|
|
||
| const loadingIndicatorDocs = getWebComponentDocs('va-loader'); | ||
|
|
||
| export default { | ||
| title: 'Components/Loader', | ||
| id: 'components/va-loader', | ||
| parameters: { | ||
| componentSubtitle: 'va-loader web component', | ||
| docs: { | ||
| page: () => ( | ||
| <StoryDocs storyDefault={Default} data={loadingIndicatorDocs} /> | ||
| ), | ||
| }, | ||
| actions: { | ||
| handles: ['component-library-analytics'], | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const defaultArgs = { | ||
| 'message': 'Loading your application...', | ||
| 'label': 'Loading', | ||
| 'set-focus': false, | ||
| 'enable-analytics': false, | ||
| }; | ||
|
|
||
| const Template = ({ | ||
| 'enable-analytics': enableAnalytics, | ||
| label, | ||
| message, | ||
| 'set-focus': setFocus, | ||
| }) => { | ||
| const [isLoading, setIsLoading] = useState(true); | ||
| return ( | ||
| <div> | ||
| {enableAnalytics && ( | ||
| <button | ||
| className="vads-u-display--flex vads-u-margin-x--auto" | ||
| onClick={() => setIsLoading(false)} | ||
| > | ||
| Finish loading | ||
| </button> | ||
| )} | ||
| {isLoading && <va-loader />} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const Default = Template.bind(null); | ||
| Default.args = { ...defaultArgs }; | ||
| Default.argTypes = propStructure(loadingIndicatorDocs); | ||
|
|
||
| export const SetFocus = Template.bind(null); | ||
| SetFocus.args = { ...defaultArgs, 'set-focus': true }; | ||
|
|
||
| export const EnableAnalytics = Template.bind(null); | ||
| EnableAnalytics.args = { ...defaultArgs, 'enable-analytics': true }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggeston (blocking): There is no |
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (blocking): The name of this file should be name of the component |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import { Component, Element, Host, Prop, State, h, Watch } from '@stencil/core'; | ||
|
|
||
| /** | ||
| * @componentName Loader | ||
| * @maturityCategory review | ||
| * @maturityLevel development | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (blocking): The |
||
| */ | ||
|
|
||
| @Component({ | ||
| tag: 'va-loader', | ||
| styleUrl: 'va-loader.css', | ||
| shadow: true, | ||
| }) | ||
| export class VaLoader { | ||
| @Element() el!: HTMLElement; | ||
| private textRef?: HTMLDivElement; | ||
| private rotationInterval: number; | ||
|
|
||
| @State() rotation: number = 0; | ||
| @State() loaderText: string; | ||
|
|
||
| /** | ||
| * The text to display in the center of the loader | ||
| */ | ||
| @Prop() centerLabel?: string = 'Loading'; | ||
|
|
||
| /** | ||
| * The ARIA role for the loader | ||
| */ | ||
| @Prop() loaderRole?: string = 'status'; | ||
|
|
||
| /** | ||
| * The ARIA live region setting | ||
| */ | ||
| @Prop() ariaLiveRegion?: string; | ||
|
|
||
| /** | ||
| * Whether the loader is currently busy (use 'true' or 'false' as string) | ||
| */ | ||
| @Prop() busy?: string; | ||
|
|
||
| /** | ||
| * Custom left alignment | ||
| */ | ||
| @Prop() alignLeft?: string = '0px'; | ||
|
|
||
| /** | ||
| * Custom top alignment | ||
| */ | ||
| @Prop() alignTop?: string = '0px'; | ||
|
|
||
| componentWillLoad() { | ||
| this.loaderText = this.centerLabel; | ||
| } | ||
|
|
||
| componentDidLoad() { | ||
| this.startRotation(); | ||
| this.adjustPosition(); | ||
| } | ||
|
|
||
| disconnectedCallback() { | ||
| window.clearInterval(this.rotationInterval); | ||
| } | ||
|
|
||
| @Watch('rotation') | ||
| handleRotationChange() { | ||
| switch (this.rotation) { | ||
| case 0: | ||
| this.loaderText = this.centerLabel; | ||
| break; | ||
| case 90: | ||
| this.loaderText = `${this.centerLabel}.`; | ||
| break; | ||
| case 180: | ||
| this.loaderText = `${this.centerLabel}..`; | ||
| break; | ||
| case 270: | ||
| this.loaderText = `${this.centerLabel}...`; | ||
| break; | ||
| } | ||
| this.adjustPosition(); | ||
| } | ||
|
|
||
| private startRotation() { | ||
| this.rotationInterval = window.setInterval(() => { | ||
| this.rotation = (this.rotation + 90) % 360; | ||
| }, 250); | ||
| } | ||
|
|
||
| private adjustPosition() { | ||
| if (this.textRef) { | ||
| const containsSpace = this.loaderText.includes(' '); | ||
| this.textRef.style.top = containsSpace ? '40%' : '45%'; | ||
| this.textRef.style.left = containsSpace ? '35%' : '30%'; | ||
| } | ||
| } | ||
|
|
||
| render() { | ||
| const spanStyle = { | ||
| left: this.alignLeft, | ||
| top: this.alignTop, | ||
| }; | ||
|
|
||
| return ( | ||
| <Host> | ||
| <div | ||
| class="vacds-loader" | ||
| role={this.loaderRole} | ||
| aria-live={this.ariaLiveRegion} | ||
| aria-busy={this.busy} | ||
| tabindex={0} | ||
| > | ||
| <div class="vacds-loader-border"> | ||
| <span class="edge edge-left"></span> | ||
| <span class="edge edge-right"></span> | ||
| </div> | ||
| <div class="vacds-loader-text" ref={el => (this.textRef = el)}> | ||
| <span style={spanStyle}>{this.loaderText}</span> | ||
| </div> | ||
| </div> | ||
| </Host> | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import { newE2EPage } from '@stencil/core/testing'; | ||
| import { axeCheck } from '../../../testing/test-helpers'; | ||
|
|
||
| describe('va-loader', () => { | ||
| it('renders', async () => { | ||
| const page = await newE2EPage(); | ||
| await page.setContent('<va-loader></va-loader>'); | ||
|
|
||
| const element = await page.find('va-loader'); | ||
| expect(element).not.toBeNull(); | ||
|
|
||
| const loaderDiv = await page.find('va-loader >>> .vacds-loader'); | ||
| expect(loaderDiv).not.toBeNull(); | ||
| expect(await loaderDiv.getAttribute('role')).toBe('status'); | ||
|
|
||
| const textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| const text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading'); | ||
| }); | ||
|
|
||
| it('renders with custom label', async () => { | ||
| const customLabel = 'Loading spinner'; | ||
| const page = await newE2EPage(); | ||
| await page.setContent( | ||
| `<va-loader center-label="${customLabel}"></va-loader>`, | ||
| ); | ||
|
|
||
| const textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| const text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe(customLabel); | ||
| }); | ||
|
|
||
| it('updates text based on rotation state', async () => { | ||
| const page = await newE2EPage(); | ||
| await page.setContent('<va-loader></va-loader>'); | ||
|
|
||
| // Initial state | ||
| let textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| let text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading'); | ||
|
|
||
| // Wait for first rotation (250ms) | ||
| await page.waitForTimeout(250); | ||
| textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading.'); | ||
|
|
||
| // Wait for second rotation (250ms) | ||
| await page.waitForTimeout(250); | ||
| textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading..'); | ||
|
|
||
| // Wait for third rotation (250ms) | ||
| await page.waitForTimeout(250); | ||
| textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading...'); | ||
|
|
||
| // Wait for fourth rotation (250ms) - back to initial state | ||
| await page.waitForTimeout(250); | ||
| textDiv = await page.find('va-loader >>> .vacds-loader-text'); | ||
| text = await textDiv.getProperty('textContent'); | ||
| expect(text).toBe('Loading'); | ||
| }); | ||
|
|
||
| it('passes an axe check', async () => { | ||
| const page = await newE2EPage(); | ||
| await page.setContent('<va-loader></va-loader>'); | ||
|
|
||
| await axeCheck(page); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
labelandmessageare declared but not being used.