diff --git a/change/@fluentui-web-components-d423d05a-1b1e-422f-ab9a-afc288197681.json b/change/@fluentui-web-components-d423d05a-1b1e-422f-ab9a-afc288197681.json new file mode 100644 index 0000000000000..cd6c5c717b627 --- /dev/null +++ b/change/@fluentui-web-components-d423d05a-1b1e-422f-ab9a-afc288197681.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "add progressbar as new component", + "packageName": "@fluentui/web-components", + "email": "ryan@ryanmerrill.net", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md index 2796247c3e572..42d325f15e064 100644 --- a/packages/web-components/docs/api-report.md +++ b/packages/web-components/docs/api-report.md @@ -8,6 +8,7 @@ import { ElementStyles } from '@microsoft/fast-element'; import { ElementViewTemplate } from '@microsoft/fast-element'; import { FASTElement } from '@microsoft/fast-element'; import { FASTElementDefinition } from '@microsoft/fast-element'; +import { FASTProgress } from '@microsoft/fast-foundation'; import { StartEnd } from '@microsoft/fast-foundation'; import { StartEndOptions } from '@microsoft/fast-foundation'; import { StaticallyComposableHTML } from '@microsoft/fast-foundation'; @@ -176,6 +177,50 @@ export const CounterBadgeStyles: ElementStyles; // @public export const CounterBadgeTemplate: ElementViewTemplate; +// @public +export class ProgressBar extends FASTProgress { + shape: ProgressBarShape; + thickness: ProgressBarThickness; + validationState: ProgressBarValidationState | null; +} + +// @public +export const ProgressBarDefinition: FASTElementDefinition; + +// @public +export const ProgressBarShape: { + readonly rounded: "rounded"; + readonly square: "square"; +}; + +// @public +export type ProgressBarShape = ValuesOf; + +// @public +export const ProgressBarStyles: ElementStyles; + +// @public (undocumented) +export const ProgressBarTemplate: ElementViewTemplate; + +// @public +export const ProgressBarThickness: { + readonly medium: "medium"; + readonly large: "large"; +}; + +// @public +export type ProgressBarThickness = ValuesOf; + +// @public +export const ProgressBarValidationState: { + readonly success: "success"; + readonly warning: "warning"; + readonly error: "error"; +}; + +// @public +export type ProgressBarValidationState = ValuesOf; + // @public class Text_2 extends FASTElement { align: TextAlign; diff --git a/packages/web-components/package.json b/packages/web-components/package.json index 9cd9deeb1d6f2..6fc45457d8240 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -35,6 +35,10 @@ "./text": { "types": "./dist/esm/text/define.d.ts", "default": "./dist/esm/text/define.js" + }, + "./progress-bar": { + "types": "./dist/esm/progress-bar/define.d.ts", + "default": "./dist/esm/progress-bar/define.js" } }, "scripts": { diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts index bd9a8b7c5b0b6..0feb1f29f22f0 100644 --- a/packages/web-components/src/index.ts +++ b/packages/web-components/src/index.ts @@ -1,3 +1,4 @@ export * from './badge/index.js'; export * from './counter-badge/index.js'; +export * from './progress-bar/index.js'; export * from './text/index.js'; diff --git a/packages/web-components/src/progress-bar/define.ts b/packages/web-components/src/progress-bar/define.ts new file mode 100644 index 0000000000000..a0bf72c3f6d6a --- /dev/null +++ b/packages/web-components/src/progress-bar/define.ts @@ -0,0 +1,4 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition } from './progress-bar.definition.js'; + +definition.define(FluentDesignSystem.registry); diff --git a/packages/web-components/src/progress-bar/index.ts b/packages/web-components/src/progress-bar/index.ts new file mode 100644 index 0000000000000..8689e08b832e3 --- /dev/null +++ b/packages/web-components/src/progress-bar/index.ts @@ -0,0 +1,5 @@ +export * from './progress-bar.js'; +export * from './progress-bar.options.js'; +export { definition as ProgressBarDefinition } from './progress-bar.definition.js'; +export { styles as ProgressBarStyles } from './progress-bar.styles.js'; +export { template as ProgressBarTemplate } from './progress-bar.template.js'; diff --git a/packages/web-components/src/progress-bar/progress-bar.definition.ts b/packages/web-components/src/progress-bar/progress-bar.definition.ts new file mode 100644 index 0000000000000..0e0afa49b1aa5 --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.definition.ts @@ -0,0 +1,18 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { ProgressBar } from './progress-bar.js'; +import { styles } from './progress-bar.styles.js'; +import { template } from './progress-bar.template.js'; + +/** + * The Fluent ProgressBar Element. + * + * + * @public + * @remarks + * HTML Element: \ + */ +export const definition = ProgressBar.compose({ + name: `${FluentDesignSystem.prefix}-progress-bar`, + template, + styles, +}); diff --git a/packages/web-components/src/progress-bar/progress-bar.options.ts b/packages/web-components/src/progress-bar/progress-bar.options.ts new file mode 100644 index 0000000000000..21e5947c60952 --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.options.ts @@ -0,0 +1,47 @@ +import { ValuesOf } from '@microsoft/fast-foundation'; + +/** + * ProgressBarThickness Constants + * @public + */ +export const ProgressBarThickness = { + medium: 'medium', + large: 'large', +} as const; + +/** + * Applies bar thickness to the content + * @public + */ +export type ProgressBarThickness = ValuesOf; + +/** + * ProgressBarShape Constants + * @public + */ +export const ProgressBarShape = { + rounded: 'rounded', + square: 'square', +} as const; + +/** + * Applies bar shape to the content + * @public + */ +export type ProgressBarShape = ValuesOf; + +/** + * ProgressBarValidationState Constants + * @public + */ +export const ProgressBarValidationState = { + success: 'success', + warning: 'warning', + error: 'error', +} as const; + +/** + * Applies validation state to the content + * @public + */ +export type ProgressBarValidationState = ValuesOf; diff --git a/packages/web-components/src/progress-bar/progress-bar.stories.ts b/packages/web-components/src/progress-bar/progress-bar.stories.ts new file mode 100644 index 0000000000000..bd9203fff8c26 --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.stories.ts @@ -0,0 +1,69 @@ +import { html } from '@microsoft/fast-element'; +import type { Args, Meta } from '@storybook/html'; +import { renderComponent } from '../__test__/helpers.js'; +import type { ProgressBar as FluentProgressBar } from './progress-bar.js'; +import { ProgressBarShape, ProgressBarThickness, ProgressBarValidationState } from './progress-bar.options.js'; +import './define.js'; + +type ProgressStoryArgs = Args & FluentProgressBar; +type ProgressStoryMeta = Meta; + +const storyTemplate = html` + x.thickness} + shape=${x => x.shape} + max=${x => x.max} + aria-valuemax=${x => x.max} + aria-valuenow=${x => x.value} + value=${x => x.value} + validation-state=${x => x.validationState} + > +`; + +export default { + title: 'Components/ProgressBar', + args: { + max: 100, + value: 15, + thickness: 'medium', + shape: 'rounded', + validationState: null, + }, + argTypes: { + max: { + control: 'number', + defaultValue: 100, + }, + value: { + control: 'number', + defaultValue: 15, + }, + thickness: { + control: { + type: 'select', + }, + options: Object.values(ProgressBarThickness), + defaultValue: 'medium', + }, + shape: { + options: Object.values(ProgressBarShape), + control: { + type: 'select', + }, + defaultValue: 'rounded', + }, + validationState: { + options: Object.values(ProgressBarValidationState), + control: { + type: 'select', + }, + defaultValue: null, + }, + }, +} as ProgressStoryMeta; + +export const Progress = renderComponent(storyTemplate).bind({}); + +export const ProgressIndeterminate = renderComponent(html` + +`); diff --git a/packages/web-components/src/progress-bar/progress-bar.styles.ts b/packages/web-components/src/progress-bar/progress-bar.styles.ts new file mode 100644 index 0000000000000..281d19ffb5545 --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.styles.ts @@ -0,0 +1,167 @@ +import { css } from '@microsoft/fast-element'; +import { display } from '@microsoft/fast-foundation'; +import { + borderRadiusMedium, + colorBrandBackground2, + colorCompoundBrandBackground, + colorNeutralBackground6, + colorPaletteDarkOrangeBackground2, + colorPaletteDarkOrangeBackground3, + colorPaletteGreenBackground2, + colorPaletteGreenBackground3, + colorPaletteRedBackground2, + colorPaletteRedBackground3, +} from '../theme/design-tokens.js'; + +/** Text styles + * @public + */ +export const styles = css` + ${display('flex')} + + :host { + align-items: center; + height: 2px; + overflow-x: hidden; + border-radius: ${borderRadiusMedium}; + } + + :host([thickness='large']), + :host([thickness='large']) .progress, + :host([thickness='large']) .determinate { + height: 4px; + } + + :host([shape='square']), + :host([shape='square']) .progress, + :host([shape='square']) .determinate { + border-radius: 0; + } + + :host([validation-state='error']) .determinate { + background-color: ${colorPaletteRedBackground3}; + } + + :host([validation-state='error']) .indeterminate-indicator-1, + :host([validation-state='error']) .indeterminate-indicator-2 { + background: linear-gradient( + to right, + ${colorPaletteRedBackground2} 0%, + ${colorPaletteRedBackground3} 50%, + ${colorPaletteRedBackground2} + ); + } + + :host([validation-state='warning']) .determinate { + background-color: ${colorPaletteDarkOrangeBackground3}; + } + + :host([validation-state='warning']) .indeterminate-indicator-1, + :host([validation-state='warning']) .indeterminate-indicator-2 { + background: linear-gradient( + to right, + ${colorPaletteDarkOrangeBackground2} 0%, + ${colorPaletteDarkOrangeBackground3} 50%, + ${colorPaletteDarkOrangeBackground2} + ); + } + + :host([validation-state='success']) .determinate { + background-color: ${colorPaletteGreenBackground3}; + } + + :host([validation-state='success']) .indeterminate-indicator-1, + :host([validation-state='success']) .indeterminate-indicator-2 { + background: linear-gradient( + to right, + ${colorPaletteGreenBackground2} 0%, + ${colorPaletteGreenBackground3} 50%, + ${colorPaletteGreenBackground2} + ); + } + + .progress { + background-color: ${colorNeutralBackground6}; + border-radius: ${borderRadiusMedium}; + width: 100%; + height: 2px; + display: flex; + align-items: center; + position: relative; + } + + .determinate { + background-color: ${colorCompoundBrandBackground}; + border-radius: ${borderRadiusMedium}; + height: 2px; + transition: all 0.2s ease-in-out; + display: flex; + } + + .indeterminate-indicator-1 { + position: absolute; + opacity: 0; + height: 100%; + background: linear-gradient( + to right, + ${colorBrandBackground2} 0%, + ${colorCompoundBrandBackground} 50%, + ${colorBrandBackground2} + ); + border-radius: ${borderRadiusMedium}; + animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); + width: 40%; + animation: indeterminate-1 3s infinite; + } + + .indeterminate-indicator-2 { + position: absolute; + opacity: 0; + height: 100%; + background: linear-gradient( + to right, + ${colorBrandBackground2} 0%, + ${colorCompoundBrandBackground} 50%, + ${colorBrandBackground2} + ); + border-radius: ${borderRadiusMedium}; + animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); + width: 60%; + animation: indeterminate-2 3s infinite; + } + + @keyframes indeterminate-1 { + 0% { + opacity: 1; + transform: translateX(-100%); + } + 70% { + opacity: 1; + transform: translateX(300%); + } + 70.01% { + opacity: 0; + } + 100% { + opacity: 0; + transform: translateX(300%); + } + } + @keyframes indeterminate-2 { + 0% { + opacity: 0; + transform: translateX(-150%); + } + 29.99% { + opacity: 0; + } + 30% { + opacity: 1; + transform: translateX(-150%); + } + 100% { + transform: translateX(166.66%); + opacity: 1; + } + } +`; diff --git a/packages/web-components/src/progress-bar/progress-bar.template.ts b/packages/web-components/src/progress-bar/progress-bar.template.ts new file mode 100644 index 0000000000000..acf300205766e --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.template.ts @@ -0,0 +1,8 @@ +import type { ElementViewTemplate } from '@microsoft/fast-element'; +import { progressTemplate } from '@microsoft/fast-foundation'; +import type { ProgressBar } from './progress-bar.js'; + +export const template: ElementViewTemplate = progressTemplate({ + indeterminateIndicator1: ``, +}); diff --git a/packages/web-components/src/progress-bar/progress-bar.ts b/packages/web-components/src/progress-bar/progress-bar.ts new file mode 100644 index 0000000000000..aff1a3e153638 --- /dev/null +++ b/packages/web-components/src/progress-bar/progress-bar.ts @@ -0,0 +1,37 @@ +import { attr } from '@microsoft/fast-element'; +import { FASTProgress } from '@microsoft/fast-foundation'; +import type { ProgressBarShape, ProgressBarThickness, ProgressBarValidationState } from './progress-bar.options.js'; + +/** + * The base class used for constructing a fluent-progress-bar custom element + * @public + */ +export class ProgressBar extends FASTProgress { + /** + * The thickness of the progress bar + * + * @public + * @remarks + * HTML Attribute: thickness + */ + @attr + public thickness: ProgressBarThickness; + + /** + * The shape of the progress bar + * @public + * @remarks + * HTML Attribute: shape + */ + @attr + public shape: ProgressBarShape; + + /** + * The validation state of the progress bar + * @public + * @remarks + * HTML Attribute: validation-state + */ + @attr({ attribute: 'validation-state' }) + public validationState: ProgressBarValidationState | null; +}