From ddbbb6c11288fb1debbf95ef5e2b1b7dd3cca241 Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:14:31 -0700 Subject: [PATCH 1/2] feat: update progress bar to use element internals custom states for visual states --- ...-024847b6-4c7b-4e23-964d-466ce50c33a5.json | 7 +++ .../src/progress-bar/progress-bar.styles.ts | 13 +++--- .../src/progress-bar/progress-bar.ts | 46 +++++++++++++++++++ .../web-components/src/styles/states/index.ts | 6 +++ 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 change/@fluentui-web-components-024847b6-4c7b-4e23-964d-466ce50c33a5.json diff --git a/change/@fluentui-web-components-024847b6-4c7b-4e23-964d-466ce50c33a5.json b/change/@fluentui-web-components-024847b6-4c7b-4e23-964d-466ce50c33a5.json new file mode 100644 index 0000000000000..46d19d8bc276f --- /dev/null +++ b/change/@fluentui-web-components-024847b6-4c7b-4e23-964d-466ce50c33a5.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: update progress bar to use element internals custom states for visual states", + "packageName": "@fluentui/web-components", + "email": "13071055+chrisdholt@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/src/progress-bar/progress-bar.styles.ts b/packages/web-components/src/progress-bar/progress-bar.styles.ts index ab3295c6c4d6a..56637ca5b3a61 100644 --- a/packages/web-components/src/progress-bar/progress-bar.styles.ts +++ b/packages/web-components/src/progress-bar/progress-bar.styles.ts @@ -10,6 +10,7 @@ import { colorPaletteRedBackground3, colorTransparentBackground, } from '../theme/design-tokens.js'; +import { errorState, largeState, squareState, successState, warningState } from '../styles/states/index.js'; /** ProgressBar styles * @public @@ -26,11 +27,11 @@ export const styles = css` contain: content; } - :host([thickness='large']) { + :host(${largeState}) { height: 4px; } - :host([shape='square']) { + :host(${squareState}) { border-radius: ${borderRadiusNone}; } @@ -59,15 +60,15 @@ export const styles = css` animation-iteration-count: infinite; } - :host([validation-state='error']) .indicator { + :host(${errorState}) .indicator { background-color: ${colorPaletteRedBackground3}; } - :host([validation-state='warning']) .indicator { + :host(${warningState}) .indicator { background-color: ${colorPaletteDarkOrangeBackground3}; } - :host([validation-state='success']) .indicator { + :host(${successState}) .indicator { background-color: ${colorPaletteGreenBackground3}; } @@ -98,7 +99,7 @@ export const styles = css` background-color: CanvasText; } .indicator, - :host(:is([validation-state='success'], [validation-state='warning'], [validation-state='error'])) .indicator { + :host(:is(${successState}, ${warningState}, ${errorState})) .indicator { background-color: Highlight; } `), diff --git a/packages/web-components/src/progress-bar/progress-bar.ts b/packages/web-components/src/progress-bar/progress-bar.ts index c432005cd5755..5d0bf6a4f2a46 100644 --- a/packages/web-components/src/progress-bar/progress-bar.ts +++ b/packages/web-components/src/progress-bar/progress-bar.ts @@ -1,4 +1,5 @@ import { attr, FASTElement, nullableNumberConverter, volatile } from '@microsoft/fast-element'; +import { toggleState } from '../utils/element-internals.js'; import { ProgressBarShape, ProgressBarThickness, ProgressBarValidationState } from './progress-bar.options.js'; /** @@ -24,6 +25,20 @@ export class ProgressBar extends FASTElement { @attr public thickness?: ProgressBarThickness; + /** + * Handles changes to thickness attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public thicknessChanged(prev: ProgressBarThickness | undefined, next: ProgressBarThickness | undefined) { + if (prev) { + toggleState(this.elementInternals, `${prev}`, false); + } + if (next) { + toggleState(this.elementInternals, `${next}`, true); + } + } + /** * The shape of the progress bar * @public @@ -32,6 +47,20 @@ export class ProgressBar extends FASTElement { @attr public shape?: ProgressBarShape; + /** + * Handles changes to shape attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public shapeChanged(prev: ProgressBarShape | undefined, next: ProgressBarShape | undefined) { + if (prev) { + toggleState(this.elementInternals, `${prev}`, false); + } + if (next) { + toggleState(this.elementInternals, `${next}`, true); + } + } + /** * The validation state of the progress bar * @public @@ -40,6 +69,23 @@ export class ProgressBar extends FASTElement { @attr({ attribute: 'validation-state' }) public validationState: ProgressBarValidationState | null = null; + /** + * Handles changes to validation-state attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public validationStateChanged( + prev: ProgressBarValidationState | undefined, + next: ProgressBarValidationState | undefined, + ) { + if (prev) { + toggleState(this.elementInternals, `${prev}`, false); + } + if (next) { + toggleState(this.elementInternals, `${next}`, true); + } + } + /** * The value of the progress * @internal diff --git a/packages/web-components/src/styles/states/index.ts b/packages/web-components/src/styles/states/index.ts index bb948364f5cf5..e5fcc36ff02e7 100644 --- a/packages/web-components/src/styles/states/index.ts +++ b/packages/web-components/src/styles/states/index.ts @@ -138,6 +138,12 @@ export const pressedState = css.partial`:is([state--pressed], :state(pressed))`; */ export const brandState = css.partial`:is([state--brand], :state(brand))`; +/** + * Selector for the `error` state. + * @public + */ +export const errorState = css.partial`:is([state--error], :state(error))`; + /** * Selector for the `danger` state. * @public From 597c15b25103eaae875f041018de0766d6c391eb Mon Sep 17 00:00:00 2001 From: Chris Holt <13071055+chrisdholt@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:37:46 -0700 Subject: [PATCH 2/2] tests! --- packages/web-components/docs/api-report.md | 3 +++ .../src/progress-bar/progress-bar.spec.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md index be2383ba72e78..9b09601dbb3d6 100644 --- a/packages/web-components/docs/api-report.md +++ b/packages/web-components/docs/api-report.md @@ -2487,8 +2487,11 @@ export class ProgressBar extends FASTElement { // @internal get percentComplete(): number; shape?: ProgressBarShape; + shapeChanged(prev: ProgressBarShape | undefined, next: ProgressBarShape | undefined): void; thickness?: ProgressBarThickness; + thicknessChanged(prev: ProgressBarThickness | undefined, next: ProgressBarThickness | undefined): void; validationState: ProgressBarValidationState | null; + validationStateChanged(prev: ProgressBarValidationState | undefined, next: ProgressBarValidationState | undefined): void; // @internal value?: number; // @internal diff --git a/packages/web-components/src/progress-bar/progress-bar.spec.ts b/packages/web-components/src/progress-bar/progress-bar.spec.ts index 65503d977f6d9..1b5a700a38114 100644 --- a/packages/web-components/src/progress-bar/progress-bar.spec.ts +++ b/packages/web-components/src/progress-bar/progress-bar.spec.ts @@ -101,12 +101,15 @@ test.describe('Progress Bar', () => { }); await expect(element).toHaveJSProperty('thickness', 'medium'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('medium'))).toBe(true); await element.evaluate((node: ProgressBar) => { node.thickness = 'large'; }); await expect(element).toHaveJSProperty('thickness', 'large'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('medium'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('large'))).toBe(true); }); test('should set and retrieve the `shape` property correctly', async ({ page }) => { @@ -117,12 +120,15 @@ test.describe('Progress Bar', () => { }); await expect(element).toHaveJSProperty('shape', 'square'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('square'))).toBe(true); await element.evaluate((node: ProgressBar) => { node.shape = 'rounded'; }); await expect(element).toHaveJSProperty('shape', 'rounded'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('square'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('rounded'))).toBe(true); }); test('should set and retrieve the `validationState` property correctly', async ({ page }) => { @@ -133,17 +139,30 @@ test.describe('Progress Bar', () => { }); await expect(element).toHaveJSProperty('validationState', 'success'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('success'))).toBe(true); await element.evaluate((node: ProgressBar) => { node.validationState = 'warning'; }); await expect(element).toHaveJSProperty('validationState', 'warning'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('success'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('warning'))).toBe(true); await element.evaluate((node: ProgressBar) => { node.validationState = 'error'; }); await expect(element).toHaveJSProperty('validationState', 'error'); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('warning'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('error'))).toBe(true); + + await element.evaluate((node: ProgressBar) => { + node.validationState = null; + }); + + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('success'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('warning'))).toBe(false); + expect(await element.evaluate((node: ProgressBar) => node.elementInternals.states.has('error'))).toBe(false); }); });