diff --git a/.github/workflows/visual-test.yml b/.github/workflows/visual-test.yml index fa54c0af..70df2afe 100644 --- a/.github/workflows/visual-test.yml +++ b/.github/workflows/visual-test.yml @@ -9,7 +9,7 @@ on: jobs: visual-test: runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 40 steps: - name: Checkout 🛎️ uses: actions/checkout@v2 diff --git a/README.md b/README.md index 7b499224..c8e989a5 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,91 @@ # UI Toolkit for Jupyter -**WIP this is very early work in progress and nothing is yet working.** But don't hesitate to open issues and PRs if you want to help. +**WIP this is early work in progress.** But don't hesitate to open issues and PRs if you want to +help. -![Extension status](https://img.shields.io/badge/status-draft-critical 'Not yet working') +[![Extension status](https://img.shields.io/badge/status-draft-critical 'Not yet working')](https://jupyterlab-contrib.github.io/) [![NPM Version](https://img.shields.io/npm/v/@jupyter-notebook/web-components?color=blue)](https://www.npmjs.com/package/@jupyter-notebook/web-components) [![Toolkit CI Status](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/actions/workflows/ci.yml) -![Deploy Docs Status](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/actions/workflows/docs-cd.yml/badge.svg) +[![Deploy Docs Status](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/actions/workflows/docs-cd.yml/badge.svg)](https://jupyterlab-contrib.github.io/jupyter-ui-toolkit/) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab-contrib/jupyter-ui-toolkit/main) -![Toolkit for Jupyter Artwork](./packages/components/docs/assets/toolkit-artwork.png) +![Toolkit for Jupyter Artwork](https://raw.githubusercontent.com/jupyterlab-contrib/jupyter-ui-toolkit/main/packages/components/docs/assets/toolkit-artwork.png) ## Introduction -The UI Toolkit is a component library for building web interfaces in Jupyter ecosystem (JupyterHub, Jupyter Widgets, JupyterLab,...). +The UI Toolkit is a component library for building web interfaces in Jupyter ecosystem (JupyterHub, +Jupyter Widgets, JupyterLab,...). Features of the library include: -- **Implements the Jupyter design language:** All components follow the design language of Jupyter – enabling developers to create extensions that have a consistent look and feel with the rest of the ecosystem. -- **Automatic support for color themes:** All components are designed with theming in mind and will automatically display the current application theme. -- **Use any tech stack:** The library ships as a set of web components, meaning developers can use the toolkit no matter what tech stack (React, Vue, Svelte, etc.) their extension is built with. -- **Accessible out of the box:** All components ship with web standard compliant ARIA labels and keyboard navigation. - -Note this project started as a fork of the [WebView toolkit for Visual Studio Code](https://github.com/microsoft/vscode-webview-ui-toolkit) (licensed under MIT) on which Jupyter design specification. The fundamental technology used is [Fast Design](https://www.fast.design/). +- **Implements the Jupyter design language:** All components follow the design language of Jupyter + – enabling developers to create extensions that have a consistent look and feel with the rest of + the ecosystem. +- **Automatic support for color themes:** All components are designed with theming in mind and will + automatically display the current application theme. +- **Use any tech stack:** The library ships as a set of web components, meaning developers can use + the toolkit no matter what tech stack (React, Vue, Svelte, etc.) their extension is built with. +- **Accessible out of the box:** All components ship with web standard compliant ARIA labels and + keyboard navigation. + +This repository contains three packages: + +- [`@jupyter-notebook/web-components`](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/packages/components/): + The main package defining the web components. +- [`@jupyter-notebook/react-components`](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/packages/react-components): + Wrapped the web components to use them with [React](https://reactjs.org). +- [`jupyter-ui-demo`](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/packages/lab-example): + Unpublished JupyterLab extension to demonstrate the integration of the toolkit. + +Those features are brought through the [Fast Design](https://www.fast.design/). And it is inspired +by the [WebView toolkit for Visual Studio Code](https://github.com/microsoft/vscode-webview-ui-toolkit) +as example for creating a customized toolkit. ## Release -The UI Toolkit is currently in a proof of concept. Track progress towards 1.0 [here](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.0). Styles and API -are not guarantee between minor versions prior to v1.0.0. +The UI Toolkit is currently in a proof of concept. Track progress towards 1.0 [here](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.0). +Styles and API are not guarantee between minor versions prior to v1.0.0. ## Getting started - ## Documentation Further documentation can be found in the following places: -- [Component Docs](./packages/components/docs/components.md) +- [Component Docs](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/packages/components/docs/components.md) - [Storybook (Interactive Component Sandbox)](https://jupyterlab-contrib.github.io/jupyter-ui-toolkit/) -- [Toolkit Extension Samples](./packages/lab-example) +- [Toolkit Extension Samples](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/packages/lab-example): + [Try online](https://mybinder.org/v2/gh/jupyterlab-contrib/jupyter-ui-toolkit/main) ## Contributing -See the [contributing](./CONTRIBUTING.md) documentation. +See the [contributing](https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/tree/main/CONTRIBUTING.md) documentation. diff --git a/packages/components/package.json b/packages/components/package.json index ce2f92f3..55d70e87 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -17,13 +17,13 @@ "sideEffects": false, "scripts": { "start": "start-storybook -p 6006", - "start:ci": "start-storybook -p 6006 --ci", + "start:ci": "start-storybook -p 6006 --ci --quiet", "build": "rollup -c && tsc -p ./tsconfig.json", "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/playwright.config.ts b/packages/components/playwright.config.ts index 9e81033e..b13ebba2 100644 --- a/packages/components/playwright.config.ts +++ b/packages/components/playwright.config.ts @@ -16,7 +16,7 @@ const config: PlaywrightTestConfig = { trace: 'on-first-retry', launchOptions: { // Force slow motion to let storybook the time to update styles - slowMo: 30 + slowMo: 40 } }, projects: [ diff --git a/packages/components/src/avatar/avatar.stories.ts b/packages/components/src/avatar/avatar.stories.ts new file mode 100644 index 00000000..2ebdf58b --- /dev/null +++ b/packages/components/src/avatar/avatar.stories.ts @@ -0,0 +1,75 @@ +// 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' } + }, + parameters: { + actions: { + disabled: true + } + }, + 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 new file mode 100644 index 00000000..d40f2d56 --- /dev/null +++ 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' + ); + }); +}); diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-chromium-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-chromium-linux.png new file mode 100644 index 00000000..1841e1b3 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-chromium-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-firefox-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-firefox-linux.png new file mode 100644 index 00000000..9eb7d399 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-firefox-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-webkit-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-webkit-linux.png new file mode 100644 index 00000000..fa4f458e Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-default-webkit-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-chromium-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-chromium-linux.png new file mode 100644 index 00000000..e0d51f09 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-chromium-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-firefox-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-firefox-linux.png new file mode 100644 index 00000000..177088c6 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-firefox-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-webkit-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-webkit-linux.png new file mode 100644 index 00000000..ba86a51c Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-square-webkit-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-chromium-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-chromium-linux.png new file mode 100644 index 00000000..344d42e0 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-chromium-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-firefox-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-firefox-linux.png new file mode 100644 index 00000000..0e079c63 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-firefox-linux.png differ diff --git a/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-webkit-linux.png b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-webkit-linux.png new file mode 100644 index 00000000..c1528382 Binary files /dev/null and b/packages/components/src/avatar/avatar.test.ts-snapshots/avatar-with-image-webkit-linux.png differ diff --git a/packages/components/src/avatar/index.ts b/packages/components/src/avatar/index.ts new file mode 100644 index 00000000..e375487b --- /dev/null +++ b/packages/components/src/avatar/index.ts @@ -0,0 +1,56 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + accentFillRest, + Avatar, + avatarStyles as styles, + foregroundOnAccentRest, + imgTemplate +} from '@microsoft/fast-components'; +import { css, ElementStyles } from '@microsoft/fast-element'; +import { + Avatar as FoundationAvatar, + AvatarOptions, + avatarTemplate as template, + FoundationElementTemplate +} from '@microsoft/fast-foundation'; + +export { Avatar } from '@microsoft/fast-components'; + +export const avatarStyles: FoundationElementTemplate< + ElementStyles, + AvatarOptions +> = (context, definition: AvatarOptions) => css` + ${styles(context, definition)} + + .backplate { + min-width: var(--avatar-size, var(--avatar-size-default)); + background-color: ${accentFillRest}; + } + + .link { + color: ${foregroundOnAccentRest}; + } +`; + +/** + * 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: avatarStyles, + media: imgTemplate, + shadowOptions: { + delegatesFocus: true + } +}); 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/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-chromium-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-chromium-linux.png new file mode 100644 index 00000000..19a8a455 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-firefox-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-firefox-linux.png new file mode 100644 index 00000000..7b3d52f4 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-webkit-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-webkit-linux.png new file mode 100644 index 00000000..1d2b47be Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-default-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-chromium-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-chromium-linux.png new file mode 100644 index 00000000..f61b9be7 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-firefox-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-firefox-linux.png new file mode 100644 index 00000000..901457d9 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-webkit-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-webkit-linux.png new file mode 100644 index 00000000..5b0bcbe8 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-end-icon-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-chromium-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-chromium-linux.png new file mode 100644 index 00000000..29b0d131 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-firefox-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-firefox-linux.png new file mode 100644 index 00000000..b4f22fe8 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-webkit-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-webkit-linux.png new file mode 100644 index 00000000..8fca53cd Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-with-start-icon-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-chromium-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-chromium-linux.png new file mode 100644 index 00000000..fbf2cf18 Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-firefox-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-firefox-linux.png new file mode 100644 index 00000000..3c98620f Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-webkit-linux.png b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-webkit-linux.png new file mode 100644 index 00000000..a5b76fef Binary files /dev/null and b/packages/components/src/breadcrumb-item/breadcrumb-item.test.ts-snapshots/breadcrumb-item-without-href-webkit-linux.png differ 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..ed21f785 --- /dev/null +++ b/packages/components/src/breadcrumb/breadcrumb.stories.ts @@ -0,0 +1,80 @@ +// 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' } + }, + parameters: { + actions: { + disabled: true + } + } +}; + +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') : ''} + ` + ) + .join('\n')} + `; +}; + +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/breadcrumb.test.ts-snapshots/breadcrumb-default-chromium-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-chromium-linux.png new file mode 100644 index 00000000..b8ee23e1 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-firefox-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-firefox-linux.png new file mode 100644 index 00000000..faae3ac2 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-webkit-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-webkit-linux.png new file mode 100644 index 00000000..a777ebf1 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-default-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-chromium-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-chromium-linux.png new file mode 100644 index 00000000..6444506f Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-firefox-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-firefox-linux.png new file mode 100644 index 00000000..9c7a7869 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-webkit-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-webkit-linux.png new file mode 100644 index 00000000..d2f02a9d Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-custom-children-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-chromium-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-chromium-linux.png new file mode 100644 index 00000000..f57010e5 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-firefox-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-firefox-linux.png new file mode 100644 index 00000000..b9b9f508 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-webkit-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-webkit-linux.png new file mode 100644 index 00000000..6e983338 Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-end-icon-webkit-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-chromium-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-chromium-linux.png new file mode 100644 index 00000000..e208920d Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-chromium-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-firefox-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-firefox-linux.png new file mode 100644 index 00000000..788894bc Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-firefox-linux.png differ diff --git a/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-webkit-linux.png b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-webkit-linux.png new file mode 100644 index 00000000..585bddaf Binary files /dev/null and b/packages/components/src/breadcrumb/breadcrumb.test.ts-snapshots/breadcrumb-with-start-icon-webkit-linux.png differ 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 56c1ff3a..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, @@ -8,16 +12,17 @@ import { density, designUnit, disabledOpacity, + focusStrokeWidth, foregroundOnAccentActive, foregroundOnAccentHover, foregroundOnAccentRest, neutralFillActive, - neutralFillFocus, neutralFillHover, neutralFillRest, neutralFillStealthActive, neutralFillStealthHover, neutralFillStealthRest, + neutralFillStrongFocus, neutralForegroundRest, strokeWidth, typeRampBaseFontSize, @@ -38,10 +43,7 @@ import { errorFillActive, errorFillFocus, errorFillHover, - errorFillRest, - foregroundOnErrorActive, - foregroundOnErrorHover, - foregroundOnErrorRest + errorFillRest } from '../design-token'; import { heightNumber } from '../styles'; @@ -58,8 +60,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 +92,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 +108,6 @@ const BaseButtonStyles = css` background-color: ${neutralFillHover}; } - :host(:hover) .control { - outline-color: ${neutralFillHover}; - } - :host(:active) { background-color: ${neutralFillActive}; } @@ -124,12 +120,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 +173,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 +194,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 +220,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 +242,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 +259,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 +280,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 +310,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 +327,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 +355,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 +377,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 +390,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 +421,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 +445,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; } 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..1f86eba1 100644 --- a/packages/components/src/custom-elements.ts +++ b/packages/components/src/custom-elements.ts @@ -2,21 +2,50 @@ // 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 { jpProgress } from './progress/index'; +import { jpSearch } from './search/index'; import { jpSelect } from './select/index'; +import { jpTabPanel } from './tab-panel/index'; +import { jpTab } from './tab/index'; +import { jpTabs } from './tabs/index'; 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 { Progress } from './progress/index'; +import type { Search } from './search/index'; import type { Select } from './select/index'; +import type { TabPanel } from './tab-panel/index'; +import type { Tab } from './tab/index'; +import type { Tabs } from './tabs/index'; import type { TextField } from './text-field/index'; // export all components -export { jpButton, jpOption, jpSelect, jpTextField }; +export { + jpAvatar, + jpBreadcrumb, + jpBreadcrumbItem, + jpButton, + jpOption, + jpProgress, + jpSearch, + jpSelect, + jpTab, + jpTabPanel, + jpTabs, + jpTextField +}; /** * All Jupyter Web Components @@ -26,9 +55,17 @@ export { jpButton, jpOption, jpSelect, jpTextField }; * statically link and register all available components. */ export const allComponents = { + jpAvatar, + jpBreadcrumb, + jpBreadcrumbItem, jpButton, jpOption, + jpProgress, + jpSearch, jpSelect, + jpTab, + jpTabPanel, + jpTabs, jpTextField, register(container?: Container, ...rest: any[]): void { if (!container) { diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index f3df3627..d1f03ec5 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -8,7 +8,18 @@ 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 './progress/index'; +export * from './search/index'; +export * from './select/index'; +export * from './tab-panel/index'; +export * from './tab/index'; +export * from './tabs/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/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..0846fd86 --- /dev/null +++ b/packages/components/src/progress/progress.stories.ts @@ -0,0 +1,65 @@ +// 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 } + }, + parameters: { + actions: { + disabled: true + } + }, + 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..2a14f844 --- /dev/null +++ b/packages/components/src/progress/progress.test.ts @@ -0,0 +1,20 @@ +// 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 ({ browserName, page }) => { + // FIXME + test.skip(browserName === 'webkit', 'Progress bar animation unstable.'); + + await page.goto('/iframe.html?id=progress--with-value'); + + // Ensure the progress animation is finished + await page.waitForTimeout(200); + + expect(await page.locator('jp-progress').screenshot()).toMatchSnapshot( + 'progress-with-value.png' + ); + }); +}); diff --git a/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-chromium-linux.png b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-chromium-linux.png new file mode 100644 index 00000000..d8de3c45 Binary files /dev/null and b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-chromium-linux.png differ diff --git a/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-firefox-linux.png b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-firefox-linux.png new file mode 100644 index 00000000..eaa5ca13 Binary files /dev/null and b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-firefox-linux.png differ diff --git a/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-webkit-linux.png b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-webkit-linux.png new file mode 100644 index 00000000..137d4bdb Binary files /dev/null and b/packages/components/src/progress/progress.test.ts-snapshots/progress-with-value-webkit-linux.png differ 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..b9f89ea1 --- /dev/null +++ b/packages/components/src/search/search.styles.ts @@ -0,0 +1,238 @@ +// 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, + focusStrokeWidth, + 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}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) ${accentFillFocus} inset; + } + + .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/search/search.test.ts-snapshots/search-default-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-default-chromium-linux.png new file mode 100644 index 00000000..850c8407 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-default-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-default-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-default-firefox-linux.png new file mode 100644 index 00000000..973ca4c6 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-default-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-default-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-default-webkit-linux.png new file mode 100644 index 00000000..5d777749 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-default-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-chromium-linux.png new file mode 100644 index 00000000..474471e8 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-firefox-linux.png new file mode 100644 index 00000000..5e1974eb Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-webkit-linux.png new file mode 100644 index 00000000..f94a4bee Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-autofocus-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-chromium-linux.png new file mode 100644 index 00000000..2e0f9803 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-firefox-linux.png new file mode 100644 index 00000000..f798817f Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-webkit-linux.png new file mode 100644 index 00000000..59594384 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-disabled-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-chromium-linux.png new file mode 100644 index 00000000..3fa52009 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-firefox-linux.png new file mode 100644 index 00000000..1da73473 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-webkit-linux.png new file mode 100644 index 00000000..57ec22d8 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-maxlength-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-chromium-linux.png new file mode 100644 index 00000000..b48a31b1 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-firefox-linux.png new file mode 100644 index 00000000..4f1b87e3 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-webkit-linux.png new file mode 100644 index 00000000..517a7080 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-placeholder-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-chromium-linux.png new file mode 100644 index 00000000..e10a78bd Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-firefox-linux.png new file mode 100644 index 00000000..6879ffcb Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-webkit-linux.png new file mode 100644 index 00000000..aea1f6f9 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-search-icon-webkit-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-size-chromium-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-size-chromium-linux.png new file mode 100644 index 00000000..9d6eccd1 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-size-chromium-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-size-firefox-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-size-firefox-linux.png new file mode 100644 index 00000000..4793116f Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-size-firefox-linux.png differ diff --git a/packages/components/src/search/search.test.ts-snapshots/search-with-size-webkit-linux.png b/packages/components/src/search/search.test.ts-snapshots/search-with-size-webkit-linux.png new file mode 100644 index 00000000..a12731f4 Binary files /dev/null and b/packages/components/src/search/search.test.ts-snapshots/search-with-size-webkit-linux.png differ 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.styles.ts b/packages/components/src/select/select.styles.ts index 81ca1d11..032b66ed 100644 --- a/packages/components/src/select/select.styles.ts +++ b/packages/components/src/select/select.styles.ts @@ -15,6 +15,8 @@ import { neutralFillInputHover, neutralFillInputRest, neutralFillStealthRest, + neutralFillStrongHover, + neutralFillStrongRest, neutralForegroundRest, neutralLayerFloating, neutralStrokeRest, @@ -50,7 +52,7 @@ export const selectStyles: FoundationElementTemplate< --elevation: 14; background: ${neutralFillInputRest}; border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; + border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; box-sizing: border-box; color: ${neutralForegroundRest}; font-family: ${bodyFont}; @@ -103,11 +105,12 @@ export const selectStyles: FoundationElementTemplate< :host(:not([disabled]):hover) { background: ${neutralFillInputHover}; - border-color: ${accentFillHover}; + border-color: ${neutralFillStrongHover}; } :host(:${focusVisible}) { border-color: ${accentFillFocus}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) ${accentFillFocus} inset; } :host([disabled]) { 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/tab-panel/index.ts b/packages/components/src/tab-panel/index.ts new file mode 100644 index 00000000..a476ba34 --- /dev/null +++ b/packages/components/src/tab-panel/index.ts @@ -0,0 +1,31 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + TabPanel, + tabPanelTemplate as template +} from '@microsoft/fast-foundation'; +import { tabPanelStyles as styles } from '@microsoft/fast-components'; + +/** + * A function that returns a {@link @microsoft/fast-foundation#TabPanel} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#tabPanelTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpTabPanel = TabPanel.compose({ + baseName: 'tab-panel', + template, + styles +}); + +/** + * Base class for TabPanel + * @public + */ +export { TabPanel }; + +export { styles as tabPanelStyles }; diff --git a/packages/components/src/tab/index.ts b/packages/components/src/tab/index.ts new file mode 100644 index 00000000..c946ab05 --- /dev/null +++ b/packages/components/src/tab/index.ts @@ -0,0 +1,28 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { Tab, tabTemplate as template } from '@microsoft/fast-foundation'; +import { tabStyles as styles } from './tab.styles'; + +/** + * A function that returns a {@link @microsoft/fast-foundation#Tab} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#tabTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpTab = Tab.compose({ + baseName: 'tab', + template, + styles +}); + +/** + * Base class for Tab + * @public + */ +export { Tab }; + +export { styles as tabStyles }; diff --git a/packages/components/src/tab/tab.styles.ts b/packages/components/src/tab/tab.styles.ts new file mode 100644 index 00000000..34721de3 --- /dev/null +++ b/packages/components/src/tab/tab.styles.ts @@ -0,0 +1,160 @@ +// Copyright (c) Jupyter Development Team. +// Copyright (c) Microsoft Corporation. +// Distributed under the terms of the Modified BSD License. + +import { + accentFillFocus, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + neutralFillActive, + neutralFillHover, + neutralFillRest, + neutralFillStealthRest, + neutralForegroundHint, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight +} from '@microsoft/fast-components'; +import { css, ElementStyles } from '@microsoft/fast-element'; +import { + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; +import { heightNumber } from '../styles'; + +/** + * Styles for Tab + * @public + */ +export const tabStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display('inline-flex')} :host { + box-sizing: border-box; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + height: calc(${heightNumber} * 1px); + padding: calc(${designUnit} * 5px) calc(${designUnit} * 4px); + color: ${neutralForegroundHint}; + fill: currentcolor; + border-radius: 0 0 calc(${controlCornerRadius} * 1px) + calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid transparent; + align-items: center; + grid-row: 2; + justify-content: center; + cursor: pointer; + } + + :host(:hover) { + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host(:active) { + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host([disabled]) { + cursor: ${disabledCursor}; + opacity: ${disabledOpacity}; + } + + :host([disabled]:hover) { + color: ${neutralForegroundHint}; + background: ${neutralFillStealthRest}; + } + + :host([aria-selected='true']) { + background: ${neutralFillRest}; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host([aria-selected='true']:hover) { + background: ${neutralFillHover}; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host([aria-selected='true']:active) { + background: ${neutralFillActive}; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host(:${focusVisible}) { + outline: none; + border: calc(${strokeWidth} * 1px) solid ${accentFillFocus}; + } + + :host(:focus) { + outline: none; + } + + :host(.vertical) { + justify-content: end; + grid-column: 2; + border-bottom-left-radius: 0; + border-top-right-radius: calc(${controlCornerRadius} * 1px); + } + + :host(.vertical[aria-selected='true']) { + z-index: 2; + } + + :host(.vertical:hover) { + color: ${neutralForegroundRest}; + } + + :host(.vertical:active) { + color: ${neutralForegroundRest}; + } + + :host(.vertical:hover[aria-selected='true']) { + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + forced-color-adjust: none; + border-color: transparent; + color: ${SystemColors.ButtonText}; + fill: currentcolor; + } + :host(:hover), + :host(.vertical:hover), + :host([aria-selected='true']:hover) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + fill: currentcolor; + } + :host([aria-selected='true']) { + background: ${SystemColors.HighlightText}; + color: ${SystemColors.Highlight}; + fill: currentcolor; + } + :host(:${focusVisible}) { + border-color: ${SystemColors.ButtonText}; + box-shadow: none; + } + :host([disabled]), + :host([disabled]:hover) { + opacity: 1; + color: ${SystemColors.GrayText}; + background: ${SystemColors.ButtonFace}; + } + ` + ) + ); diff --git a/packages/components/src/tabs/index.ts b/packages/components/src/tabs/index.ts new file mode 100644 index 00000000..50322c2b --- /dev/null +++ b/packages/components/src/tabs/index.ts @@ -0,0 +1,31 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { Tabs, tabsTemplate as template } from '@microsoft/fast-foundation'; +import { tabsStyles as styles } from './tabs.styles'; + +/** + * A function that returns a {@link @microsoft/fast-foundation#Tabs} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#tabsTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const jpTabs = Tabs.compose({ + baseName: 'tabs', + template, + styles +}); + +export * from '../tab'; +export * from '../tab-panel'; + +/** + * Base class for Tabs + * @public + */ +export { Tabs }; + +export { styles as tabsStyles }; diff --git a/packages/components/src/tabs/tabs.stories.ts b/packages/components/src/tabs/tabs.stories.ts new file mode 100644 index 00000000..973c004f --- /dev/null +++ b/packages/components/src/tabs/tabs.stories.ts @@ -0,0 +1,63 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { setTheme } from '../utilities/storybook'; + +export default { + title: 'Tabs', + argTypes: { + activePanel: { control: 'select', options: [null, 'One', 'Two', 'Three'] }, + activeIndicator: { control: 'boolean' }, + orientation: { control: 'radio', options: ['horizontal', 'vertical'] } + }, + parameters: { + actions: { + disabled: true + } + } +}; + +const Template = ( + args, + { globals: { backgrounds, accent }, parameters } +): string => { + setTheme(accent, parameters.backgrounds, backgrounds); + + return ` + Tab one + Tab two + Tab three + + Tab one content. This is for testing. + + + Tab two content. This is for testing. + + + Tab three content. This is for testing. + + `; +}; + +export const Default = Template.bind({}); +Default.args = { + activePanel: null, + activeIndicator: true, + orientation: 'horizontal' +}; + +export const Vertical = Template.bind({}); +Vertical.args = { + ...Default.args, + orientation: 'vertical' +}; + +export const WithoutIndicator = Template.bind({}); +WithoutIndicator.args = { + ...Default.args, + activeIndicator: false +}; diff --git a/packages/components/src/tabs/tabs.styles.ts b/packages/components/src/tabs/tabs.styles.ts new file mode 100644 index 00000000..013363db --- /dev/null +++ b/packages/components/src/tabs/tabs.styles.ts @@ -0,0 +1,132 @@ +import { css, ElementStyles } from '@microsoft/fast-element'; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + TabsOptions +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; +import { + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight +} from '@microsoft/fast-components'; +import { heightNumber } from '../styles/index'; + +/** + * Styles for Tabs + * @public + */ +export const tabsStyles: FoundationElementTemplate< + ElementStyles, + TabsOptions +> = (context, definition) => + css` + ${display('grid')} :host { + box-sizing: border-box; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + color: ${neutralForegroundRest}; + grid-template-columns: auto 1fr auto; + grid-template-rows: auto 1fr; + } + + .tablist { + display: grid; + grid-template-rows: auto auto; + grid-template-columns: auto; + position: relative; + width: max-content; + align-self: end; + padding: calc(${designUnit} * 4px) calc(${designUnit} * 4px) 0; + box-sizing: border-box; + } + + .start, + .end { + align-self: center; + } + + .activeIndicator { + grid-row: 1; + grid-column: 1; + width: 100%; + height: 4px; + justify-self: center; + background: ${accentFillRest}; + margin-top: 0; + border-radius: calc(${controlCornerRadius} * 1px) + calc(${controlCornerRadius} * 1px) 0 0; + } + + .activeIndicatorTransition { + transition: transform 0.01s ease-in-out; + } + + .tabpanel { + grid-row: 2; + grid-column-start: 1; + grid-column-end: 4; + position: relative; + } + + :host([orientation='vertical']) { + grid-template-rows: auto 1fr auto; + grid-template-columns: auto 1fr; + } + + :host([orientation='vertical']) .tablist { + grid-row-start: 2; + grid-row-end: 2; + display: grid; + grid-template-rows: auto; + grid-template-columns: auto 1fr; + position: relative; + width: max-content; + justify-self: end; + align-self: flex-start; + width: 100%; + padding: 0 calc(${designUnit} * 4px) + calc((${heightNumber} - ${designUnit}) * 1px) 0; + } + + :host([orientation='vertical']) .tabpanel { + grid-column: 2; + grid-row-start: 1; + grid-row-end: 4; + } + + :host([orientation='vertical']) .end { + grid-row: 3; + } + + :host([orientation='vertical']) .activeIndicator { + grid-column: 1; + grid-row: 1; + width: 4px; + height: 100%; + margin-inline-end: 0px; + align-self: center; + border-radius: calc(${controlCornerRadius} * 1px) 0 0 + calc(${controlCornerRadius} * 1px); + } + + :host([orientation='vertical']) .activeIndicatorTransition { + transition: transform 0.01s ease-in-out; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .activeIndicator, + :host([orientation='vertical']) .activeIndicator { + forced-color-adjust: none; + background: ${SystemColors.Highlight}; + } + ` + ) + ); diff --git a/packages/components/src/tabs/tabs.test.ts b/packages/components/src/tabs/tabs.test.ts new file mode 100644 index 00000000..61791b57 --- /dev/null +++ b/packages/components/src/tabs/tabs.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('Tabs', () => { + test('Default', async ({ page }) => { + await page.goto('/iframe.html?id=tabs--default'); + + expect(await page.locator('jp-tabs').screenshot()).toMatchSnapshot( + 'tabs-default.png' + ); + }); + + test('Vertical', async ({ page }) => { + await page.goto('/iframe.html?id=tabs--vertical'); + + expect(await page.locator('jp-tabs').screenshot()).toMatchSnapshot( + 'tabs-vertical.png' + ); + }); + + test('Without Indicator', async ({ page }) => { + await page.goto('/iframe.html?id=tabs--without-indicator'); + + expect(await page.locator('jp-tabs').screenshot()).toMatchSnapshot( + 'tabs-without-indicator.png' + ); + }); +}); diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-chromium-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-chromium-linux.png new file mode 100644 index 00000000..9eae0a0a Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-chromium-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-firefox-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-firefox-linux.png new file mode 100644 index 00000000..83341680 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-firefox-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-webkit-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-webkit-linux.png new file mode 100644 index 00000000..34f8ee60 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-default-webkit-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-chromium-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-chromium-linux.png new file mode 100644 index 00000000..8742d4d2 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-chromium-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-firefox-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-firefox-linux.png new file mode 100644 index 00000000..8542ad46 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-firefox-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-webkit-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-webkit-linux.png new file mode 100644 index 00000000..6ff344e7 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-vertical-webkit-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-chromium-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-chromium-linux.png new file mode 100644 index 00000000..019661c0 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-chromium-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-firefox-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-firefox-linux.png new file mode 100644 index 00000000..18ab6e17 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-firefox-linux.png differ diff --git a/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-webkit-linux.png b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-webkit-linux.png new file mode 100644 index 00000000..d852c5d1 Binary files /dev/null and b/packages/components/src/tabs/tabs.test.ts-snapshots/tabs-without-indicator-webkit-linux.png differ 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 18f15bf9..fa4bf3fb 100644 --- a/packages/components/src/text-field/text-field.stories.ts +++ b/packages/components/src/text-field/text-field.stories.ts @@ -1,9 +1,12 @@ +// 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'; 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.styles.ts b/packages/components/src/text-field/text-field.styles.ts index d67e37a0..407b425d 100644 --- a/packages/components/src/text-field/text-field.styles.ts +++ b/packages/components/src/text-field/text-field.styles.ts @@ -3,17 +3,19 @@ // Distributed under the terms of the Modified BSD License. import { - accentFillActive, accentFillFocus, - accentFillHover, bodyFont, controlCornerRadius, designUnit, disabledOpacity, + focusStrokeWidth, neutralFillHover, neutralFillInputHover, neutralFillInputRest, neutralFillRest, + neutralFillStrongActive, + neutralFillStrongHover, + neutralFillStrongRest, neutralForegroundRest, neutralStrokeRest, strokeWidth, @@ -24,9 +26,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 +38,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 +57,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,16 +121,17 @@ 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: ${neutralFillStrongActive}; } :host(:focus-within:not([disabled])) .root { border-color: ${accentFillFocus}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) ${accentFillFocus} inset; } :host([appearance='filled']) .root { 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' diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-default-firefox-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-default-firefox-linux.png index f15e1e4b..8c0d7cd1 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-default-firefox-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-default-firefox-linux.png differ diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-chromium-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-chromium-linux.png index 9fc56675..d9fc92e5 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-chromium-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-chromium-linux.png differ diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-firefox-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-firefox-linux.png index 953a0c7b..a2ca6f5f 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-firefox-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-firefox-linux.png differ diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-webkit-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-webkit-linux.png index 031607f2..ff6aab87 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-webkit-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-autofocus-webkit-linux.png differ diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-placeholder-firefox-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-placeholder-firefox-linux.png index 5687d0c4..108f1216 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-placeholder-firefox-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-placeholder-firefox-linux.png differ diff --git a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-size-firefox-linux.png b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-size-firefox-linux.png index a38c3e3b..c8fdc3a2 100644 Binary files a/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-size-firefox-linux.png and b/packages/components/src/text-field/text-field.test.ts-snapshots/text-field-with-size-firefox-linux.png differ diff --git a/packages/lab-example/src/index.tsx b/packages/lab-example/src/index.tsx index d7f18e1d..7507c482 100644 --- a/packages/lab-example/src/index.tsx +++ b/packages/lab-example/src/index.tsx @@ -1,7 +1,13 @@ import { + Avatar, Button, Option, + Progress, + Search, Select, + Tab, + TabPanel, + Tabs, TextField } from '@jupyter-notebook/react-components'; import { @@ -25,7 +31,7 @@ const plugin: JupyterFrontEndPlugin = { id: 'jupyter-ui-demo:plugin', optional: [IThemeManager], autoStart: true, - activate: (app: JupyterFrontEnd, theme: IThemeManager | null) => { + activate: (app: JupyterFrontEnd) => { console.log('JupyterLab extension jupyter-ui-demo is activated!'); const widget = new Widget({ node: createNode() }); @@ -33,13 +39,23 @@ const plugin: JupyterFrontEndPlugin = { widget.id = 'artwork-ui-components'; widget.title.label = 'Toolkit Gallery'; - // Add listener - const firstButton = widget.node.querySelector('jp-button'); - if (firstButton) { - firstButton.addEventListener('click', () => { + // Add listeners + const buttons = widget.node.querySelectorAll('jp-button'); + buttons.forEach(button => { + button.addEventListener('click', () => { alert('Reacting to vanilla click event.'); }); - } + }); + const search = widget.node.querySelector('jp-search'); + search?.addEventListener('change', event => { + console.log(event); + alert(`Search change event: ${(event as any).target.value}`); + }); + const textField = widget.node.querySelector('jp-text-field'); + textField?.addEventListener('change', event => { + console.log(event); + alert(`Text field change event: ${(event as any).target.value}`); + }); const reactWidget = ReactWidget.create(); reactWidget.addClass('jp-Artwork'); @@ -76,22 +92,33 @@ const plugin: JupyterFrontEndPlugin = { }; function Artwork(): JSX.Element { + const onChange = (event: any) => { + alert(`React on React change event: ${event?.target?.value}`); + }; const onClick = () => { alert('Reacting on React click event.'); }; + return ( -
-
+
+
- - +
- Text Field Label + + Search Label + + + Text Field Label +
Link Text */}
+
+ JS +
+ + + +
+
+
+ + One + Two + Three + This is panel one content. + This is panel two content. + This is panel three content. + +
{/*
@@ -134,13 +179,14 @@ function createNode(): HTMLElement { node.insertAdjacentHTML( 'afterbegin', ` -