From 90c6be9c5b11fee42b94ecbfdc82a7003fba2762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 2 Feb 2022 12:29:20 +0100 Subject: [PATCH 01/20] Use outline only for button focus --- packages/components/package.json | 4 +- .../components/src/button/button.styles.ts | 122 +++++++++--------- 2 files changed, 60 insertions(+), 66 deletions(-) diff --git a/packages/components/package.json b/packages/components/package.json index ce2f92f3..cab9e020 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -22,8 +22,8 @@ "build:docs": "build-storybook", "deploy:docs": "yarn run build:docs && gh-pages -d storybook-static", "doc": "api-extractor run --local", - "prettier:check": "prettier --config ./.prettierrc --check \"**/*.{ts,js,md}\"", - "prettier": "prettier --config ./.prettierrc --write \"**/*.{ts,js,md}\"", + "prettier:check": "prettier --config ../../.prettierrc --check \"src/**/*.ts\"", + "prettier": "prettier --config ../../.prettierrc --write \"src/**/*.ts\"", "eslint:check": "eslint . --ext .ts", "eslint": "eslint . --ext .ts --fix", "prepublishOnly": "yarn run build", diff --git a/packages/components/src/button/button.styles.ts b/packages/components/src/button/button.styles.ts index 56c1ff3a..0f5a6603 100644 --- a/packages/components/src/button/button.styles.ts +++ b/packages/components/src/button/button.styles.ts @@ -8,16 +8,17 @@ import { density, designUnit, disabledOpacity, + focusStrokeWidth, foregroundOnAccentActive, foregroundOnAccentHover, foregroundOnAccentRest, neutralFillActive, - neutralFillFocus, neutralFillHover, neutralFillRest, neutralFillStealthActive, neutralFillStealthHover, neutralFillStealthRest, + neutralFillStrongFocus, neutralForegroundRest, strokeWidth, typeRampBaseFontSize, @@ -38,10 +39,7 @@ import { errorFillActive, errorFillFocus, errorFillHover, - errorFillRest, - foregroundOnErrorActive, - foregroundOnErrorHover, - foregroundOnErrorRest + errorFillRest } from '../design-token'; import { heightNumber } from '../styles'; @@ -58,8 +56,8 @@ function appearanceBehavior(value: string, styles: ElementStyles) { return new PropertyStyleSheetBehavior('appearance', value, styles); } -// TODO do we really want to use outline for focus, active, ... => this call for a minimal style for toolbar probably -// outline force to use a padding so that the outline is not hidden by other elements. +// TODO do we really want to use outline for focus => this call for a minimal style for toolbar probably +// outline force to use a margin so that the outline is not hidden by other elements. /** * @internal @@ -90,9 +88,7 @@ const BaseButtonStyles = css` align-items: center; padding: 0 calc((10 + (${designUnit} * 2 * ${density})) * 1px); white-space: nowrap; - outline: 1px solid transparent; - outline-offset: calc((${designUnit} + ${density}) * 1px); - -moz-outline-radius: 0px; + outline: none; text-decoration: none; border: calc(${strokeWidth} * 1px) solid transparent; color: inherit; @@ -108,10 +104,6 @@ const BaseButtonStyles = css` background-color: ${neutralFillHover}; } - :host(:hover) .control { - outline-color: ${neutralFillHover}; - } - :host(:active) { background-color: ${neutralFillActive}; } @@ -124,12 +116,11 @@ const BaseButtonStyles = css` padding: 1px; } - :host(:active) .control { - outline-color: ${neutralFillActive}; - } - - :host .control:${focusVisible} { - outline-color: ${neutralFillFocus}; + /* prettier-ignore */ + .control:${focusVisible} { + outline: 1px solid ${neutralFillStrongFocus}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } .control::-moz-focus-inner { @@ -178,16 +169,19 @@ const BaseButtonStyles = css` color: ${SystemColors.HighlightText}; } - .control: ${focusVisible} { + /* prettier-ignore */ + .control:${focusVisible} { forced-color-adjust: none; background-color: ${SystemColors.Highlight}; - outline-color: ${SystemColors.ButtonText}; + outline: 1px solid ${SystemColors.ButtonText}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; color: ${SystemColors.HighlightText}; } .control:hover, :host([appearance='outline']) .control:hover { - outline-color: ${SystemColors.ButtonText}; + border-color: ${SystemColors.ButtonText}; } :host([href]) .control { @@ -196,10 +190,11 @@ const BaseButtonStyles = css` } :host([href]) .control:hover, - :host([href]) .control:${focusVisible} { + :host([href]) .control:${focusVisible} { forced-color-adjust: none; background: ${SystemColors.ButtonFace}; - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + box-shadow: 0 0 0 1px ${SystemColors.LinkText} inset; color: ${SystemColors.LinkText}; fill: currentColor; } @@ -221,18 +216,15 @@ const AccentButtonStyles = css` color: ${foregroundOnAccentHover}; } - :host([appearance='accent']:hover) .control { - outline-color: ${accentFillHover}; - } - :host([appearance='accent']:active) .control:active { background: ${accentFillActive}; color: ${foregroundOnAccentActive}; - outline-color: ${accentFillActive}; } :host([appearance="accent"]) .control:${focusVisible} { - outline-color: ${accentFillFocus}; + outline: 1px solid ${accentFillFocus}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } `.withBehaviors( forcedColorsStylesheetBehavior( @@ -246,12 +238,14 @@ const AccentButtonStyles = css` :host([appearance='accent']) .control:hover, :host([appearance='accent']:active) .control:active { background: ${SystemColors.HighlightText}; - outline-color: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; color: ${SystemColors.Highlight}; } :host([appearance="accent"]) .control:${focusVisible} { - outline-color: ${SystemColors.Highlight}; + outline: 1px solid ${SystemColors.Highlight}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } :host([appearance='accent'][href]) .control { @@ -261,13 +255,16 @@ const AccentButtonStyles = css` :host([appearance='accent'][href]) .control:hover { background: ${SystemColors.ButtonFace}; - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + box-shadow: none; color: ${SystemColors.LinkText}; fill: currentColor; } :host([appearance="accent"][href]) .control:${focusVisible} { - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) + ${SystemColors.HighlightText} inset; } ` ) @@ -279,26 +276,23 @@ const AccentButtonStyles = css` const ErrorButtonStyles = css` :host([appearance='error']) { background: ${errorFillRest}; - color: ${foregroundOnErrorRest}; + color: ${foregroundOnAccentRest}; } :host([appearance='error']:hover) { background: ${errorFillHover}; - color: ${foregroundOnErrorHover}; - } - - :host([appearance='error']:hover) .control { - outline-color: ${errorFillHover}; + color: ${foregroundOnAccentHover}; } :host([appearance='error']:active) .control:active { background: ${errorFillActive}; - color: ${foregroundOnErrorActive}; - outline-color: ${errorFillActive}; + color: ${foregroundOnAccentActive}; } :host([appearance="error"]) .control:${focusVisible} { - outline-color: ${errorFillFocus}; + outline: 1px solid ${errorFillFocus}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } `.withBehaviors( forcedColorsStylesheetBehavior( @@ -312,12 +306,14 @@ const ErrorButtonStyles = css` :host([appearance='error']) .control:hover, :host([appearance='error']:active) .control:active { background: ${SystemColors.HighlightText}; - outline-color: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; color: ${SystemColors.Highlight}; } :host([appearance="error"]) .control:${focusVisible} { - outline-color: ${SystemColors.Highlight}; + outline: 1px solid ${SystemColors.Highlight}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } :host([appearance='error'][href]) .control { @@ -327,13 +323,16 @@ const ErrorButtonStyles = css` :host([appearance='error'][href]) .control:hover { background: ${SystemColors.ButtonFace}; - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + box-shadow: none; color: ${SystemColors.LinkText}; fill: currentColor; } :host([appearance="error"][href]) .control:${focusVisible} { - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) + ${SystemColors.HighlightText} inset; } ` ) @@ -352,25 +351,18 @@ const OutlineButtonStyles = css` border-color: ${accentFillHover}; } - :host([appearance='outline']:hover) .control { - outline-color: ${accentFillHover}; - } - :host([appearance='outline']:active) { border-color: ${accentFillActive}; } - :host([appearance='outline']:active) .control:active { - outline-color: ${accentFillActive}; - } - :host([appearance='outline']) .control { border-color: inherit; } :host([appearance="outline"]) .control:${focusVisible} { - border-color: ${accentFillFocus}; - outline-color: ${accentFillActive}; + outline: 1px solid ${accentFillFocus}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; } `.withBehaviors( forcedColorsStylesheetBehavior( @@ -381,7 +373,9 @@ const OutlineButtonStyles = css` :host([appearance="outline"]) .control:${focusVisible} { forced-color-adjust: none; background-color: ${SystemColors.Highlight}; - border-color: ${SystemColors.ButtonText}; + outline: 1px solid ${SystemColors.ButtonText}; + outline-offset: calc((${designUnit} + ${density}) * 1px); + -moz-outline-radius: 0px; color: ${SystemColors.HighlightText}; fill: currentColor; } @@ -392,7 +386,7 @@ const OutlineButtonStyles = css` fill: currentColor; } :host([appearance="outline"][href]) .control:hover, - :host([appearance="outline"][href]) .control:${focusVisible} { + :host([appearance="outline"][href]) .control:${focusVisible} { forced-color-adjust: none; border-color: ${SystemColors.LinkText}; box-shadow: 0 0 0 1px ${SystemColors.LinkText} inset; @@ -423,14 +417,14 @@ const StealthButtonStyles = css` :host([appearance='stealth']) .control { forced-color-adjust: none; background: ${SystemColors.ButtonFace}; - outline-color: transparent; + border-color: transparent; color: ${SystemColors.ButtonText}; fill: currentColor; } :host([appearance='stealth']:hover) .control { background: ${SystemColors.Highlight}; - outline-color: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; color: ${SystemColors.HighlightText}; fill: currentColor; } @@ -447,9 +441,9 @@ const StealthButtonStyles = css` } :host([appearance="stealth"][href]:hover) .control, - :host([appearance="stealth"][href]:${focusVisible}) .control { + :host([appearance="stealth"][href]:${focusVisible}) .control { background: ${SystemColors.LinkText}; - outline-color: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; color: ${SystemColors.HighlightText}; fill: currentColor; } From 40b405bcdb316e917e4b1d81b09714f591df3d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 2 Feb 2022 14:41:14 +0100 Subject: [PATCH 02/20] Improve styling for text field --- .../src/text-field/text-field.styles.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/components/src/text-field/text-field.styles.ts b/packages/components/src/text-field/text-field.styles.ts index d67e37a0..770ceb39 100644 --- a/packages/components/src/text-field/text-field.styles.ts +++ b/packages/components/src/text-field/text-field.styles.ts @@ -3,9 +3,7 @@ // Distributed under the terms of the Modified BSD License. import { - accentFillActive, accentFillFocus, - accentFillHover, bodyFont, controlCornerRadius, designUnit, @@ -14,6 +12,9 @@ import { neutralFillInputHover, neutralFillInputRest, neutralFillRest, + neutralFillStrongFocus, + neutralFillStrongHover, + neutralFillStrongRest, neutralForegroundRest, neutralStrokeRest, strokeWidth, @@ -24,9 +25,9 @@ import { css, ElementStyles } from '@microsoft/fast-element'; import { disabledCursor, display, - ElementDefinitionContext, focusVisible, forcedColorsStylesheetBehavior, + FoundationElementTemplate, TextFieldOptions } from '@microsoft/fast-foundation'; import { SystemColors } from '@microsoft/fast-web-utilities'; @@ -36,13 +37,10 @@ import { heightNumber } from '../styles'; * Styles for Text Field * @public */ -export const textFieldStyles: ( - context: ElementDefinitionContext, - definition: TextFieldOptions -) => ElementStyles = ( - context: ElementDefinitionContext, - definition: TextFieldOptions -) => +export const textFieldStyles: FoundationElementTemplate< + ElementStyles, + TextFieldOptions +> = (context, definition) => css` ${display('inline-block')} :host { font-family: ${bodyFont}; @@ -58,7 +56,7 @@ export const textFieldStyles: ( color: ${neutralForegroundRest}; background: ${neutralFillInputRest}; border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; + border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; height: calc(${heightNumber} * 1px); } @@ -122,12 +120,12 @@ export const textFieldStyles: ( :host(:hover:not([disabled])) .root { background: ${neutralFillInputHover}; - border-color: ${accentFillHover}; + border-color: ${neutralFillStrongHover}; } :host(:active:not([disabled])) .root { background: ${neutralFillInputHover}; - border-color: ${accentFillActive}; + border-color: ${neutralFillStrongFocus}; } :host(:focus-within:not([disabled])) .root { From 9d0ea9816924f18b6414f1cdc8b328f00c562df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 2 Feb 2022 16:24:04 +0100 Subject: [PATCH 03/20] Add Avatar, Breadcrumb and Breadcrumb Item --- .../components/src/avatar/avatar.stories.ts | 0 packages/components/src/avatar/avatar.test.ts | 0 packages/components/src/avatar/index.ts | 38 +++++ .../breadcrumb-item.stories.ts | 53 +++++++ .../breadcrumb-item/breadcrumb-item.styles.ts | 148 ++++++++++++++++++ .../breadcrumb-item/breadcrumb-item.test.ts | 38 +++++ .../components/src/breadcrumb-item/index.ts | 37 +++++ .../src/breadcrumb/breadcrumb.stories.ts | 73 +++++++++ .../src/breadcrumb/breadcrumb.test.ts | 38 +++++ packages/components/src/breadcrumb/index.ts | 32 ++++ .../components/src/button/button.stories.ts | 2 +- .../components/src/button/button.styles.ts | 4 + packages/components/src/button/button.test.ts | 14 +- packages/components/src/custom-elements.ts | 19 ++- packages/components/src/index.ts | 6 + .../components/src/option/option.stories.ts | 2 +- packages/components/src/option/option.test.ts | 6 +- .../components/src/select/select.stories.ts | 2 +- packages/components/src/select/select.test.ts | 8 +- .../src/text-field/text-field.stories.ts | 2 +- .../src/text-field/text-field.test.ts | 18 +-- 21 files changed, 512 insertions(+), 28 deletions(-) create mode 100644 packages/components/src/avatar/avatar.stories.ts create mode 100644 packages/components/src/avatar/avatar.test.ts create mode 100644 packages/components/src/avatar/index.ts create mode 100644 packages/components/src/breadcrumb-item/breadcrumb-item.stories.ts create mode 100644 packages/components/src/breadcrumb-item/breadcrumb-item.styles.ts create mode 100644 packages/components/src/breadcrumb-item/breadcrumb-item.test.ts create mode 100644 packages/components/src/breadcrumb-item/index.ts create mode 100644 packages/components/src/breadcrumb/breadcrumb.stories.ts create mode 100644 packages/components/src/breadcrumb/breadcrumb.test.ts create mode 100644 packages/components/src/breadcrumb/index.ts diff --git a/packages/components/src/avatar/avatar.stories.ts b/packages/components/src/avatar/avatar.stories.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/components/src/avatar/avatar.test.ts b/packages/components/src/avatar/avatar.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/components/src/avatar/index.ts b/packages/components/src/avatar/index.ts new file mode 100644 index 00000000..808ed934 --- /dev/null +++ b/packages/components/src/avatar/index.ts @@ -0,0 +1,38 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + Avatar, + imgTemplate, + avatarStyles as styles +} from '@microsoft/fast-components'; +import { + AvatarOptions, + Avatar as FoundationAvatar, + avatarTemplate as template +} from '@microsoft/fast-foundation'; + +export { Avatar } from '@microsoft/fast-components'; + +/** + * A function that returns a {@link @microsoft/fast-foundation#Avatar} registration for configuring the component with a DesignSystem. + * {@link @microsoft/fast-foundation#avatarTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpAvatar = Avatar.compose({ + baseName: 'avatar', + baseClass: FoundationAvatar, + template, + styles, + media: imgTemplate, + shadowOptions: { + delegatesFocus: true + } +}); + +export { styles as avatarStyles }; diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.stories.ts b/packages/components/src/breadcrumb-item/breadcrumb-item.stories.ts new file mode 100644 index 00000000..e88325a4 --- /dev/null +++ b/packages/components/src/breadcrumb-item/breadcrumb-item.stories.ts @@ -0,0 +1,53 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { getFaIcon, setTheme } from '../utilities/storybook'; + +export default { + title: 'Breadcrumb Item', + argTypes: { + href: { control: 'boolean' }, + startIcon: { control: 'boolean' }, + endIcon: { control: 'boolean' } + } +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): string => { + setTheme(accent, parameters.backgrounds, backgrounds); + + return ` + ${args.startIcon ? getFaIcon('folder', 'start') : ''} + Breadcrumb item + ${args.endIcon ? getFaIcon('robot', 'end') : ''} + `; +}; + +export const Default = Template.bind({}); +Default.args = { + href: true, + startIcon: false, + endIcon: false +}; + +export const WithoutHref = Template.bind({}); +WithoutHref.args = { + ...Default.args, + href: false +}; + +export const WithStartIcon = Template.bind({}); +WithStartIcon.args = { + ...Default.args, + startIcon: true +}; + +export const WithEndIcon = Template.bind({}); +WithEndIcon.args = { + ...Default.args, + endIcon: true +}; diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.styles.ts b/packages/components/src/breadcrumb-item/breadcrumb-item.styles.ts new file mode 100644 index 00000000..1459e365 --- /dev/null +++ b/packages/components/src/breadcrumb-item/breadcrumb-item.styles.ts @@ -0,0 +1,148 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { css, ElementStyles } from '@microsoft/fast-element'; +import { + BreadcrumbItemOptions, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; +import { + accentForegroundActive, + accentForegroundFocus, + accentForegroundHover, + accentForegroundRest, + bodyFont, + focusStrokeWidth, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight +} from '@microsoft/fast-components'; +import { heightNumber } from '../styles/index'; + +/** + * Styles for Breadcrumb item + * @public + */ +export const breadcrumbItemStyles: FoundationElementTemplate< + ElementStyles, + BreadcrumbItemOptions +> = (context, definition) => + css` + ${display('inline-flex')} :host { + background: transparent; + box-sizing: border-box; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + fill: currentColor; + line-height: ${typeRampBaseLineHeight}; + min-width: calc(${heightNumber} * 1px); + outline: none; + color: ${neutralForegroundRest} + } + + .listitem { + display: flex; + align-items: center; + width: max-content; + } + + .separator { + margin: 0 6px; + display: flex; + } + + .control { + align-items: center; + box-sizing: border-box; + color: ${accentForegroundRest}; + cursor: pointer; + display: flex; + fill: inherit; + outline: none; + text-decoration: none; + white-space: nowrap; + } + + .control:hover { + color: ${accentForegroundHover}; + } + + .control:active { + color: ${accentForegroundActive}; + } + + .control .content { + position: relative; + } + + .control .content::before { + content: ""; + display: block; + height: calc(${strokeWidth} * 1px); + left: 0; + position: absolute; + right: 0; + top: calc(1em + 4px); + width: 100%; + } + + .control:hover .content::before { + background: ${accentForegroundHover}; + } + + .control:active .content::before { + background: ${accentForegroundActive}; + } + + .control:${focusVisible} .content::before { + background: ${accentForegroundFocus}; + height: calc(${focusStrokeWidth} * 1px); + } + + .control:not([href]) { + color: ${neutralForegroundRest}; + cursor: default; + } + + .control:not([href]) .content::before { + background: none; + } + + .start, + .end { + display: flex; + } + + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + } + + .start { + margin-inline-end: 6px; + } + + .end { + margin-inline-start: 6px; + } +`.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .control:hover .content::before, + .control:${focusVisible} .content::before { + background: ${SystemColors.LinkText}; + } + .start, + .end { + fill: ${SystemColors.ButtonText}; + } + ` + ) + ); diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts new file mode 100644 index 00000000..3c8f68e8 --- /dev/null +++ b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts @@ -0,0 +1,38 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test.describe('Breadcrumb Item', () => { + test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb-item--default'); + + expect( + await page.locator('jp-breadcrumb-item').screenshot() + ).toMatchSnapshot('breadcrumb-item-default.png'); + }); + + test('Without href', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb-item--without-href'); + + expect( + await page.locator('jp-breadcrumb-item').screenshot() + ).toMatchSnapshot('breadcrumb-item-without-href.png'); + }); + + test('With Start Icon', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb-item--with-start-icon'); + + expect( + await page.locator('jp-breadcrumb-item').screenshot() + ).toMatchSnapshot('breadcrumb-item-with-start-icon.png'); + }); + + test('With End Icon', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb-item--with-end-icon'); + + expect( + await page.locator('jp-breadcrumb-item').screenshot() + ).toMatchSnapshot('breadcrumb-item-with-end-icon.png'); + }); +}); diff --git a/packages/components/src/breadcrumb-item/index.ts b/packages/components/src/breadcrumb-item/index.ts new file mode 100644 index 00000000..153f468b --- /dev/null +++ b/packages/components/src/breadcrumb-item/index.ts @@ -0,0 +1,37 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + BreadcrumbItem, + BreadcrumbItemOptions, + breadcrumbItemTemplate as template +} from '@microsoft/fast-foundation'; +import { breadcrumbItemStyles as styles } from './breadcrumb-item.styles'; + +/** + * A function that returns a BreadcrumbItem registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#breadcrumbItemTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpBreadcrumbItem = BreadcrumbItem.compose({ + baseName: 'breadcrumb-item', + template, + styles, + separator: '/', + shadowOptions: { + delegatesFocus: true + } +}); + +/** + * Base class for BreadcrumbItem + * @public + */ +export { BreadcrumbItem }; + +export { styles as breadcrumbItemStyles }; diff --git a/packages/components/src/breadcrumb/breadcrumb.stories.ts b/packages/components/src/breadcrumb/breadcrumb.stories.ts new file mode 100644 index 00000000..6ff0e5e2 --- /dev/null +++ b/packages/components/src/breadcrumb/breadcrumb.stories.ts @@ -0,0 +1,73 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { getFaIcon, setTheme } from '../utilities/storybook'; + +export default { + title: 'Breadcrumb', + argTypes: { + customChildren: { control: 'boolean' }, + svgSeparator: { control: 'boolean' }, + startIcon: { control: 'boolean' }, + endIcon: { control: 'boolean' } + } +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): string => { + setTheme(accent, parameters.backgrounds, backgrounds); + + return ` + ${[1, 2, 3].map(v => + args.customChildren + ? ` + ${args.startIcon ? getFaIcon('folder', 'start') : ''} + + Breadcrumb item ${v} + + ${args.endIcon ? getFaIcon('robot', 'end') : ''} + ${args.svgSeparator ? getFaIcon('angle-right', 'separator') : ''} + ` + : ` + ${args.startIcon ? getFaIcon('folder', 'start') : ''} + Breadcrumb item ${v} + ${args.endIcon ? getFaIcon('robot', 'end') : ''} + ${args.svgSeparator ? getFaIcon('angle-right', 'separator') : ''} + ` + )} + `; +}; + +export const Default = Template.bind({}); +Default.args = { + customChildren: false, + svgSeparator: false, + startIcon: false, + endIcon: false +}; + +export const WithCustomChildren = Template.bind({}); +WithCustomChildren.args = { + ...Default.args, + customChildren: true +}; + +export const WithSvgSeparator = Template.bind({}); +WithSvgSeparator.args = { + ...Default.args, + svgSeparator: true +}; + +export const WithStartIcon = Template.bind({}); +WithStartIcon.args = { + ...Default.args, + startIcon: true +}; + +export const WithEndIcon = Template.bind({}); +WithEndIcon.args = { + ...Default.args, + endIcon: true +}; diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts b/packages/components/src/breadcrumb/breadcrumb.test.ts new file mode 100644 index 00000000..f3da8df6 --- /dev/null +++ b/packages/components/src/breadcrumb/breadcrumb.test.ts @@ -0,0 +1,38 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test.describe('Breadcrumb', () => { + test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb--default'); + + expect(await page.locator('jp-breadcrumb').screenshot()).toMatchSnapshot( + 'breadcrumb-default.png' + ); + }); + + test('With Custom Children', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb--with-custom-children'); + + expect(await page.locator('jp-breadcrumb').screenshot()).toMatchSnapshot( + 'breadcrumb-with-custom-children.png' + ); + }); + + test('With Start Icon', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb--with-start-icon'); + + expect(await page.locator('jp-breadcrumb').screenshot()).toMatchSnapshot( + 'breadcrumb-with-start-icon.png' + ); + }); + + test('With End Icon', async ({ page }) => { + await page.goto('/iframe.html?id=breadcrumb--with-end-icon'); + + expect(await page.locator('jp-breadcrumb').screenshot()).toMatchSnapshot( + 'breadcrumb-with-end-icon.png' + ); + }); +}); diff --git a/packages/components/src/breadcrumb/index.ts b/packages/components/src/breadcrumb/index.ts new file mode 100644 index 00000000..fa06812f --- /dev/null +++ b/packages/components/src/breadcrumb/index.ts @@ -0,0 +1,32 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + Breadcrumb, + breadcrumbTemplate as template +} from '@microsoft/fast-foundation'; +import { breadcrumbStyles as styles } from '@microsoft/fast-components'; + +/** + * A function that returns a Breadcrumb registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#breadcrumbTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpBreadcrumb = Breadcrumb.compose({ + baseName: 'breadcrumb', + template, + styles +}); + +/** + * Base class for Breadcrumb + * @public + */ +export { Breadcrumb }; + +export { styles as breadcrumbStyles }; diff --git a/packages/components/src/button/button.stories.ts b/packages/components/src/button/button.stories.ts index 93287be2..1380cba8 100644 --- a/packages/components/src/button/button.stories.ts +++ b/packages/components/src/button/button.stories.ts @@ -5,7 +5,7 @@ import { action } from '@storybook/addon-actions'; import { getFaIcon, setTheme } from '../utilities/storybook'; export default { - title: 'Library/Button', + title: 'Button', argTypes: { label: { control: 'text' }, appearance: { diff --git a/packages/components/src/button/button.styles.ts b/packages/components/src/button/button.styles.ts index 0f5a6603..f77346bb 100644 --- a/packages/components/src/button/button.styles.ts +++ b/packages/components/src/button/button.styles.ts @@ -1,3 +1,7 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + import { accentFillActive, accentFillFocus, diff --git a/packages/components/src/button/button.test.ts b/packages/components/src/button/button.test.ts index e48dba56..e374f868 100644 --- a/packages/components/src/button/button.test.ts +++ b/packages/components/src/button/button.test.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; test.describe('Button', () => { test('Default', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--default'); + await page.goto('/iframe.html?id=button--default'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-default.png' @@ -10,7 +10,7 @@ test.describe('Button', () => { }); test('Error', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--error'); + await page.goto('/iframe.html?id=button--error'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-error.png' @@ -18,7 +18,7 @@ test.describe('Button', () => { }); test('Neutral', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--neutral'); + await page.goto('/iframe.html?id=button--neutral'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-neutral.png' @@ -26,7 +26,7 @@ test.describe('Button', () => { }); // test('With Autofocus', async ({ page }) => { - // await page.goto('/iframe.html?id=library-button--with-autofocus'); + // await page.goto('/iframe.html?id=button--with-autofocus'); // expect( // await page.locator('jp-button').screenshot() @@ -34,7 +34,7 @@ test.describe('Button', () => { // }); test('With Disabled', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--with-disabled'); + await page.goto('/iframe.html?id=button--with-disabled'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-with-disabled.png' @@ -42,7 +42,7 @@ test.describe('Button', () => { }); test('With Start Icon', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--with-start-icon'); + await page.goto('/iframe.html?id=button--with-start-icon'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-with-start-icon.png' @@ -50,7 +50,7 @@ test.describe('Button', () => { }); test('Icon Only', async ({ page }) => { - await page.goto('/iframe.html?id=library-button--icon-only'); + await page.goto('/iframe.html?id=button--icon-only'); expect(await page.locator('jp-button').screenshot()).toMatchSnapshot( 'button-icon-only.png' diff --git a/packages/components/src/custom-elements.ts b/packages/components/src/custom-elements.ts index 7dbcd518..12ce6b56 100644 --- a/packages/components/src/custom-elements.ts +++ b/packages/components/src/custom-elements.ts @@ -2,6 +2,9 @@ // Distributed under the terms of the Modified BSD License. import type { Container } from '@microsoft/fast-foundation'; +import { jpAvatar } from './avatar/index'; +import { jpBreadcrumb } from './breadcrumb/index'; +import { jpBreadcrumbItem } from './breadcrumb-item/index'; import { jpButton } from './button/index'; import { jpOption } from './option/index'; import { jpSelect } from './select/index'; @@ -10,13 +13,24 @@ import { jpTextField } from './text-field/index'; // Don't delete these. They're needed so that API-extractor doesn't add import types // with improper pathing /* eslint-disable @typescript-eslint/no-unused-vars */ +import type { Avatar } from './avatar/index'; +import type { Breadcrumb } from './breadcrumb/index'; +import type { BreadcrumbItem } from './breadcrumb-item/index'; import type { Button } from './button/index'; import type { Option } from './option/index'; import type { Select } from './select/index'; import type { TextField } from './text-field/index'; // export all components -export { jpButton, jpOption, jpSelect, jpTextField }; +export { + jpAvatar, + jpBreadcrumb, + jpBreadcrumbItem, + jpButton, + jpOption, + jpSelect, + jpTextField +}; /** * All Jupyter Web Components @@ -26,6 +40,9 @@ export { jpButton, jpOption, jpSelect, jpTextField }; * statically link and register all available components. */ export const allComponents = { + jpAvatar, + jpBreadcrumb, + jpBreadcrumbItem, jpButton, jpOption, jpSelect, diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index f3df3627..70f73c98 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -8,7 +8,13 @@ export * from './jupyter-design-system'; export * from './custom-elements'; // Export components and classes +export * from './avatar/index'; +export * from './breadcrumb/index'; +export * from './breadcrumb-item/index'; export * from './button/index'; +export * from './option/index'; +export * from './select/index'; +export * from './text-field/index'; // Add Jupyter theme change listener { diff --git a/packages/components/src/option/option.stories.ts b/packages/components/src/option/option.stories.ts index 74549aff..1e5bf435 100644 --- a/packages/components/src/option/option.stories.ts +++ b/packages/components/src/option/option.stories.ts @@ -1,7 +1,7 @@ import { setTheme } from '../utilities/storybook'; export default { - title: 'Library/Option', + title: 'Option', argTypes: { label: { control: 'text' }, isDisabled: { control: 'boolean' }, diff --git a/packages/components/src/option/option.test.ts b/packages/components/src/option/option.test.ts index 9d346c95..4c0d8ab5 100644 --- a/packages/components/src/option/option.test.ts +++ b/packages/components/src/option/option.test.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; test.describe('Option', () => { test('Default', async ({ page }) => { - await page.goto('/iframe.html?id=library-option--default'); + await page.goto('/iframe.html?id=option--default'); expect(await page.locator('jp-option').screenshot()).toMatchSnapshot( 'option-default.png' @@ -10,7 +10,7 @@ test.describe('Option', () => { }); test('With Disabled', async ({ page }) => { - await page.goto('/iframe.html?id=library-option--with-disabled'); + await page.goto('/iframe.html?id=option--with-disabled'); expect(await page.locator('jp-option').screenshot()).toMatchSnapshot( 'option-disabled.png' @@ -18,7 +18,7 @@ test.describe('Option', () => { }); test('With Selected', async ({ page }) => { - await page.goto('/iframe.html?id=library-option--with-selected'); + await page.goto('/iframe.html?id=option--with-selected'); expect(await page.locator('jp-option').screenshot()).toMatchSnapshot( 'option-selected.png' diff --git a/packages/components/src/select/select.stories.ts b/packages/components/src/select/select.stories.ts index 9053df50..e167a824 100644 --- a/packages/components/src/select/select.stories.ts +++ b/packages/components/src/select/select.stories.ts @@ -5,7 +5,7 @@ import { action } from '@storybook/addon-actions'; import { getFaIcon, setTheme } from '../utilities/storybook'; export default { - title: 'Library/Select', + title: 'Select', argTypes: { isOpen: { control: 'boolean' }, isDisabled: { control: 'boolean' }, diff --git a/packages/components/src/select/select.test.ts b/packages/components/src/select/select.test.ts index e2e6ffe8..e24ccfed 100644 --- a/packages/components/src/select/select.test.ts +++ b/packages/components/src/select/select.test.ts @@ -5,7 +5,7 @@ import { test, expect } from '@playwright/test'; test.describe('Select', () => { test('Default', async ({ page }) => { - await page.goto('/iframe.html?id=library-select--default'); + await page.goto('/iframe.html?id=select--default'); expect(await page.locator('jp-select').screenshot()).toMatchSnapshot( 'select-default.png' @@ -13,7 +13,7 @@ test.describe('Select', () => { }); test('WithOpen', async ({ page }) => { - await page.goto('/iframe.html?id=library-select--with-open'); + await page.goto('/iframe.html?id=select--with-open'); expect(await page.locator('jp-select').screenshot()).toMatchSnapshot( 'select-with-open.png' @@ -21,7 +21,7 @@ test.describe('Select', () => { }); test('WithDisabled', async ({ page }) => { - await page.goto('/iframe.html?id=library-select--with-disabled'); + await page.goto('/iframe.html?id=select--with-disabled'); expect(await page.locator('jp-select').screenshot()).toMatchSnapshot( 'select-with-disabled.png' @@ -29,7 +29,7 @@ test.describe('Select', () => { }); test('WithCustomIndicator', async ({ page }) => { - await page.goto('/iframe.html?id=library-select--with-custom-indicator'); + await page.goto('/iframe.html?id=select--with-custom-indicator'); expect(await page.locator('jp-select').screenshot()).toMatchSnapshot( 'select-with-custom-indicator.png' diff --git a/packages/components/src/text-field/text-field.stories.ts b/packages/components/src/text-field/text-field.stories.ts index 18f15bf9..036d6e8e 100644 --- a/packages/components/src/text-field/text-field.stories.ts +++ b/packages/components/src/text-field/text-field.stories.ts @@ -3,7 +3,7 @@ import { getFaIcon, setTheme } from '../utilities/storybook'; import { TextField } from './index'; export default { - title: 'Library/Text Field', + title: 'Text Field', argTypes: { label: { control: 'text' }, placeholder: { control: 'text' }, diff --git a/packages/components/src/text-field/text-field.test.ts b/packages/components/src/text-field/text-field.test.ts index cc859ff0..1149874d 100644 --- a/packages/components/src/text-field/text-field.test.ts +++ b/packages/components/src/text-field/text-field.test.ts @@ -5,7 +5,7 @@ import { test, expect } from '@playwright/test'; test.describe('Text Field', () => { test('Default', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--default'); + await page.goto('/iframe.html?id=text-field--default'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-default.png' @@ -13,7 +13,7 @@ test.describe('Text Field', () => { }); test('With Placeholder', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-placeholder'); + await page.goto('/iframe.html?id=text-field--with-placeholder'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-placeholder.png' @@ -21,7 +21,7 @@ test.describe('Text Field', () => { }); test('With Autofocus', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-autofocus'); + await page.goto('/iframe.html?id=text-field--with-autofocus'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-autofocus.png' @@ -29,7 +29,7 @@ test.describe('Text Field', () => { }); test('With Disabled', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-disabled'); + await page.goto('/iframe.html?id=text-field--with-disabled'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-disabled.png' @@ -37,7 +37,7 @@ test.describe('Text Field', () => { }); test('With Size', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-size'); + await page.goto('/iframe.html?id=text-field--with-size'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-size.png' @@ -45,7 +45,7 @@ test.describe('Text Field', () => { }); test('With Type', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-type'); + await page.goto('/iframe.html?id=text-field--with-type'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-type.png' @@ -53,7 +53,7 @@ test.describe('Text Field', () => { }); test('With Max Length', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-max-length'); + await page.goto('/iframe.html?id=text-field--with-max-length'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-max-length.png' @@ -61,7 +61,7 @@ test.describe('Text Field', () => { }); test('With Readonly', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-readonly'); + await page.goto('/iframe.html?id=text-field--with-readonly'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-readonly.png' @@ -69,7 +69,7 @@ test.describe('Text Field', () => { }); test('With Start Icon', async ({ page }) => { - await page.goto('/iframe.html?id=library-text-field--with-start-icon'); + await page.goto('/iframe.html?id=text-field--with-start-icon'); expect(await page.locator('jp-text-field').screenshot()).toMatchSnapshot( 'text-field-with-start-icon.png' From ab6fbcbfab652fa23a22b72ecd9b8e16cf4a1be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 2 Feb 2022 17:04:07 +0100 Subject: [PATCH 04/20] Add stories and test for avatar --- .../components/src/avatar/avatar.stories.ts | 72 +++++++++++++++++++ packages/components/src/avatar/avatar.test.ts | 30 ++++++++ 2 files changed, 102 insertions(+) diff --git a/packages/components/src/avatar/avatar.stories.ts b/packages/components/src/avatar/avatar.stories.ts index e69de29b..28fc4b1a 100644 --- a/packages/components/src/avatar/avatar.stories.ts +++ b/packages/components/src/avatar/avatar.stories.ts @@ -0,0 +1,72 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { setTheme } from '../utilities/storybook'; + +export default { + title: 'Avatar', + argTypes: { + shape: { control: 'select', options: ['circle', 'square', 'default'] }, + fill: { + control: 'select', + options: ['accent-primary', 'accent-secondary'] + }, + color: { control: 'select', options: ['foo', 'bar'] }, + image: { control: 'boolean' } + }, + decorators: [ + story => ` + ${story()}` + ] +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): string => { + setTheme(accent, parameters.backgrounds, backgrounds); + + return ` + ${ + args.image + ? '' + : 'JS' + } + `; +}; + +export const Default = Template.bind({}); +Default.args = { + shape: 'circle', + fill: 'accent-primary', + color: 'foo', + image: false +}; + +export const Square = Template.bind({}); +Square.args = { + ...Default.args, + shape: 'square' +}; + +export const WithImage = Template.bind({}); +WithImage.args = { + ...Default.args, + image: true +}; diff --git a/packages/components/src/avatar/avatar.test.ts b/packages/components/src/avatar/avatar.test.ts index e69de29b..d40f2d56 100644 --- a/packages/components/src/avatar/avatar.test.ts +++ b/packages/components/src/avatar/avatar.test.ts @@ -0,0 +1,30 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test.describe('Avatar', () => { + test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=avatar--default'); + + expect(await page.locator('jp-avatar').screenshot()).toMatchSnapshot( + 'avatar-default.png' + ); + }); + + test('Square', async ({ page }) => { + await page.goto('/iframe.html?id=avatar--square'); + + expect(await page.locator('jp-avatar').screenshot()).toMatchSnapshot( + 'avatar-square.png' + ); + }); + + test('With Image', async ({ page }) => { + await page.goto('/iframe.html?id=avatar--with-image'); + + expect(await page.locator('jp-avatar').screenshot()).toMatchSnapshot( + 'avatar-with-image.png' + ); + }); +}); From 4fde4e17165531ad37cb3c482de9e6303dbb67d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 2 Feb 2022 17:22:53 +0100 Subject: [PATCH 05/20] Add progress --- packages/components/src/custom-elements.ts | 4 ++ packages/components/src/index.ts | 1 + packages/components/src/progress/index.ts | 35 +++++++++++ .../src/progress/progress.stories.ts | 60 +++++++++++++++++++ .../components/src/progress/progress.test.ts | 14 +++++ 5 files changed, 114 insertions(+) create mode 100644 packages/components/src/progress/index.ts create mode 100644 packages/components/src/progress/progress.stories.ts create mode 100644 packages/components/src/progress/progress.test.ts diff --git a/packages/components/src/custom-elements.ts b/packages/components/src/custom-elements.ts index 12ce6b56..0d584bdb 100644 --- a/packages/components/src/custom-elements.ts +++ b/packages/components/src/custom-elements.ts @@ -7,6 +7,7 @@ import { jpBreadcrumb } from './breadcrumb/index'; import { jpBreadcrumbItem } from './breadcrumb-item/index'; import { jpButton } from './button/index'; import { jpOption } from './option/index'; +import { jpProgress } from './progress/index'; import { jpSelect } from './select/index'; import { jpTextField } from './text-field/index'; @@ -18,6 +19,7 @@ import type { Breadcrumb } from './breadcrumb/index'; import type { BreadcrumbItem } from './breadcrumb-item/index'; import type { Button } from './button/index'; import type { Option } from './option/index'; +import type { Progress } from './progress/index'; import type { Select } from './select/index'; import type { TextField } from './text-field/index'; @@ -28,6 +30,7 @@ export { jpBreadcrumbItem, jpButton, jpOption, + jpProgress, jpSelect, jpTextField }; @@ -45,6 +48,7 @@ export const allComponents = { jpBreadcrumbItem, jpButton, jpOption, + jpProgress, jpSelect, jpTextField, register(container?: Container, ...rest: any[]): void { diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 70f73c98..0176783d 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -13,6 +13,7 @@ export * from './breadcrumb/index'; export * from './breadcrumb-item/index'; export * from './button/index'; export * from './option/index'; +export * from './progress/index'; export * from './select/index'; export * from './text-field/index'; diff --git a/packages/components/src/progress/index.ts b/packages/components/src/progress/index.ts new file mode 100644 index 00000000..0cdaadc0 --- /dev/null +++ b/packages/components/src/progress/index.ts @@ -0,0 +1,35 @@ +import { + BaseProgress as Progress, + ProgressOptions, + progressTemplate as template +} from '@microsoft/fast-foundation'; +import { progressStyles as styles } from '@microsoft/fast-components'; + +/** + * A function that returns a {@link @microsoft/fast-foundation#BaseProgress} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#progressTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpProgress = Progress.compose({ + baseName: 'progress', + template, + styles, + indeterminateIndicator1: ` + + `, + indeterminateIndicator2: ` + + ` +}); + +/** + * Base class for Progress + * @public + */ +export { Progress }; + +export { styles as progressStyles }; diff --git a/packages/components/src/progress/progress.stories.ts b/packages/components/src/progress/progress.stories.ts new file mode 100644 index 00000000..dc46eddd --- /dev/null +++ b/packages/components/src/progress/progress.stories.ts @@ -0,0 +1,60 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { setTheme } from '../utilities/storybook'; + +export default { + title: 'Progress', + argTypes: { + min: { control: 'number', min: 0 }, + max: { control: 'number', min: 0 }, + value: { control: 'number', min: 0 }, + paused: { control: 'boolean' }, + height: { control: 'number', min: 4 } + }, + decorators: [ + story => `
+ ${story()} +
` + ] +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): string => { + setTheme(accent, parameters.backgrounds, backgrounds); + return ` + `; +}; + +export const Default = Template.bind({}); +Default.args = { + min: null, + max: null, + value: null, + paused: false, + height: null +}; + +export const WithValue = Template.bind({}); +WithValue.args = { + ...Default.args, + min: 0, + max: 50, + value: 30 +}; + +export const Paused = Template.bind({}); +Paused.args = { + ...WithValue.args, + paused: true +}; diff --git a/packages/components/src/progress/progress.test.ts b/packages/components/src/progress/progress.test.ts new file mode 100644 index 00000000..34393942 --- /dev/null +++ b/packages/components/src/progress/progress.test.ts @@ -0,0 +1,14 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test.describe('Progress', () => { + test('With value', async ({ page }) => { + await page.goto('/iframe.html?id=avatar--with-value'); + + expect(await page.locator('jp-avatar').screenshot()).toMatchSnapshot( + 'avatar-with-value.png' + ); + }); +}); From a0d2f741a68a3d90a2474366691687d7609bc8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Thu, 3 Feb 2022 11:00:35 +0100 Subject: [PATCH 06/20] Add search component --- packages/components/src/custom-elements.ts | 4 + packages/components/src/index.ts | 1 + packages/components/src/search/index.ts | 41 +++ .../components/src/search/search.stories.ts | 122 +++++++++ .../components/src/search/search.styles.ts | 236 ++++++++++++++++++ packages/components/src/search/search.test.ts | 62 +++++ packages/components/src/text-field/index.ts | 13 +- .../src/text-field/text-field.stories.ts | 3 + .../src/text-field/text-field.styles.ts | 4 +- 9 files changed, 482 insertions(+), 4 deletions(-) create mode 100644 packages/components/src/search/index.ts create mode 100644 packages/components/src/search/search.stories.ts create mode 100644 packages/components/src/search/search.styles.ts create mode 100644 packages/components/src/search/search.test.ts diff --git a/packages/components/src/custom-elements.ts b/packages/components/src/custom-elements.ts index 0d584bdb..78032dea 100644 --- a/packages/components/src/custom-elements.ts +++ b/packages/components/src/custom-elements.ts @@ -8,6 +8,7 @@ import { jpBreadcrumbItem } from './breadcrumb-item/index'; import { jpButton } from './button/index'; import { jpOption } from './option/index'; import { jpProgress } from './progress/index'; +import { jpSearch } from './search/index'; import { jpSelect } from './select/index'; import { jpTextField } from './text-field/index'; @@ -20,6 +21,7 @@ import type { BreadcrumbItem } from './breadcrumb-item/index'; import type { Button } from './button/index'; import type { Option } from './option/index'; import type { Progress } from './progress/index'; +import type { Search } from './search/index'; import type { Select } from './select/index'; import type { TextField } from './text-field/index'; @@ -31,6 +33,7 @@ export { jpButton, jpOption, jpProgress, + jpSearch, jpSelect, jpTextField }; @@ -49,6 +52,7 @@ export const allComponents = { jpButton, jpOption, jpProgress, + jpSearch, jpSelect, jpTextField, register(container?: Container, ...rest: any[]): void { diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 0176783d..02cde8c5 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -14,6 +14,7 @@ export * from './breadcrumb-item/index'; export * from './button/index'; export * from './option/index'; export * from './progress/index'; +export * from './search/index'; export * from './select/index'; export * from './text-field/index'; diff --git a/packages/components/src/search/index.ts b/packages/components/src/search/index.ts new file mode 100644 index 00000000..40b0f97e --- /dev/null +++ b/packages/components/src/search/index.ts @@ -0,0 +1,41 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + Search as FoundationSearch, + searchTemplate as template +} from '@microsoft/fast-foundation'; +import { Search } from '@microsoft/fast-components'; +import { searchStyles as styles } from './search.styles'; + +// TODO +// we need to add error/invalid + +/** + * A function that returns a {@link @microsoft/fast-foundation#Search} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#searchTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + * + * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} + */ +export const jpSearch = Search.compose({ + baseName: 'search', + baseClass: FoundationSearch, + template, + styles, + shadowOptions: { + delegatesFocus: true + } +}); + +export { Search, SearchAppearance } from '@microsoft/fast-components'; + +/** + * Styles for Search + * @public + */ +export { styles as searchStyles }; diff --git a/packages/components/src/search/search.stories.ts b/packages/components/src/search/search.stories.ts new file mode 100644 index 00000000..5c74c82f --- /dev/null +++ b/packages/components/src/search/search.stories.ts @@ -0,0 +1,122 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { action } from '@storybook/addon-actions'; +import { getFaIcon, setTheme } from '../utilities/storybook'; +import { Search } from './index'; + +export default { + title: 'Search', + argTypes: { + label: { control: 'text' }, + placeholder: { control: 'text' }, + value: { control: 'text' }, + maxLength: { control: 'number' }, + size: { control: 'number' }, + isReadOnly: { control: 'boolean' }, + isDisabled: { control: 'boolean' }, + isAutoFocused: { control: 'boolean' }, + searchIcon: { control: 'boolean' }, + appearance: { control: 'radio', options: ['outline', 'filled'] }, + onChange: { + action: 'changed', + table: { + disable: true + } + } + } +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): HTMLElement => { + setTheme(accent, parameters.backgrounds, backgrounds); + const container = document.createElement('div'); + container.insertAdjacentHTML( + 'afterbegin', + ` + ${args.label} + ${args.searchIcon ? getFaIcon('search', 'end') : ''} + ` + ); + + const search = container.firstChild as Search; + + if (args.value) { + search.value = args.value; + } + + if (args.onChange) { + search.addEventListener('change', args.onChange); + } + + return search; +}; + +export const Default = Template.bind({}); +Default.args = { + label: 'Search Label', + placeholder: '', + value: '', + maxLength: '', + size: '', + isReadOnly: false, + isDisabled: false, + isAutoFocused: false, + appearance: 'outline', + searchIcon: false, + onChange: action('search-onchange') +}; + +export const WithPlaceholder = Template.bind({}); +WithPlaceholder.args = { + ...Default.args, + placeholder: 'Placeholder Text' +}; + +export const WithAutofocus = Template.bind({}); +WithAutofocus.args = { + ...Default.args, + autofocus: true +}; + +export const WithDisabled = Template.bind({}); +WithDisabled.args = { + ...Default.args, + disabled: true +}; + +export const WithSize = Template.bind({}); +WithSize.args = { + ...Default.args, + placeholder: 'This search is 50 characters in width', + size: 50 +}; + +export const WithMaxLength = Template.bind({}); +WithMaxLength.args = { + ...Default.args, + placeholder: 'This search field can only contain a maximum of 10 characters', + maxLength: 10 +}; + +export const WithReadonly = Template.bind({}); +WithReadonly.args = { + ...Default.args, + readonly: true +}; + +export const WithSearchIcon = Template.bind({}); +WithSearchIcon.args = { + ...Default.args, + searchIcon: true +}; diff --git a/packages/components/src/search/search.styles.ts b/packages/components/src/search/search.styles.ts new file mode 100644 index 00000000..d4339e5c --- /dev/null +++ b/packages/components/src/search/search.styles.ts @@ -0,0 +1,236 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + accentFillFocus, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + fillColor, + neutralFillHover, + neutralFillInputHover, + neutralFillInputRest, + neutralFillStrongActive, + neutralFillStrongHover, + neutralFillStrongRest, + neutralForegroundRest, + neutralStrokeRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight +} from '@microsoft/fast-components'; +import { css, ElementStyles } from '@microsoft/fast-element'; +import { + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + TextFieldOptions +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; +import { heightNumber } from '../styles/index'; + +export const searchStyles: FoundationElementTemplate< + ElementStyles, + TextFieldOptions +> = (context, definition) => + css` + ${display('inline-block')} :host { + font-family: ${bodyFont}; + outline: none; + user-select: none; + } + + .root { + box-sizing: border-box; + position: relative; + display: flex; + flex-direction: row; + color: ${neutralForegroundRest}; + background: ${neutralFillInputRest}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; + height: calc(${heightNumber} * 1px); + } + + .control { + -webkit-appearance: none; + font: inherit; + background: transparent; + border: 0; + color: inherit; + height: calc(100% - 4px); + width: 100%; + margin-top: auto; + margin-bottom: auto; + border: none; + padding: 0; + padding-inline-start: calc(${designUnit} * 2px + 1px); + padding-inline-end: calc( + (${designUnit} * 2px) + (${heightNumber} * 1px) + 1px + ); + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + } + + .control::-webkit-search-cancel-button { + -webkit-appearance: none; + } + + .control:hover, + .control:${focusVisible}, + .control:disabled, + .control:active { + outline: none; + } + + /* Cancel margin set for button focus outline */ + jp-button { + margin: 0; + } + + .clear-button { + position: absolute; + right: 0; + top: 1px; + height: calc(100% - 2px); + opacity: 0; + } + + .input-wrapper { + display: flex; + position: relative; + width: 100%; + } + + .label { + display: block; + color: ${neutralForegroundRest}; + cursor: pointer; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + margin-bottom: 4px; + } + + .label__hidden { + display: none; + visibility: hidden; + } + + .start, + .end { + display: flex; + margin: 1px; + fill: currentcolor; + } + + ::slotted([slot='end']) { + height: 100%; + } + + .end { + margin-inline-end: 1px; + } + + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + margin-inline-end: 11px; + margin-inline-start: 11px; + margin-top: auto; + margin-bottom: auto; + } + + :host(:hover:not([disabled])) .root { + background: ${neutralFillInputHover}; + border-color: ${neutralFillStrongHover}; + } + + :host(:active:not([disabled])) .root { + background: ${neutralFillInputHover}; + border-color: ${neutralFillStrongActive}; + } + + :host(:focus-within:not([disabled])) .root { + border-color: ${accentFillFocus}; + } + + .clear-button__hidden { + opacity: 0; + } + + :host(:hover:not([disabled], [readOnly])) .clear-button, + :host(:active:not([disabled], [readOnly])) .clear-button, + :host(:focus-within:not([disabled], [readOnly])) .clear-button { + opacity: 1; + } + + :host(:hover:not([disabled], [readOnly])) .clear-button__hidden, + :host(:active:not([disabled], [readOnly])) .clear-button__hidden, + :host(:focus-within:not([disabled], [readOnly])) .clear-button__hidden { + opacity: 0; + } + + :host([appearance='filled']) .root { + background: ${fillColor}; + } + + :host([appearance='filled']:hover:not([disabled])) .root { + background: ${neutralFillHover}; + } + + :host([disabled]) .label, + :host([readonly]) .label, + :host([readonly]) .control, + :host([disabled]) .control { + cursor: ${disabledCursor}; + } + + :host([disabled]) { + opacity: ${disabledOpacity}; + } + + :host([disabled]) .control { + border-color: ${neutralStrokeRest}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .root, + :host([appearance='filled']) .root { + forced-color-adjust: none; + background: ${SystemColors.Field}; + border-color: ${SystemColors.FieldText}; + } + :host(:hover:not([disabled])) .root, + :host([appearance='filled']:hover:not([disabled])) .root, + :host([appearance='filled']:hover) .root { + background: ${SystemColors.Field}; + border-color: ${SystemColors.Highlight}; + } + .start, + .end { + fill: currentcolor; + } + :host([disabled]) { + opacity: 1; + } + :host([disabled]) .root, + :host([appearance='filled']:hover[disabled]) .root { + border-color: ${SystemColors.GrayText}; + background: ${SystemColors.Field}; + } + :host(:focus-within:enabled) .root { + border-color: ${SystemColors.Highlight}; + box-shadow: 0 0 0 1px ${SystemColors.Highlight} inset; + } + input::placeholder { + color: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/search/search.test.ts b/packages/components/src/search/search.test.ts new file mode 100644 index 00000000..68f98e89 --- /dev/null +++ b/packages/components/src/search/search.test.ts @@ -0,0 +1,62 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { test, expect } from '@playwright/test'; + +test.describe('Search', () => { + test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=search--default'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-default.png' + ); + }); + + test('With Placeholder', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-placeholder'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-placeholder.png' + ); + }); + + test('With Autofocus', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-autofocus'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-autofocus.png' + ); + }); + + test('With Disabled', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-disabled'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-disabled.png' + ); + }); + + test('With Size', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-size'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-size.png' + ); + }); + + test('With Maxlength', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-max-length'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-maxlength.png' + ); + }); + + test('With Search Icon', async ({ page }) => { + await page.goto('/iframe.html?id=search--with-search-icon'); + + expect(await page.locator('jp-search').screenshot()).toMatchSnapshot( + 'search-with-search-icon.png' + ); + }); +}); diff --git a/packages/components/src/text-field/index.ts b/packages/components/src/text-field/index.ts index 3f1724f1..2856f0d7 100644 --- a/packages/components/src/text-field/index.ts +++ b/packages/components/src/text-field/index.ts @@ -1,7 +1,11 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + import { - TextField, + TextField as FoundationTextField, textFieldTemplate as template } from '@microsoft/fast-foundation'; +import { TextField } from '@microsoft/fast-components'; import { textFieldStyles as styles } from './text-field.styles'; // TODO @@ -19,6 +23,7 @@ import { textFieldStyles as styles } from './text-field.styles'; */ export const jpTextField = TextField.compose({ baseName: 'text-field', + baseClass: FoundationTextField, template, styles, shadowOptions: { @@ -26,6 +31,10 @@ export const jpTextField = TextField.compose({ } }); -export { TextField }; +export { TextField, TextFieldAppearance } from '@microsoft/fast-components'; +/** + * Styles for TextField + * @public + */ export { styles as textFieldStyles }; diff --git a/packages/components/src/text-field/text-field.stories.ts b/packages/components/src/text-field/text-field.stories.ts index 036d6e8e..fa4bf3fb 100644 --- a/packages/components/src/text-field/text-field.stories.ts +++ b/packages/components/src/text-field/text-field.stories.ts @@ -1,3 +1,6 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + import { action } from '@storybook/addon-actions'; import { getFaIcon, setTheme } from '../utilities/storybook'; import { TextField } from './index'; diff --git a/packages/components/src/text-field/text-field.styles.ts b/packages/components/src/text-field/text-field.styles.ts index 770ceb39..305c5819 100644 --- a/packages/components/src/text-field/text-field.styles.ts +++ b/packages/components/src/text-field/text-field.styles.ts @@ -12,7 +12,7 @@ import { neutralFillInputHover, neutralFillInputRest, neutralFillRest, - neutralFillStrongFocus, + neutralFillStrongActive, neutralFillStrongHover, neutralFillStrongRest, neutralForegroundRest, @@ -125,7 +125,7 @@ export const textFieldStyles: FoundationElementTemplate< :host(:active:not([disabled])) .root { background: ${neutralFillInputHover}; - border-color: ${neutralFillStrongFocus}; + border-color: ${neutralFillStrongActive}; } :host(:focus-within:not([disabled])) .root { From 8fb80950b073eff9703a5966dd46443a295ff095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Thu, 3 Feb 2022 17:28:35 +0100 Subject: [PATCH 07/20] Add tabs --- .../components/src/avatar/avatar.stories.ts | 5 + .../src/breadcrumb/breadcrumb.stories.ts | 5 + packages/components/src/custom-elements.ts | 12 ++ packages/components/src/index.ts | 3 + .../src/progress/progress.stories.ts | 5 + packages/components/src/tab-panel/index.ts | 31 ++++ packages/components/src/tab/index.ts | 28 +++ packages/components/src/tab/tab.styles.ts | 165 ++++++++++++++++++ packages/components/src/tabs/index.ts | 31 ++++ packages/components/src/tabs/tabs.stories.ts | 63 +++++++ packages/components/src/tabs/tabs.styles.ts | 135 ++++++++++++++ packages/components/src/tabs/tabs.test.ts | 30 ++++ 12 files changed, 513 insertions(+) create mode 100644 packages/components/src/tab-panel/index.ts create mode 100644 packages/components/src/tab/index.ts create mode 100644 packages/components/src/tab/tab.styles.ts create mode 100644 packages/components/src/tabs/index.ts create mode 100644 packages/components/src/tabs/tabs.stories.ts create mode 100644 packages/components/src/tabs/tabs.styles.ts create mode 100644 packages/components/src/tabs/tabs.test.ts diff --git a/packages/components/src/avatar/avatar.stories.ts b/packages/components/src/avatar/avatar.stories.ts index 28fc4b1a..7bbe50b5 100644 --- a/packages/components/src/avatar/avatar.stories.ts +++ b/packages/components/src/avatar/avatar.stories.ts @@ -14,6 +14,11 @@ export default { color: { control: 'select', options: ['foo', 'bar'] }, image: { control: 'boolean' } }, + parameters: { + actions: { + disabled: true + } + }, decorators: [ story => `