diff --git a/LICENSE b/LICENSE index ed61467d..4643ba48 100644 --- a/LICENSE +++ b/LICENSE @@ -25,3 +25,31 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +This project code source is modified from `@microsoft/fast-components` licensed under + +FAST - https://www.fast.design/ + +MIT License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE diff --git a/packages/components/karma.conf.cjs b/packages/components/karma.conf.cjs new file mode 100644 index 00000000..7b222476 --- /dev/null +++ b/packages/components/karma.conf.cjs @@ -0,0 +1,152 @@ +const path = require("path"); + +const basePath = path.resolve(__dirname); + +const commonChromeFlags = [ + "--no-default-browser-check", + "--no-first-run", + "--no-sandbox", + "--no-managed-user-acknowledgment-check", + "--disable-background-timer-throttling", + "--disable-backing-store-limit", + "--disable-boot-animation", + "--disable-cloud-import", + "--disable-contextual-search", + "--disable-default-apps", + "--disable-extensions", + "--disable-infobars", + "--disable-translate", +]; + +module.exports = function (config) { + let browsers; + if (process.env.BROWSERS) { + browsers = [process.env.BROWSERS]; + } else if (config.browsers) { + browsers = config.browsers; + } else { + browsers = ["Chrome"]; + } + + const setup = "setup-browser" + (config.package ? "-" + config.package : ""); + const options = { + basePath, + browserDisconnectTimeout: 10000, + processKillTimeout: 10000, + frameworks: ["source-map-support", "mocha"], + plugins: [ + require("karma-mocha"), + require("karma-mocha-reporter"), + require("karma-webpack"), + require("karma-source-map-support"), + require("karma-sourcemap-loader"), + require("karma-coverage-istanbul-reporter"), + require("karma-chrome-launcher"), + require("karma-firefox-launcher"), + ], + files: [`dist/esm/__test__/${setup}.js`], + preprocessors: { + [`dist/esm/__test__/${setup}.js`]: ["webpack", "sourcemap"], + }, + webpackMiddleware: { + // webpack-dev-middleware configuration + // i. e. + stats: "errors-only", + }, + webpack: { + mode: "none", + resolve: { + extensions: [".js"], + modules: ["dist", "node_modules"], + mainFields: ["module", "main"], + }, + devtool: "inline-source-map", + performance: { + hints: false, + }, + optimization: { + namedModules: false, + namedChunks: false, + nodeEnv: false, + usedExports: true, + flagIncludedChunks: false, + occurrenceOrder: false, + sideEffects: true, + concatenateModules: true, + splitChunks: { + name: false, + }, + runtimeChunk: false, + noEmitOnErrors: false, + checkWasmTypes: false, + minimize: false, + }, + module: { + rules: [ + { + test: /\.js\.map$/, + use: ["ignore-loader"], + }, + { + test: /\.js$/, + use: [ + { + loader: "source-map-loader", + options: { + enforce: "pre", + }, + }, + ], + }, + ], + }, + }, + mime: { + "text/x-typescript": ["ts"], + }, + reporters: [config.reporter || (process.env.CI ? "min" : "progress")], + browsers: browsers, + customLaunchers: { + ChromeDebugging: { + base: "Chrome", + flags: [...commonChromeFlags, "--remote-debugging-port=9333"], + debug: true, + }, + ChromeHeadlessOpt: { + base: "ChromeHeadless", + flags: [...commonChromeFlags], + }, + }, + client: { + captureConsole: true, + mocha: { + bail: config["bail"], + ui: "bdd", + timeout: 5000, + }, + }, + logLevel: config.LOG_ERROR, // to disable the WARN 404 for image requests + }; + + if (config.coverage) { + options.webpack.module.rules.push({ + enforce: "post", + exclude: /(__tests__|testing|node_modules|\.spec\.[tj]s$)/, + loader: "istanbul-instrumenter-loader", + options: { esModules: true }, + test: /\.[tj]s$/, + }); + options.reporters = ["coverage-istanbul", ...options.reporters]; + options.coverageIstanbulReporter = { + reports: ["html", "text-summary", "json", "lcovonly", "cobertura"], + dir: "coverage", + }; + options.junitReporter = { + outputDir: "coverage", + outputFile: "test-results.xml", + useBrowserName: false, + }; + } + + config.set(options); +}; \ No newline at end of file diff --git a/packages/components/package.json b/packages/components/package.json index 44d240c1..622fafc1 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -21,6 +21,7 @@ "main": "dist/esm/index.js", "types": "dist/dts/index.d.ts", "sideEffects": false, + "type": "module", "scripts": { "start": "storybook dev -p 6006", "start:ci": "storybook dev -p 6006 --ci --quiet", @@ -41,7 +42,6 @@ }, "dependencies": { "@microsoft/fast-colors": "^5.3.1", - "@microsoft/fast-components": "^2.30.6", "@microsoft/fast-element": "^1.12.0", "@microsoft/fast-foundation": "^2.49.4", "@microsoft/fast-web-utilities": "^5.4.1" @@ -55,7 +55,6 @@ "@playwright/test": "^1.35.1", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-node-resolve": "^11.2.0", - "@rollup/plugin-typescript": "^8.2.0", "@storybook/addon-a11y": "^7.5.3", "@storybook/addon-actions": "^7.5.3", "@storybook/addon-docs": "^7.5.3", @@ -66,7 +65,13 @@ "@storybook/html": "^7.5.3", "@storybook/html-webpack5": "^7.5.3", "@storybook/theming": "^7.5.3", + "@types/chai": "^4.2.11", + "@types/chai-spies": "^1.0.1", "@types/jest": "^29.0.0", + "@types/karma": "^5.0.0", + "@types/mocha": "^8.2.0", + "@types/node": "^18.0.0", + "@types/webpack-env": "^1.15.2", "@typescript-eslint/eslint-plugin": "^5.60.1", "eslint": "^8.43.0", "eslint-config-prettier": "^8.8.0", @@ -84,6 +89,7 @@ "rollup-plugin-filesize": "^9.1.1", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-transform-tagged-template": "0.0.3", + "rollup-plugin-typescript2": "^0.27.0", "storybook": "^7.5.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", diff --git a/packages/components/rollup.config.js b/packages/components/rollup.config.js index 5c816111..809b39b1 100644 --- a/packages/components/rollup.config.js +++ b/packages/components/rollup.config.js @@ -3,7 +3,7 @@ import commonjs from '@rollup/plugin-commonjs'; import filesize from 'rollup-plugin-filesize'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import transformTaggedTemplate from 'rollup-plugin-transform-tagged-template'; -import typescript from '@rollup/plugin-typescript'; +import typescript from "rollup-plugin-typescript2"; import { terser } from 'rollup-plugin-terser'; import del from 'rollup-plugin-delete'; @@ -17,6 +17,15 @@ export default [ { context: 'this', input: 'src/index-rollup.ts', + onwarn(warning, warn) { + // The IIFE export doesn't have a namespace since component exports + // are expected to be top-level objects + if (warning.code === "MISSING_NAME_OPTION_FOR_IIFE_EXPORT") { + return; + } + + warn(warning); + }, output: [ { file: 'dist/toolkit.js', diff --git a/packages/components/src/__test__/setup-browser.ts b/packages/components/src/__test__/setup-browser.ts new file mode 100644 index 00000000..55b02eb6 --- /dev/null +++ b/packages/components/src/__test__/setup-browser.ts @@ -0,0 +1,6 @@ +function importAll(r: __WebpackModuleApi.RequireContext): void { + r.keys().forEach(r); +} + +// Explicitly add to browser test +importAll(require.context("../", true, /\.spec\.js$/)); diff --git a/packages/components/src/__test__/setup-node.ts b/packages/components/src/__test__/setup-node.ts new file mode 100644 index 00000000..be4102c4 --- /dev/null +++ b/packages/components/src/__test__/setup-node.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +if (window.document && !window.document.createRange) { + window.document.createRange = () => ({ + setStart: () => {}, + setEnd: () => {}, + // @ts-ignore + commonAncestorContainer: { + nodeName: "BODY", + ownerDocument: document, + }, + }); +} diff --git a/packages/components/src/accordion-item/index.ts b/packages/components/src/accordion-item/index.ts index c79daf53..2eedea42 100644 --- a/packages/components/src/accordion-item/index.ts +++ b/packages/components/src/accordion-item/index.ts @@ -2,9 +2,9 @@ // Distributed under the terms of the Modified BSD License. import { - AccordionItem, - AccordionItemOptions, - accordionItemTemplate as template + AccordionItem, + AccordionItemOptions, + accordionItemTemplate as template } from '@microsoft/fast-foundation'; import { accordionItemStyles as styles } from './accordion-item.styles'; @@ -18,39 +18,37 @@ import { accordionItemStyles as styles } from './accordion-item.styles'; * Generates HTML Element: `` */ export const jpAccordionItem = AccordionItem.compose({ - baseName: 'accordion-item', - template, - styles, - collapsedIcon: /* html */ ` - - - + baseName: 'accordion-item', + template, + styles, + collapsedIcon: /* html */ ` + + + `, - expandedIcon: /* html */ ` - - + - + /> + ` }); diff --git a/packages/components/src/accordion/accordion.stories.ts b/packages/components/src/accordion/accordion.stories.ts index d70638f5..00573e8a 100644 --- a/packages/components/src/accordion/accordion.stories.ts +++ b/packages/components/src/accordion/accordion.stories.ts @@ -4,7 +4,7 @@ import type { HtmlRenderer, Meta, StoryObj, StoryFn } from '@storybook/html'; import { setTheme } from '../utilities/storybook'; export default { - title: 'Components/Accordion', + title: 'Components/Accordion', parameters: { controls: { diff --git a/packages/components/src/accordion/accordion.styles.ts b/packages/components/src/accordion/accordion.styles.ts new file mode 100644 index 00000000..fb9e3acf --- /dev/null +++ b/packages/components/src/accordion/accordion.styles.ts @@ -0,0 +1,30 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + bodyFont, + neutralForegroundRest, + neutralStrokeDividerRest, + strokeWidth, + typeRampMinus1FontSize, + typeRampMinus1LineHeight, +} from "../design-tokens.js"; + +/** + * Styles for Accordion + * @public + */ +export const accordionStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("flex")} :host { + box-sizing: border-box; + flex-direction: column; + font-family: ${bodyFont}; + font-size: ${typeRampMinus1FontSize}; + line-height: ${typeRampMinus1LineHeight}; + color: ${neutralForegroundRest}; + border-top: calc(${strokeWidth} * 1px) solid ${neutralStrokeDividerRest}; + } + `; diff --git a/packages/components/src/accordion/index.ts b/packages/components/src/accordion/index.ts index f4f07958..6447132c 100644 --- a/packages/components/src/accordion/index.ts +++ b/packages/components/src/accordion/index.ts @@ -1,13 +1,7 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. +import { Accordion, accordionTemplate as template } from "@microsoft/fast-foundation"; +import { accordionStyles as styles } from "./accordion.styles.js"; -import { - Accordion, - accordionTemplate as template -} from '@microsoft/fast-foundation'; -import { accordionStyles as styles } from '@microsoft/fast-components'; - -export * from '../accordion-item/index'; +export * from "../accordion-item/index.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Accordion} registration for configuring the component with a DesignSystem. @@ -19,9 +13,9 @@ export * from '../accordion-item/index'; * Generates HTML Element: `` */ export const jpAccordion = Accordion.compose({ - baseName: 'accordion', - template, - styles + baseName: 'accordion', + template, + styles }); /** diff --git a/packages/components/src/anchor/anchor.pw.spec.ts b/packages/components/src/anchor/anchor.pw.spec.ts new file mode 100644 index 00000000..61fa6d22 --- /dev/null +++ b/packages/components/src/anchor/anchor.pw.spec.ts @@ -0,0 +1,53 @@ +import type { + Anchor as jpAnchorType +} from "@microsoft/fast-foundation"; +import chai from "chai"; + +const { expect } = chai; + +type jpAnchor = HTMLElement & jpAnchorType; + +describe("jpAnchor", function () { + beforeEach(async function () { + if (!this.page && !this.browser) { + this.skip(); + } + + this.documentHandle = await this.page.evaluateHandle(() => document); + + this.setupHandle = await this.page.evaluateHandle( + (document) => { + const element = document.createElement("jp-anchor") as jpAnchor; + element.href = "#"; + element.textContent = "Hello"; + element.id = "anchor1"; + + document.body.appendChild(element) + }, + this.documentHandle + ); + }); + + afterEach(async function () { + if (this.setupHandle) { + await this.setupHandle.dispose(); + } + }); + + // jpAnchor should render on the page + it("should render on the page", async function () { + const element = await this.page.waitForSelector("jp-anchor"); + + expect(element).to.exist; + }); + + it("receive focus when focused programatically", async function () { + const element = await this.page.waitForSelector("jp-anchor"); + + await this.page.evaluateHandle(element => element.focus(), element) + + expect(await this.page.evaluate( + () => document.activeElement?.id + )).to.equal(await element.evaluate(node => node.id)); + }); +}); diff --git a/packages/components/src/anchor/anchor.stories.ts b/packages/components/src/anchor/anchor.stories.ts index ae88fe5f..6b671988 100644 --- a/packages/components/src/anchor/anchor.stories.ts +++ b/packages/components/src/anchor/anchor.stories.ts @@ -4,7 +4,7 @@ import type { StoryFn, Meta, StoryObj } from '@storybook/html'; import { getFaIcon, setTheme } from '../utilities/storybook'; export default { - title: 'Components/Anchor', + title: 'Components/Anchor', argTypes: { label: { control: 'text' }, appearance: { diff --git a/packages/components/src/anchor/anchor.styles.ts b/packages/components/src/anchor/anchor.styles.ts new file mode 100644 index 00000000..017c1c8a --- /dev/null +++ b/packages/components/src/anchor/anchor.styles.ts @@ -0,0 +1,29 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { AnchorOptions, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + AccentButtonStyles, + BaseButtonStyles, + HypertextStyles, + LightweightButtonStyles, + OutlineButtonStyles, + StealthButtonStyles, +} from "../styles/index.js"; +import { appearanceBehavior } from "../utilities/behaviors.js"; + +/** + * Styles for Anchor + * @public + */ +export const anchorStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${BaseButtonStyles} + `.withBehaviors( + appearanceBehavior("accent", AccentButtonStyles), + appearanceBehavior("hypertext", HypertextStyles), + appearanceBehavior("lightweight", LightweightButtonStyles), + appearanceBehavior("outline", OutlineButtonStyles), + appearanceBehavior("stealth", StealthButtonStyles) + ); diff --git a/packages/components/src/anchor/index.ts b/packages/components/src/anchor/index.ts index c02e29df..155384fb 100644 --- a/packages/components/src/anchor/index.ts +++ b/packages/components/src/anchor/index.ts @@ -1,31 +1,86 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { attr } from "@microsoft/fast-element"; import { - Anchor as FoundationAnchor, - anchorTemplate as template -} from '@microsoft/fast-foundation'; -import { Anchor, anchorStyles as styles } from '@microsoft/fast-components'; + Anchor as FoundationAnchor, + anchorTemplate as template, +} from "@microsoft/fast-foundation"; +import { ButtonAppearance } from "../button/index.js"; +import { anchorStyles as styles } from "./anchor.styles.js"; + +/** + * Types of anchor appearance. + * @public + */ +export type AnchorAppearance = ButtonAppearance | "hypertext"; + +/** + * Base class for Anchor + * @public + */ +export class Anchor extends FoundationAnchor { + /** + * The appearance the anchor should have. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance?: AnchorAppearance; + public appearanceChanged( + oldValue: AnchorAppearance, + newValue: AnchorAppearance + ): void { + if (this.$fastController.isConnected) { + this.classList.remove(oldValue); + this.classList.add(newValue); + } + } + + public connectedCallback() { + super.connectedCallback(); + + if (!this.appearance) { + this.appearance = "neutral"; + } + } + + /** + * Applies 'icon-only' class when there is only an SVG in the default slot + * + * @internal + * + */ + public defaultSlottedContentChanged(oldValue: any, newValue: any): void { + const slottedElements = this.defaultSlottedContent.filter( + x => x.nodeType === Node.ELEMENT_NODE + ); + if (slottedElements.length === 1 && slottedElements[0] instanceof SVGElement) { + this.control!.classList.add("icon-only"); + } else { + this.control!.classList.remove("icon-only"); + } + } +} /** - * A function that returns a Anchor registration for configuration with a DesignSystem. + * A function that returns a {@link @microsoft/fast-foundation#Anchor} registration for configuring the component with a DesignSystem. * Implements {@link @microsoft/fast-foundation#anchorTemplate} * + * * @public * @remarks * Generates HTML Element: `` + * + * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} */ export const jpAnchor = Anchor.compose({ - baseName: 'anchor', - baseClass: FoundationAnchor, - template, - styles + baseName: "anchor", + baseClass: FoundationAnchor, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); -/** - * Base class for Anchor - * @public - */ -export { Anchor }; - export { styles as anchorStyles }; diff --git a/packages/components/src/anchored-region/anchored-region.styles.ts b/packages/components/src/anchored-region/anchored-region.styles.ts new file mode 100644 index 00000000..1918b4d4 --- /dev/null +++ b/packages/components/src/anchored-region/anchored-region.styles.ts @@ -0,0 +1,16 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; + +/** + * Styles for AnchoredRegion + * @public + */ +export const anchoredRegionStyles: FoundationElementTemplate = ( + context, + definition +) => css` + :host { + contain: layout; + display: block; + } +`; diff --git a/packages/components/src/anchored-region/index.ts b/packages/components/src/anchored-region/index.ts index eb02bf38..eb9f3539 100644 --- a/packages/components/src/anchored-region/index.ts +++ b/packages/components/src/anchored-region/index.ts @@ -1,11 +1,8 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - AnchoredRegion, - anchoredRegionTemplate as template -} from '@microsoft/fast-foundation'; -import { anchoredRegionStyles as styles } from '@microsoft/fast-components'; + AnchoredRegion, + anchoredRegionTemplate as template, +} from "@microsoft/fast-foundation"; +import { anchoredRegionStyles as styles } from "./anchored-region.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#AnchoredRegion} registration for configuring the component with a DesignSystem. @@ -17,9 +14,9 @@ import { anchoredRegionStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpAnchoredRegion = AnchoredRegion.compose({ - baseName: 'anchored-region', - template, - styles + baseName: 'anchored-region', + template, + styles }); /** diff --git a/packages/components/src/avatar/avatar.styles.ts b/packages/components/src/avatar/avatar.styles.ts index c52a60cc..9bc6ed27 100644 --- a/packages/components/src/avatar/avatar.styles.ts +++ b/packages/components/src/avatar/avatar.styles.ts @@ -4,119 +4,115 @@ import { css, ElementStyles } from '@microsoft/fast-element'; import { - AvatarOptions, - Badge, - display, - FoundationElementTemplate -} from '@microsoft/fast-foundation'; + AvatarOptions, + Badge, + display, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; import { - accentFillRest, - baseHeightMultiplier, - controlCornerRadius, - density, - designUnit, - DirectionalStyleSheetBehavior, - foregroundOnAccentRest, - neutralForegroundRest, - typeRampBaseFontSize -} from '../design-tokens'; + accentFillRest, + baseHeightMultiplier, + controlCornerRadius, + density, + designUnit, + neutralForegroundRest, + typeRampBaseFontSize, +} from "../design-tokens.js"; +import { DirectionalStyleSheetBehavior } from "../styles/direction.js"; const rtl: FoundationElementTemplate = ( context, definition ) => css` - ::slotted(${context.tagFor(Badge)}) { - left: 0; - } + ::slotted(${context.tagFor(Badge)}) { + left: 0; + } `; const ltr: FoundationElementTemplate = ( context, definition ) => css` - ::slotted(${context.tagFor(Badge)}) { - right: 0; - } + ::slotted(${context.tagFor(Badge)}) { + right: 0; + } `; /** * Styles for Avatar * @public */ -export const avatarStyles: FoundationElementTemplate< - ElementStyles, - AvatarOptions -> = (context, definition) => - css` - ${display('flex')} :host { - position: relative; - height: var(--avatar-size, var(--avatar-size-default)); - width: var(--avatar-size, var(--avatar-size-default)); - --avatar-size-default: calc( - ( - (${baseHeightMultiplier} + ${density}) * ${designUnit} + - ((${designUnit} * 8) - 40) - ) * 1px - ); - --avatar-text-size: ${typeRampBaseFontSize}; - --avatar-text-ratio: ${designUnit}; - } +export const avatarStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("flex")} :host { + position: relative; + height: var(--avatar-size, var(--avatar-size-default)); + width: var(--avatar-size, var(--avatar-size-default)); + --avatar-size-default: calc( + ( + (${baseHeightMultiplier} + ${density}) * ${designUnit} + + ((${designUnit} * 8) - 40) + ) * 1px + ); + --avatar-text-size: ${typeRampBaseFontSize}; + --avatar-text-ratio: ${designUnit}; + } - .link { - text-decoration: none; - color: ${neutralForegroundRest}; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - min-width: 100%; - } + .link { + text-decoration: none; + color: ${neutralForegroundRest}; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + min-width: 100%; + } - .square { - border-radius: calc(${controlCornerRadius} * 1px); - min-width: 100%; - overflow: hidden; - } + .square { + border-radius: calc(${controlCornerRadius} * 1px); + min-width: 100%; + overflow: hidden; + } - .circle { - border-radius: 100%; - min-width: 100%; - overflow: hidden; - } + .circle { + border-radius: 100%; + min-width: 100%; + overflow: hidden; + } - .backplate { - position: relative; - display: flex; - background-color: ${accentFillRest}; - } + .backplate { + position: relative; + display: flex; +background-color: ${accentFillRest}; + } - .media, - ::slotted(img) { - max-width: 100%; - position: absolute; - display: block; - } + .media, + ::slotted(img) { + max-width: 100%; + position: absolute; + display: block; + } - .content { - font-size: calc( - ( - var(--avatar-text-size) + - var(--avatar-size, var(--avatar-size-default)) - ) / var(--avatar-text-ratio) - ); - color: ${foregroundOnAccentRest}; - line-height: var(--avatar-size, var(--avatar-size-default)); - display: block; - min-height: var(--avatar-size, var(--avatar-size-default)); - } + .content { + font-size: calc( + (var(--avatar-text-size) + var(--avatar-size, var(--avatar-size-default))) / + var(--avatar-text-ratio) + ); + line-height: var(--avatar-size, var(--avatar-size-default)); + display: block; + min-height: var(--avatar-size, var(--avatar-size-default)); + } - ::slotted(${context.tagFor(Badge)}) { - position: absolute; - display: block; - } - `.withBehaviors( - new DirectionalStyleSheetBehavior( - ltr(context, definition), - rtl(context, definition) - ) - ); + ::slotted(${context.tagFor(Badge)}) { + position: absolute; + display: block; + } + `.withBehaviors( + new DirectionalStyleSheetBehavior( + ltr(context, definition), + rtl(context, definition) + ) + ); diff --git a/packages/components/src/avatar/index.ts b/packages/components/src/avatar/index.ts index b4c8bf25..c57167a0 100644 --- a/packages/components/src/avatar/index.ts +++ b/packages/components/src/avatar/index.ts @@ -1,16 +1,56 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { Avatar, imgTemplate } from '@microsoft/fast-components'; +import { attr, html, when } from "@microsoft/fast-element"; import { - Avatar as FoundationAvatar, - AvatarOptions, - avatarTemplate as template -} from '@microsoft/fast-foundation'; -import { avatarStyles as styles } from './avatar.styles'; + AvatarOptions, + Avatar as FoundationAvatar, + avatarTemplate as template, +} from "@microsoft/fast-foundation"; +import { avatarStyles as styles } from "./avatar.styles.js"; -export { Avatar, imgTemplate } from '@microsoft/fast-components'; -export { styles as avatarStyles }; +/** + * The Jupyter Avatar Class + * @public + * + */ +export class Avatar extends FoundationAvatar { + /** + * Indicates the Avatar should have an image source + * + * @public + * @remarks + * HTML Attribute: src + */ + @attr({ attribute: "src" }) + public imgSrc: string | undefined; + + /** + * Indicates the Avatar should have alt text + * + * @public + * @remarks + * HTML Attribute: alt + */ + @attr public alt: string | undefined; +} + +/** + * The Jupyter Avatar Template for Images + * @public + * + */ +export const imgTemplate = html` + ${when( + x => x.imgSrc, + html` + ${x => x.alt} + ` + )} +`; /** * A function that returns a {@link @microsoft/fast-foundation#Avatar} registration for configuring the component with a DesignSystem. @@ -22,12 +62,14 @@ export { styles as avatarStyles }; * Generates HTML Element: `` */ export const jpAvatar = Avatar.compose({ - baseName: 'avatar', - baseClass: FoundationAvatar, - template, - styles, - media: imgTemplate, - shadowOptions: { - delegatesFocus: true - } + baseName: 'avatar', + baseClass: FoundationAvatar, + template, + styles, + media: imgTemplate, + shadowOptions: { + delegatesFocus: true, + }, }); + +export { styles as avatarStyles }; diff --git a/packages/components/src/badge/badge.styles.ts b/packages/components/src/badge/badge.styles.ts index f21104cb..ce27b659 100644 --- a/packages/components/src/badge/badge.styles.ts +++ b/packages/components/src/badge/badge.styles.ts @@ -47,7 +47,6 @@ export const badgeStyles: FoundationElementTemplate = ( :host([circular]) .control { border-radius: 100px; padding: 0 calc(${designUnit} * 1px); - /* Need to work with Brian on width and height here */ height: calc((${heightNumber} - (${designUnit} * 3)) * 1px); min-width: calc((${heightNumber} - (${designUnit} * 3)) * 1px); display: flex; diff --git a/packages/components/src/badge/index.ts b/packages/components/src/badge/index.ts index 3cebc7ca..8b410cab 100644 --- a/packages/components/src/badge/index.ts +++ b/packages/components/src/badge/index.ts @@ -1,8 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { Badge, badgeTemplate as template } from '@microsoft/fast-foundation'; -import { badgeStyles as styles } from './badge.styles'; +import { Badge, badgeTemplate as template } from "@microsoft/fast-foundation"; +import { badgeStyles as styles } from "./badge.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Badge} registration for configuring the component with a DesignSystem. @@ -14,9 +11,9 @@ import { badgeStyles as styles } from './badge.styles'; * Generates HTML Element: `` */ export const jpBadge = Badge.compose({ - baseName: 'badge', - template, - styles + baseName: 'badge', + template, + styles }); /** diff --git a/packages/components/src/breadcrumb-item/index.ts b/packages/components/src/breadcrumb-item/index.ts index 2ebbe580..e4b0e26b 100644 --- a/packages/components/src/breadcrumb-item/index.ts +++ b/packages/components/src/breadcrumb-item/index.ts @@ -1,15 +1,12 @@ -// Copyright (c) Jupyter Development Team. -// 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'; + BreadcrumbItem, + BreadcrumbItemOptions, + breadcrumbItemTemplate as template, +} from "@microsoft/fast-foundation"; +import { breadcrumbItemStyles as styles } from "./breadcrumb-item.styles.js"; /** - * A function that returns a BreadcrumbItem registration for configuring the component with a DesignSystem. + * A function that returns a {@link @microsoft/fast-foundation#BreadcrumbItem} registration for configuring the component with a DesignSystem. * Implements {@link @microsoft/fast-foundation#breadcrumbItemTemplate} * * @@ -18,13 +15,13 @@ import { breadcrumbItemStyles as styles } from './breadcrumb-item.styles'; * Generates HTML Element: `` */ export const jpBreadcrumbItem = BreadcrumbItem.compose({ - baseName: 'breadcrumb-item', - template, - styles, - separator: '/', - shadowOptions: { - delegatesFocus: true - } + baseName: 'breadcrumb-item', + template, + styles, + separator: "/", + shadowOptions: { + delegatesFocus: true, + }, }); /** diff --git a/packages/components/src/breadcrumb/breadcrumb.styles.ts b/packages/components/src/breadcrumb/breadcrumb.styles.ts new file mode 100644 index 00000000..be5c2589 --- /dev/null +++ b/packages/components/src/breadcrumb/breadcrumb.styles.ts @@ -0,0 +1,28 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + bodyFont, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; + +/** + * Styles for Breadcrumb + * @public + */ +export const breadcrumbStyles: FoundationElementTemplate = ( + context, + definition +) => css` + ${display("inline-block")} :host { + box-sizing: border-box; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + } + + .list { + display: flex; + flex-wrap: wrap; + } +`; \ No newline at end of file diff --git a/packages/components/src/breadcrumb/index.ts b/packages/components/src/breadcrumb/index.ts index 38219862..130bce96 100644 --- a/packages/components/src/breadcrumb/index.ts +++ b/packages/components/src/breadcrumb/index.ts @@ -1,14 +1,8 @@ -// Copyright (c) Jupyter Development Team. -// 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'; +import { Breadcrumb, breadcrumbTemplate as template } from "@microsoft/fast-foundation"; +import { breadcrumbStyles as styles } from "./breadcrumb.styles.js"; /** - * A function that returns a Breadcrumb registration for configuring the component with a DesignSystem. + * A function that returns a {@link @microsoft/fast-foundation#Breadcrumb} registration for configuring the component with a DesignSystem. * Implements {@link @microsoft/fast-foundation#breadcrumbTemplate} * * @@ -17,9 +11,9 @@ import { breadcrumbStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpBreadcrumb = Breadcrumb.compose({ - baseName: 'breadcrumb', - template, - styles + baseName: 'breadcrumb', + template, + styles, }); /** diff --git a/packages/components/src/button/button.pw.spec.ts b/packages/components/src/button/button.pw.spec.ts new file mode 100644 index 00000000..c1995b2a --- /dev/null +++ b/packages/components/src/button/button.pw.spec.ts @@ -0,0 +1,52 @@ +import type { + Button as jpButtonType +} from "@microsoft/fast-foundation"; +import chai from "chai"; + +const { expect } = chai; + +type jpButton = HTMLElement & jpButtonType; + +describe("jpButton", function () { + beforeEach(async function () { + if (!this.page && !this.browser) { + this.skip(); + } + + this.documentHandle = await this.page.evaluateHandle(() => document); + + this.setupHandle = await this.page.evaluateHandle( + (document) => { + const element = document.createElement("jp-button") as jpButton; + element.textContent = "Hello"; + element.id = "Button1"; + + document.body.appendChild(element) + }, + this.documentHandle + ); + }); + + afterEach(async function () { + if (this.setupHandle) { + await this.setupHandle.dispose(); + } + }); + + // jpButton should render on the page + it("should render on the page", async function () { + const element = await this.page.waitForSelector("jp-button"); + + expect(element).to.exist; + }); + + it("receive focus when focused programatically", async function () { + const element = await this.page.waitForSelector("jp-button"); + + await this.page.evaluateHandle(element => element.focus(), element) + + expect(await this.page.evaluate( + () => document.activeElement?.id + )).to.equal(await element.evaluate(node => node.id)); + }); +}); diff --git a/packages/components/src/button/button.styles.ts b/packages/components/src/button/button.styles.ts index 5410ed9f..ebd75d99 100644 --- a/packages/components/src/button/button.styles.ts +++ b/packages/components/src/button/button.styles.ts @@ -2,534 +2,24 @@ // Copyright (c) Microsoft Corporation. // Distributed under the terms of the Modified BSD License. -import { neutralFillStrongActive } from '@microsoft/fast-components'; import { css, ElementStyles } from '@microsoft/fast-element'; import { ButtonOptions, disabledCursor, - display, ElementDefinitionContext, - focusVisible, forcedColorsStylesheetBehavior, - PropertyStyleSheetBehavior } from '@microsoft/fast-foundation'; import { SystemColors } from '@microsoft/fast-web-utilities'; import { - accentFillActive, - accentFillFocus, - accentFillHover, accentFillRest, - accentForegroundActive, - accentForegroundHover, accentForegroundRest, - bodyFont, - controlCornerRadius, - density, - designUnit, disabledOpacity, - errorFillActive, - errorFillFocus, - errorFillHover, errorFillRest, - errorForegroundActive, - focusStrokeWidth, - foregroundOnAccentActive, - foregroundOnAccentHover, - foregroundOnAccentRest, - neutralFillActive, - neutralFillHover, neutralFillRest, - neutralFillStealthActive, - neutralFillStealthHover, - neutralFillStealthRest, - neutralFillStrongFocus, - neutralForegroundRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles'; +} from '../design-tokens.js'; +import { AccentButtonStyles, BaseButtonStyles, ErrorButtonStyles, LightweightButtonStyles, OutlineButtonStyles, StealthButtonStyles } from '../styles/patterns/button.js' +import { appearanceBehavior } from '../utilities/behaviors.js'; -/** - * Behavior that will conditionally apply a stylesheet based on the elements - * appearance property - * - * @param value - The value of the appearance property - * @param styles - The styles to be applied when condition matches - * - * @internal - */ -function appearanceBehavior(value: string, styles: ElementStyles) { - return new PropertyStyleSheetBehavior('appearance', value, styles); -} - -// 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 - */ -const BaseButtonStyles = css` - ${display('inline-flex')} :host { - font-family: ${bodyFont}; - outline: none; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - height: calc(${heightNumber} * 1px); - min-width: calc(${heightNumber} * 1px); - background-color: ${neutralFillRest}; - color: ${neutralForegroundRest}; - border-radius: calc(${controlCornerRadius} * 1px); - fill: currentcolor; - cursor: pointer; - margin: calc((${focusStrokeWidth} + 2) * 1px); - } - - .control { - background: transparent; - height: inherit; - flex-grow: 1; - box-sizing: border-box; - display: inline-flex; - justify-content: center; - align-items: center; - padding: 0 calc((10 + (${designUnit} * 2 * ${density})) * 1px); - white-space: nowrap; - outline: none; - text-decoration: none; - border: calc(${strokeWidth} * 1px) solid transparent; - color: inherit; - border-radius: inherit; - fill: inherit; - cursor: inherit; - font-family: inherit; - font-size: inherit; - line-height: inherit; - } - - :host(:hover) { - background-color: ${neutralFillHover}; - } - - :host(:active) { - background-color: ${neutralFillActive}; - } - - :host([aria-pressed='true']) { - box-shadow: inset 0px 0px 2px 2px ${neutralFillStrongActive}; - } - - :host([minimal]) { - --density: -4; - } - - :host([minimal]) .control { - padding: 1px; - } - - /* prettier-ignore */ - .control:${focusVisible} { - outline: calc(${focusStrokeWidth} * 1px) solid ${neutralFillStrongFocus}; - outline-offset: 2px; - -moz-outline-radius: 0px; - } - - .control::-moz-focus-inner { - border: 0; - } - - .start, - .end { - display: flex; - } - - .control.icon-only { - padding: 0; - line-height: 0; - } - - ::slotted(svg) { - ${ - /* Glyph size and margin-left is temporary - - replace when adaptive typography is figured out */ '' - } width: 16px; - height: 16px; - pointer-events: none; - } - - .start { - margin-inline-end: 11px; - } - - .end { - margin-inline-start: 11px; - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host .control { - background-color: ${SystemColors.ButtonFace}; - border-color: ${SystemColors.ButtonText}; - color: ${SystemColors.ButtonText}; - fill: currentColor; - } - - :host(:hover) .control { - forced-color-adjust: none; - background-color: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - /* prettier-ignore */ - .control:${focusVisible} { - forced-color-adjust: none; - background-color: ${SystemColors.Highlight}; - outline-color: ${SystemColors.ButtonText}; - color: ${SystemColors.HighlightText}; - } - - .control:hover, - :host([appearance='outline']) .control:hover { - border-color: ${SystemColors.ButtonText}; - } - - :host([href]) .control { - border-color: ${SystemColors.LinkText}; - color: ${SystemColors.LinkText}; - } - - :host([href]) .control:hover, - :host([href]) .control:${focusVisible} { - forced-color-adjust: none; - background: ${SystemColors.ButtonFace}; - outline-color: ${SystemColors.LinkText}; - color: ${SystemColors.LinkText}; - fill: currentColor; - } - `) -); - -/** - * @internal - */ -const AccentButtonStyles = css` - :host([appearance='accent']) { - background: ${accentFillRest}; - color: ${foregroundOnAccentRest}; - } - - :host([appearance='accent']:hover) { - background: ${accentFillHover}; - color: ${foregroundOnAccentHover}; - } - - :host([appearance='accent'][aria-pressed='true']) { - box-shadow: inset 0px 0px 2px 2px ${accentForegroundActive}; - } - - :host([appearance='accent']:active) .control:active { - background: ${accentFillActive}; - color: ${foregroundOnAccentActive}; - } - - :host([appearance="accent"]) .control:${focusVisible} { - outline-color: ${accentFillFocus}; - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([appearance='accent']) .control { - forced-color-adjust: none; - background: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - :host([appearance='accent']) .control:hover, - :host([appearance='accent']:active) .control:active { - background: ${SystemColors.HighlightText}; - border-color: ${SystemColors.Highlight}; - color: ${SystemColors.Highlight}; - } - - :host([appearance="accent"]) .control:${focusVisible} { - outline-color: ${SystemColors.Highlight}; - } - - :host([appearance='accent'][href]) .control { - background: ${SystemColors.LinkText}; - color: ${SystemColors.HighlightText}; - } - - :host([appearance='accent'][href]) .control:hover { - background: ${SystemColors.ButtonFace}; - border-color: ${SystemColors.LinkText}; - box-shadow: none; - color: ${SystemColors.LinkText}; - fill: currentColor; - } - - :host([appearance="accent"][href]) .control:${focusVisible} { - outline-color: ${SystemColors.HighlightText}; - } - `) -); - -/** - * @internal - */ -const ErrorButtonStyles = css` - :host([appearance='error']) { - background: ${errorFillRest}; - color: ${foregroundOnAccentRest}; - } - - :host([appearance='error']:hover) { - background: ${errorFillHover}; - color: ${foregroundOnAccentHover}; - } - - :host([appearance='error'][aria-pressed='true']) { - box-shadow: inset 0px 0px 2px 2px ${errorForegroundActive}; - } - - :host([appearance='error']:active) .control:active { - background: ${errorFillActive}; - color: ${foregroundOnAccentActive}; - } - - :host([appearance="error"]) .control:${focusVisible} { - outline-color: ${errorFillFocus}; - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([appearance='error']) .control { - forced-color-adjust: none; - background: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - :host([appearance='error']) .control:hover, - :host([appearance='error']:active) .control:active { - background: ${SystemColors.HighlightText}; - border-color: ${SystemColors.Highlight}; - color: ${SystemColors.Highlight}; - } - - :host([appearance="error"]) .control:${focusVisible} { - outline-color: ${SystemColors.Highlight}; - } - - :host([appearance='error'][href]) .control { - background: ${SystemColors.LinkText}; - color: ${SystemColors.HighlightText}; - } - - :host([appearance='error'][href]) .control:hover { - background: ${SystemColors.ButtonFace}; - border-color: ${SystemColors.LinkText}; - box-shadow: none; - color: ${SystemColors.LinkText}; - fill: currentColor; - } - - :host([appearance="error"][href]) .control:${focusVisible} { - outline-color: ${SystemColors.HighlightText}; - } - `) -); - -/** - * @internal - */ -export const LightweightButtonStyles = css` - :host([appearance='lightweight']) { - background: transparent; - color: ${accentForegroundRest}; - } - - :host([appearance='lightweight']) .control { - padding: 0; - height: initial; - border: none; - box-shadow: none; - border-radius: 0; - } - - :host([appearance='lightweight']:hover) { - background: transparent; - color: ${accentForegroundHover}; - } - - :host([appearance='lightweight']:active) { - background: transparent; - color: ${accentForegroundActive}; - } - - :host([appearance='lightweight']) .content { - position: relative; - } - - :host([appearance='lightweight']) .content::before { - content: ''; - display: block; - height: calc(${strokeWidth} * 1px); - position: absolute; - top: calc(1em + 4px); - width: 100%; - } - - :host([appearance='lightweight']:hover) .content::before { - background: ${accentForegroundHover}; - } - - :host([appearance='lightweight']:active) .content::before { - background: ${accentForegroundActive}; - } - - :host([appearance="lightweight"]) .control:${focusVisible} { - outline-color: transparent; - } - - :host([appearance="lightweight"]) .control:${focusVisible} .content::before { - background: ${neutralForegroundRest}; - height: calc(${focusStrokeWidth} * 1px); - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([appearance="lightweight"]) .control:hover, - :host([appearance="lightweight"]) .control:${focusVisible} { - forced-color-adjust: none; - background: ${SystemColors.ButtonFace}; - color: ${SystemColors.Highlight}; - } - :host([appearance="lightweight"]) .control:hover .content::before, - :host([appearance="lightweight"]) .control:${focusVisible} .content::before { - background: ${SystemColors.Highlight}; - } - - :host([appearance="lightweight"][href]) .control:hover, - :host([appearance="lightweight"][href]) .control:${focusVisible} { - background: ${SystemColors.ButtonFace}; - box-shadow: none; - color: ${SystemColors.LinkText}; - } - - :host([appearance="lightweight"][href]) .control:hover .content::before, - :host([appearance="lightweight"][href]) .control:${focusVisible} .content::before { - background: ${SystemColors.LinkText}; - } - `) -); - -/** - * @internal - */ -const OutlineButtonStyles = css` - :host([appearance='outline']) { - background: transparent; - border-color: ${accentFillRest}; - } - - :host([appearance='outline']:hover) { - border-color: ${accentFillHover}; - } - - :host([appearance='outline']:active) { - border-color: ${accentFillActive}; - } - - :host([appearance='outline']) .control { - border-color: inherit; - } - - :host([appearance="outline"]) .control:${focusVisible} { - outline-color: ${accentFillFocus}; - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([appearance='outline']) .control { - border-color: ${SystemColors.ButtonText}; - } - :host([appearance="outline"]) .control:${focusVisible} { - forced-color-adjust: none; - background-color: ${SystemColors.Highlight}; - outline-color: ${SystemColors.ButtonText}; - color: ${SystemColors.HighlightText}; - fill: currentColor; - } - :host([appearance='outline'][href]) .control { - background: ${SystemColors.ButtonFace}; - border-color: ${SystemColors.LinkText}; - color: ${SystemColors.LinkText}; - fill: currentColor; - } - :host([appearance="outline"][href]) .control:hover, - :host([appearance="outline"][href]) .control:${focusVisible} { - forced-color-adjust: none; - outline-color: ${SystemColors.LinkText}; - } - `) -); - -/** - * @internal - */ -const StealthButtonStyles = css` - :host([appearance='stealth']) { - background: transparent; - } - - :host([appearance='stealth']:hover) { - background: ${neutralFillStealthHover}; - } - - :host([appearance='stealth']:active) { - background: ${neutralFillStealthActive}; - } - - :host([appearance='stealth']) .control:${focusVisible} { - outline-color: ${accentFillFocus}; - } -`.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([appearance='stealth']), - :host([appearance='stealth']) .control { - forced-color-adjust: none; - background: ${SystemColors.ButtonFace}; - border-color: transparent; - color: ${SystemColors.ButtonText}; - fill: currentColor; - } - - :host([appearance='stealth']:hover) .control { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - fill: currentColor; - } - - :host([appearance="stealth"]:${focusVisible}) .control { - outline-color: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - fill: currentColor; - } - - :host([appearance='stealth'][href]) .control { - color: ${SystemColors.LinkText}; - } - - :host([appearance="stealth"][href]:hover) .control, - :host([appearance="stealth"][href]:${focusVisible}) .control { - background: ${SystemColors.LinkText}; - border-color: ${SystemColors.LinkText}; - color: ${SystemColors.HighlightText}; - fill: currentColor; - } - - :host([appearance="stealth"][href]:${focusVisible}) .control { - forced-color-adjust: none; - box-shadow: 0 0 0 1px ${SystemColors.LinkText}; - } - `) -); /** * Styles for Button diff --git a/packages/components/src/button/index.ts b/packages/components/src/button/index.ts index a2a3ad33..06110247 100644 --- a/packages/components/src/button/index.ts +++ b/packages/components/src/button/index.ts @@ -1,39 +1,34 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - -import { attr } from '@microsoft/fast-element'; +import { attr } from "@microsoft/fast-element"; import { - Button as FoundationButton, - buttonTemplate as template -} from '@microsoft/fast-foundation'; -import { buttonStyles } from './button.styles'; + Button as FoundationButton, + buttonTemplate as template, +} from "@microsoft/fast-foundation"; +import { buttonStyles as styles } from "./button.styles.js"; /** * Types of button appearance. * @public */ export type ButtonAppearance = - | 'accent' - | 'error' - | 'lightweight' - | 'neutral' - | 'outline' - | 'stealth'; + | "accent" + | "lightweight" + | "neutral" + | "outline" + | "stealth"; /** * @internal */ export class Button extends FoundationButton { - /** - * The appearance the button should have. - * - * @public - * @remarks - * HTML Attribute: appearance - */ - @attr - public appearance: ButtonAppearance; + /** + * The appearance the button should have. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance: ButtonAppearance = 'neutral'; /** * Whether the button has a compact layout or not. @@ -45,54 +40,51 @@ export class Button extends FoundationButton { @attr({ attribute: 'minimal', mode: 'boolean' }) public minimal: boolean; - public connectedCallback(): void { - super.connectedCallback(); - if (!this.appearance) { - this.appearance = 'neutral'; - } - } - - /** - * Applies 'icon-only' class when there is only an SVG in the default slot - * - * @public - * @remarks - */ - public defaultSlottedContentChanged( + /** + * Applies 'icon-only' class when there is only an SVG in the default slot + * + * @public + * @remarks + */ + public defaultSlottedContentChanged( oldValue: HTMLElement[], newValue: HTMLElement[] ): void { - const slottedElements = this.defaultSlottedContent.filter( - x => x.nodeType === Node.ELEMENT_NODE - ); - if ( + const slottedElements = this.defaultSlottedContent.filter( + x => x.nodeType === Node.ELEMENT_NODE + ); + if ( slottedElements.length === 1 && (slottedElements[0] instanceof SVGElement || slottedElements[0].classList.contains('fa') || slottedElements[0].classList.contains('fas')) ) { - this.control.classList.add('icon-only'); - } else { - this.control.classList.remove('icon-only'); + this.control.classList.add('icon-only'); + } else { + this.control.classList.remove('icon-only'); + } } - } } /** - * The button component registration. + * A function that returns a {@link @microsoft/fast-foundation#Button} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#buttonTemplate} + * * * @public * @remarks - * Generated HTML Element: `` + * Generates HTML Element: `` * * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} */ export const jpButton = Button.compose({ - baseName: 'button', - baseClass: FoundationButton, - template, - styles: buttonStyles, - shadowOptions: { - delegatesFocus: true - } + baseName: 'button', + baseClass: FoundationButton, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); + +export { styles as buttonStyles }; diff --git a/packages/components/src/card/card.styles.ts b/packages/components/src/card/card.styles.ts new file mode 100644 index 00000000..03c41a0a --- /dev/null +++ b/packages/components/src/card/card.styles.ts @@ -0,0 +1,41 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { controlCornerRadius, fillColor } from "../design-tokens.js"; +import { elevation } from "../styles/index.js"; + +/** + * Styles for Card + * @public + */ +export const cardStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("block")} :host { + --elevation: 4; + display: block; + contain: content; + height: var(--card-height, 100%); + width: var(--card-width, 100%); + box-sizing: border-box; + background: ${fillColor}; + border-radius: calc(${controlCornerRadius} * 1px); + ${elevation} + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + forced-color-adjust: none; + background: ${SystemColors.Canvas}; + box-shadow: 0 0 0 1px ${SystemColors.CanvasText}; + } + ` + ) + ); diff --git a/packages/components/src/card/index.ts b/packages/components/src/card/index.ts index 7ce5cc64..3b5bcb15 100644 --- a/packages/components/src/card/index.ts +++ b/packages/components/src/card/index.ts @@ -1,11 +1,32 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - Card as FoundationCard, - cardTemplate as template -} from '@microsoft/fast-foundation'; -import { Card, cardStyles as styles } from '@microsoft/fast-components'; + composedParent, + Card as FoundationCard, + cardTemplate as template, +} from "@microsoft/fast-foundation"; +import { Swatch } from "../color/swatch.js"; +import { fillColor, neutralFillLayerRecipe } from "../design-tokens.js"; +import { cardStyles as styles } from "./card.styles.js"; + +/** + * @internal + */ +export class Card extends FoundationCard { + connectedCallback() { + super.connectedCallback(); + + const parent = composedParent(this); + + if (parent) { + fillColor.setValueFor( + this, + (target: HTMLElement): Swatch => + neutralFillLayerRecipe + .getValueFor(target) + .evaluate(target, fillColor.getValueFor(parent)) + ); + } + } +} /** * A function that returns a {@link @microsoft/fast-foundation#Card} registration for configuring the component with a DesignSystem. @@ -17,10 +38,10 @@ import { Card, cardStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpCard = Card.compose({ - baseName: 'card', - baseClass: FoundationCard, - template, - styles + baseName: 'card', + baseClass: FoundationCard, + template, + styles, }); -export { Card, styles as cardStyles }; +export { styles as cardStyles }; diff --git a/packages/components/src/checkbox/index.ts b/packages/components/src/checkbox/index.ts index 33586393..213d4f7e 100644 --- a/packages/components/src/checkbox/index.ts +++ b/packages/components/src/checkbox/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - Checkbox, - CheckboxOptions, - checkboxTemplate as template -} from '@microsoft/fast-foundation'; -import { checkboxStyles as styles } from './checkbox.styles'; + Checkbox, + CheckboxOptions, + checkboxTemplate as template, +} from "@microsoft/fast-foundation"; +import { checkboxStyles as styles } from "./checkbox.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Checkbox} registration for configuring the component with a DesignSystem. @@ -18,26 +15,26 @@ import { checkboxStyles as styles } from './checkbox.styles'; * Generates HTML Element: `` */ export const jpCheckbox = Checkbox.compose({ - baseName: 'checkbox', - template, - styles, - checkedIndicator: /* html */ ` - - - + baseName: 'checkbox', + template, + styles, + checkedIndicator: /* html */ ` + + + `, - indeterminateIndicator: /* html */ ` + indeterminateIndicator: /* html */ `
- ` + `, }); /** diff --git a/packages/components/src/color.ts b/packages/components/src/color.ts deleted file mode 100644 index c360431b..00000000 --- a/packages/components/src/color.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { parseColorHexRGB } from '@microsoft/fast-colors'; -import { - InteractiveSwatchSet, - isDark, - Palette, - Swatch, - SwatchRGB -} from '@microsoft/fast-components'; - -export { - InteractiveSwatchSet, - isDark, - Palette, - PaletteRGB, - Recipe, - StandardLuminance, - Swatch, - SwatchRGB -} from '@microsoft/fast-components'; - -/* - * The error palette is built using the same color algorithm as the accent palette - * But by copying the algorithm from @microsoft/fast-components at commit 03d711f222bd816834a5e1d60256d3e083b27c27 - * as some helpers are not exported. - * The delta used are those of the accent palette. - */ - -export const white = SwatchRGB.create(1, 1, 1); -export const black = SwatchRGB.create(0, 0, 0); -export const baseErrorColor = parseColorHexRGB('#D32F2F')!; - -export enum ContrastTarget { - normal = 4.5, - large = 7 -} - -export function errorFillAlgorithm( - palette: Palette, - neutralPalette: Palette, - reference: Swatch, - hoverDelta: number, - activeDelta: number, - focusDelta: number, - neutralFillRestDelta: number, - neutralFillHoverDelta: number, - neutralFillActiveDelta: number -): InteractiveSwatchSet { - const error = palette.source; - const referenceIndex = neutralPalette.closestIndexOf(reference); - const swapThreshold = Math.max( - neutralFillRestDelta, - neutralFillHoverDelta, - neutralFillActiveDelta - ); - const direction = referenceIndex >= swapThreshold ? -1 : 1; - const errorIndex = palette.closestIndexOf(error); - - const hoverIndex = errorIndex; - const restIndex = hoverIndex + direction * -1 * hoverDelta; - const activeIndex = restIndex + direction * activeDelta; - const focusIndex = restIndex + direction * focusDelta; - - return { - rest: palette.get(restIndex), - hover: palette.get(hoverIndex), - active: palette.get(activeIndex), - focus: palette.get(focusIndex) - }; -} - -/** - * @internal - */ -export function errorForegroundAlgorithm( - palette: Palette, - reference: Swatch, - contrastTarget: number, - restDelta: number, - hoverDelta: number, - activeDelta: number, - focusDelta: number -): InteractiveSwatchSet { - const error = palette.source; - const errorIndex = palette.closestIndexOf(error); - const direction = isDark(reference) ? -1 : 1; - const startIndex = - errorIndex + - (direction === 1 - ? Math.min(restDelta, hoverDelta) - : Math.max(direction * restDelta, direction * hoverDelta)); - const accessibleSwatch = palette.colorContrast( - reference, - contrastTarget, - startIndex, - direction - ); - const accessibleIndex1 = palette.closestIndexOf(accessibleSwatch); - const accessibleIndex2 = - accessibleIndex1 + direction * Math.abs(restDelta - hoverDelta); - const indexOneIsRestState = - direction === 1 - ? restDelta < hoverDelta - : direction * restDelta > direction * hoverDelta; - - let restIndex: number; - let hoverIndex: number; - - if (indexOneIsRestState) { - restIndex = accessibleIndex1; - hoverIndex = accessibleIndex2; - } else { - restIndex = accessibleIndex2; - hoverIndex = accessibleIndex1; - } - - return { - rest: palette.get(restIndex), - hover: palette.get(hoverIndex), - active: palette.get(restIndex + direction * activeDelta), - focus: palette.get(restIndex + direction * focusDelta) - }; -} - -/** - * @internal - */ -export function foregroundOnErrorAlgorithm( - reference: Swatch, - contrastTarget: number -): Swatch { - return reference.contrast(white) >= contrastTarget ? white : black; -} - -export const errorBase = SwatchRGB.create( - baseErrorColor.r, - baseErrorColor.g, - baseErrorColor.b -); diff --git a/packages/components/src/color/README.md b/packages/components/src/color/README.md new file mode 100644 index 00000000..081899c6 --- /dev/null +++ b/packages/components/src/color/README.md @@ -0,0 +1,33 @@ +# Jupyter Color Recipes + +Color recipes are named colors who's value is algorithmically defined from a variety of inputs. `@jupyter/web-components` relies on these recipes heavily to achieve expressive theming options while maintaining color accessability targets. + +## Swatch +A Swatch is a representation of a color that has a `relativeLuminance` value and a method to convert the swatch to a color string. It is used by recipes to determine which colors to use for UI. + +### SwatchRGB +A concrete implementation of `Swatch`, it is a swatch with red, green, and blue 64bit color channels . + +**Example: Creating a SwatchRGB** +```ts +import { SwatchRGB } from "@jupyter/web-components"; + +const red = SwatchRGB.create(1, 0, 0); +``` + +## Palette +A palette is a collection `Swatch` instances, ordered by relative luminance, and provides mechanisms to safely retrieve swatches by index and by target contrast ratios. It also contains a `source` color, which is the color from which the palette is + +### PaletteRGB +An implementation of `Palette` of `SwatchRGB` instances. + +```ts +// Create a PaletteRGB from a SwatchRGB +const redPalette = PaletteRGB.from(red): + +// Create a PaletteRGB from an object +const greenPalette = PaletteRGB.from({r: 0, g: 1, b: 0}); + +// Create a PaletteRGB from R, G, and B arguments +const bluePalette = PaletteRGB.create(0, 0, 1); +``` \ No newline at end of file diff --git a/packages/components/src/color/palette.spec.ts b/packages/components/src/color/palette.spec.ts new file mode 100644 index 00000000..bd750f07 --- /dev/null +++ b/packages/components/src/color/palette.spec.ts @@ -0,0 +1,29 @@ + +import { expect } from "chai"; +import { PaletteRGB } from "./palette.js"; +import { SwatchRGB, isSwatchRGB } from "./swatch.js"; + +const test: SwatchRGB = { + r: 0, + g: 0, + b: 0, + relativeLuminance: 0, + contrast: () => 1, + toColorString: () => "" +} + +describe("PaletteRGB.from", () => { + it("should create a palette from the provided swatch if it matches a SwatchRGB implementation", () => { + const palette = PaletteRGB.from(test); + + expect(palette.source === test).to.be.true; + }) + + it("should create a palette from a rgb object", () => { + const source = {r: 1, g: 1, b: 1}; + const palette = PaletteRGB.from(source); + + expect(palette.source === source).to.be.false; + expect(isSwatchRGB(palette.source)).to.be.true; + }) +}); diff --git a/packages/components/src/color/palette.ts b/packages/components/src/color/palette.ts new file mode 100644 index 00000000..225d5edc --- /dev/null +++ b/packages/components/src/color/palette.ts @@ -0,0 +1,200 @@ +import { + clamp, + ColorRGBA64, + ComponentStateColorPalette, + parseColorHexRGB, +} from "@microsoft/fast-colors"; +import { isSwatchRGB, Swatch, SwatchRGB } from "./swatch.js"; +import { binarySearch } from "./utilities/binary-search.js"; +import { directionByIsDark } from "./utilities/direction-by-is-dark.js"; +import { contrast, RelativeLuminance } from "./utilities/relative-luminance.js"; + +/** + * A collection of {@link Swatch} instances + * @public + */ +export interface Palette { + readonly source: T; + readonly swatches: ReadonlyArray; + + /** + * Returns a swatch from the palette that most closely matches + * the contrast ratio provided to a provided reference. + */ + colorContrast( + reference: Swatch, + contrast: number, + initialIndex?: number, + direction?: 1 | -1 + ): T; + + /** + * Returns the index of the palette that most closely matches + * the relativeLuminance of the provided swatch + */ + closestIndexOf(reference: RelativeLuminance): number; + + /** + * Gets a swatch by index. Index is clamped to the limits + * of the palette so a Swatch will always be returned. + */ + get(index: number): T; +} + +/** @public */ +export type PaletteRGB = Palette; + +/** + * Creates a PaletteRGB from input R, G, B color values. + * @param r - Red value represented as a number between 0 and 1. + * @param g - Green value represented as a number between 0 and 1. + * @param b - Blue value represented as a number between 0 and 1. + */ +function create(r: number, g: number, b: number): PaletteRGB; +/** + * Creates a PaletteRGB from a source SwatchRGB object. + * @deprecated - Use PaletteRGB.from() + */ +function create(source: SwatchRGB): PaletteRGB; +function create(rOrSource: SwatchRGB | number, g?: number, b?: number): PaletteRGB { + if (typeof rOrSource === "number") { + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + return PaletteRGB.from(SwatchRGB.create(rOrSource, g!, b!)); + } else { + return PaletteRGB.from(rOrSource); + } +} + +/** + * Creates a PaletteRGB from a source color object. + * @param source - The source color + */ +function from(source: SwatchRGB): PaletteRGB; +function from(source: Record<"r" | "g" | "b", number>): PaletteRGB; +function from(source: any): PaletteRGB { + return isSwatchRGB(source) + ? PaletteRGBImpl.from(source) + : PaletteRGBImpl.from(SwatchRGB.create(source.r, source.g, source.b)); +} +/** @public */ +export const PaletteRGB = Object.freeze({ + create, + from, +}); + +/** + * A {@link Palette} representing RGB swatch values. + * @public + */ +class PaletteRGBImpl implements Palette { + /** + * {@inheritdoc Palette.source} + */ + public readonly source: SwatchRGB; + public readonly swatches: ReadonlyArray; + private lastIndex: number; + private reversedSwatches: ReadonlyArray; + private closestIndexCache = new Map(); + + /** + * + * @param source - The source color for the palette + * @param swatches - All swatches in the palette + */ + constructor(source: SwatchRGB, swatches: ReadonlyArray) { + this.source = source; + this.swatches = swatches; + + this.reversedSwatches = Object.freeze([...this.swatches].reverse()); + this.lastIndex = this.swatches.length - 1; + } + + /** + * {@inheritdoc Palette.colorContrast} + */ + public colorContrast( + reference: Swatch, + contrastTarget: number, + initialSearchIndex?: number, + direction?: 1 | -1 + ): SwatchRGB { + if (initialSearchIndex === undefined) { + initialSearchIndex = this.closestIndexOf(reference); + } + + let source: ReadonlyArray = this.swatches; + const endSearchIndex = this.lastIndex; + let startSearchIndex = initialSearchIndex; + + if (direction === undefined) { + direction = directionByIsDark(reference); + } + + const condition = (value: SwatchRGB) => + contrast(reference, value) >= contrastTarget; + + if (direction === -1) { + source = this.reversedSwatches; + startSearchIndex = endSearchIndex - startSearchIndex; + } + + return binarySearch(source, condition, startSearchIndex, endSearchIndex); + } + + /** + * {@inheritdoc Palette.get} + */ + public get(index: number): SwatchRGB { + return this.swatches[index] || this.swatches[clamp(index, 0, this.lastIndex)]; + } + + /** + * {@inheritdoc Palette.closestIndexOf} + */ + public closestIndexOf(reference: Swatch): number { + if (this.closestIndexCache.has(reference.relativeLuminance)) { + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + return this.closestIndexCache.get(reference.relativeLuminance)!; + } + + let index = this.swatches.indexOf(reference as SwatchRGB); + + if (index !== -1) { + this.closestIndexCache.set(reference.relativeLuminance, index); + return index; + } + + const closest = this.swatches.reduce((previous, next) => + Math.abs(next.relativeLuminance - reference.relativeLuminance) < + Math.abs(previous.relativeLuminance - reference.relativeLuminance) + ? next + : previous + ); + + index = this.swatches.indexOf(closest); + this.closestIndexCache.set(reference.relativeLuminance, index); + + return index; + } + + /** + * Create a color palette from a provided swatch + * @param source - The source swatch to create a palette from + * @returns + */ + static from(source: SwatchRGB): PaletteRGB { + return new PaletteRGBImpl( + source, + Object.freeze( + new ComponentStateColorPalette({ + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + baseColor: ColorRGBA64.fromObject(source)!, + }).palette.map(x => { + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + const _x = parseColorHexRGB(x.toStringHexRGB())!; + return SwatchRGB.create(_x.r, _x.g, _x.b); + }) + ) + ); + } +} diff --git a/packages/components/src/color/recipe.ts b/packages/components/src/color/recipe.ts new file mode 100644 index 00000000..0bff1a60 --- /dev/null +++ b/packages/components/src/color/recipe.ts @@ -0,0 +1,24 @@ +import { Swatch } from "./swatch.js"; + +/** @public */ +export interface InteractiveSwatchSet { + /** + * The swatch to apply to the rest state + */ + rest: Swatch; + + /** + * The swatch to apply to the hover state + */ + hover: Swatch; + + /** + * The swatch to apply to the active state + */ + active: Swatch; + + /** + * The swatch to apply to the focus state + */ + focus: Swatch; +} diff --git a/packages/components/src/color/recipes/accent-fill.ts b/packages/components/src/color/recipes/accent-fill.ts new file mode 100644 index 00000000..f0bf8d62 --- /dev/null +++ b/packages/components/src/color/recipes/accent-fill.ts @@ -0,0 +1,40 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; + +/** + * @internal + */ +export function accentFill( + palette: Palette, + neutralPalette: Palette, + reference: Swatch, + hoverDelta: number, + activeDelta: number, + focusDelta: number, + neutralFillRestDelta: number, + neutralFillHoverDelta: number, + neutralFillActiveDelta: number +): InteractiveSwatchSet { + const accent = palette.source; + const referenceIndex = neutralPalette.closestIndexOf(reference); + const swapThreshold = Math.max( + neutralFillRestDelta, + neutralFillHoverDelta, + neutralFillActiveDelta + ); + const direction = referenceIndex >= swapThreshold ? -1 : 1; + const accentIndex = palette.closestIndexOf(accent); + + const hoverIndex = accentIndex; + const restIndex = hoverIndex + direction * -1 * hoverDelta; + const activeIndex = restIndex + direction * activeDelta; + const focusIndex = restIndex + direction * focusDelta; + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(activeIndex), + focus: palette.get(focusIndex), + }; +} diff --git a/packages/components/src/color/recipes/accent-foreground.spec.ts b/packages/components/src/color/recipes/accent-foreground.spec.ts new file mode 100644 index 00000000..7bde416a --- /dev/null +++ b/packages/components/src/color/recipes/accent-foreground.spec.ts @@ -0,0 +1,95 @@ +import { expect } from "chai"; +import { parseColorHexRGB } from "@microsoft/fast-colors"; +import { PaletteRGB } from "../palette.js"; +import { SwatchRGB } from "../swatch.js"; +import { accentBase, black, middleGrey, white } from "../utilities/color-constants.js"; +import { accentForeground } from "./accent-foreground.js"; + +describe("accentForeground", (): void => { + const neutralPalette = PaletteRGB.create(middleGrey) + const accentPalette = PaletteRGB.create(accentBase); + + it("should increase contrast on hover state and decrease contrast on active state in either mode", (): void => { + const lightModeColors = accentForeground( + accentPalette, + white, + 4.5, + 0, + 6, + -4, + 0 + ); + const darkModeColors = accentForeground( + accentPalette, + black, + 4.5, + 0, + 6, + -4, + 0 + ); + + expect( + lightModeColors.hover.contrast(white) + ).to.be.greaterThan( + lightModeColors.rest.contrast(white) + ); + expect( + darkModeColors.hover.contrast(black) + ).to.be.greaterThan( + darkModeColors.rest.contrast(black) + ); + }); + + it("should have accessible rest and hover colors against the background color", (): void => { + const accentColors = [ + SwatchRGB.from(parseColorHexRGB("#0078D4")!), + SwatchRGB.from(parseColorHexRGB("#107C10")!), + SwatchRGB.from(parseColorHexRGB("#5C2D91")!), + SwatchRGB.from(parseColorHexRGB("#D83B01")!), + SwatchRGB.from(parseColorHexRGB("#F2C812")!), + ]; + + accentColors.forEach( + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + (accent): void => { + const accentPalette = PaletteRGB.create(accent); + + neutralPalette.swatches.forEach((swatch): void => { + const smallColors = accentForeground( + accentPalette, + swatch, + 4.5, + 0, + 6, + -4, + 0 + ); + const largeColors = accentForeground( + accentPalette, + swatch, + 3, + 0, + 6, + -4, + 0 + ); + expect( + swatch.contrast(smallColors.rest) + // There are a few states that are impossible to meet contrast on + ).to.be.gte(4.47); + expect( + swatch.contrast(smallColors.hover) + // There are a few states that are impossible to meet contrast on + ).to.be.gte(3.7); + expect( + swatch.contrast(largeColors.rest) + ).to.be.gte(3); + expect( + swatch.contrast(largeColors.hover) + ).to.be.gte(3); + }); + } + ); + }); +}); diff --git a/packages/components/src/color/recipes/accent-foreground.ts b/packages/components/src/color/recipes/accent-foreground.ts new file mode 100644 index 00000000..1c2fb47c --- /dev/null +++ b/packages/components/src/color/recipes/accent-foreground.ts @@ -0,0 +1,57 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** + * @internal + */ +export function accentForeground( + palette: Palette, + reference: Swatch, + contrastTarget: number, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number +): InteractiveSwatchSet { + const accent = palette.source; + const accentIndex = palette.closestIndexOf(accent); + const direction = directionByIsDark(reference); + const startIndex = + accentIndex + + (direction === 1 + ? Math.min(restDelta, hoverDelta) + : Math.max(direction * restDelta, direction * hoverDelta)); + const accessibleSwatch = palette.colorContrast( + reference, + contrastTarget, + startIndex, + direction + ); + const accessibleIndex1 = palette.closestIndexOf(accessibleSwatch); + const accessibleIndex2 = + accessibleIndex1 + direction * Math.abs(restDelta - hoverDelta); + const indexOneIsRestState = + direction === 1 + ? restDelta < hoverDelta + : direction * restDelta > direction * hoverDelta; + + let restIndex: number; + let hoverIndex: number; + + if (indexOneIsRestState) { + restIndex = accessibleIndex1; + hoverIndex = accessibleIndex2; + } else { + restIndex = accessibleIndex2; + hoverIndex = accessibleIndex1; + } + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(restIndex + direction * activeDelta), + focus: palette.get(restIndex + direction * focusDelta), + }; +} diff --git a/packages/components/src/color/recipes/error-fill.ts b/packages/components/src/color/recipes/error-fill.ts new file mode 100644 index 00000000..7d33e149 --- /dev/null +++ b/packages/components/src/color/recipes/error-fill.ts @@ -0,0 +1,92 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; +import { isDark } from "../utilities/is-dark.js"; + +export function errorFillAlgorithm( + palette: Palette, + neutralPalette: Palette, + reference: Swatch, + hoverDelta: number, + activeDelta: number, + focusDelta: number, + neutralFillRestDelta: number, + neutralFillHoverDelta: number, + neutralFillActiveDelta: number + ): InteractiveSwatchSet { + const error = palette.source; + const referenceIndex = neutralPalette.closestIndexOf(reference); + const swapThreshold = Math.max( + neutralFillRestDelta, + neutralFillHoverDelta, + neutralFillActiveDelta + ); + const direction = referenceIndex >= swapThreshold ? -1 : 1; + const errorIndex = palette.closestIndexOf(error); + + const hoverIndex = errorIndex; + const restIndex = hoverIndex + direction * -1 * hoverDelta; + const activeIndex = restIndex + direction * activeDelta; + const focusIndex = restIndex + direction * focusDelta; + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(activeIndex), + focus: palette.get(focusIndex) + }; + } + + /** + * @internal + */ + export function errorForegroundAlgorithm( + palette: Palette, + reference: Swatch, + contrastTarget: number, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number + ): InteractiveSwatchSet { + const error = palette.source; + const errorIndex = palette.closestIndexOf(error); + const direction = isDark(reference) ? -1 : 1; + const startIndex = + errorIndex + + (direction === 1 + ? Math.min(restDelta, hoverDelta) + : Math.max(direction * restDelta, direction * hoverDelta)); + const accessibleSwatch = palette.colorContrast( + reference, + contrastTarget, + startIndex, + direction + ); + const accessibleIndex1 = palette.closestIndexOf(accessibleSwatch); + const accessibleIndex2 = + accessibleIndex1 + direction * Math.abs(restDelta - hoverDelta); + const indexOneIsRestState = + direction === 1 + ? restDelta < hoverDelta + : direction * restDelta > direction * hoverDelta; + + let restIndex: number; + let hoverIndex: number; + + if (indexOneIsRestState) { + restIndex = accessibleIndex1; + hoverIndex = accessibleIndex2; + } else { + restIndex = accessibleIndex2; + hoverIndex = accessibleIndex1; + } + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(restIndex + direction * activeDelta), + focus: palette.get(restIndex + direction * focusDelta) + }; + } + diff --git a/packages/components/src/color/recipes/focus-stroke.ts b/packages/components/src/color/recipes/focus-stroke.ts new file mode 100644 index 00000000..f3e79f5c --- /dev/null +++ b/packages/components/src/color/recipes/focus-stroke.ts @@ -0,0 +1,22 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** @internal */ +export function focusStrokeOuter(palette: Palette, reference: Swatch) { + return palette.colorContrast(reference, 3.5); +} + +/** @internal */ +export function focusStrokeInner( + palette: Palette, + reference: Swatch, + focusColor: Swatch +): Swatch { + return palette.colorContrast( + focusColor, + 3.5, + palette.closestIndexOf(palette.source), + (directionByIsDark(reference) * -1) as 1 | -1 + ); +} diff --git a/packages/components/src/color/recipes/foreground-on-accent.spec.ts b/packages/components/src/color/recipes/foreground-on-accent.spec.ts new file mode 100644 index 00000000..91a5a8a4 --- /dev/null +++ b/packages/components/src/color/recipes/foreground-on-accent.spec.ts @@ -0,0 +1,19 @@ +import { expect } from "chai"; +import { SwatchRGB } from "../swatch.js"; +import { black } from "../utilities/color-constants.js"; +import { foregroundOnAccent } from './foreground-on-accent'; + +describe("Cut text", (): void => { + it("should return black when background does not meet contrast ratio", (): void => { + const small = foregroundOnAccent(SwatchRGB.create(1, 1, 1), 4.5) as SwatchRGB; + const large = foregroundOnAccent(SwatchRGB.create(1, 1, 1), 3) as SwatchRGB; + + expect(small.r).to.equal(black.r); + expect(small.g).to.equal(black.g); + expect(small.b).to.equal(black.b); + + expect(large.r).to.equal(black.r); + expect(large.g).to.equal(black.g); + expect(large.b).to.equal(black.b); + }); +}); diff --git a/packages/components/src/color/recipes/foreground-on-accent.ts b/packages/components/src/color/recipes/foreground-on-accent.ts new file mode 100644 index 00000000..79f4b029 --- /dev/null +++ b/packages/components/src/color/recipes/foreground-on-accent.ts @@ -0,0 +1,9 @@ +import { Swatch } from "../swatch.js"; +import { black, white } from "../utilities/color-constants.js"; + +/** + * @internal + */ +export function foregroundOnAccent(reference: Swatch, contrastTarget: number): Swatch { + return reference.contrast(white) >= contrastTarget ? white : black; +} diff --git a/packages/components/src/color/recipes/foreground-on-error.ts b/packages/components/src/color/recipes/foreground-on-error.ts new file mode 100644 index 00000000..b64e295f --- /dev/null +++ b/packages/components/src/color/recipes/foreground-on-error.ts @@ -0,0 +1,14 @@ +import { Swatch } from "../swatch.js"; +import { black, white } from "../utilities/color-constants.js"; + +/** + * @internal + */ + +export function foregroundOnErrorAlgorithm( + reference: Swatch, + contrastTarget: number +): Swatch { + return reference.contrast(white) >= contrastTarget ? white : black; +} + diff --git a/packages/components/src/color/recipes/neutral-fill-contrast.ts b/packages/components/src/color/recipes/neutral-fill-contrast.ts new file mode 100644 index 00000000..0027cafc --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill-contrast.ts @@ -0,0 +1,42 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** + * @internal + */ +export function neutralFillContrast( + palette: Palette, + reference: Swatch, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number +): InteractiveSwatchSet { + const direction = directionByIsDark(reference); + const accessibleIndex = palette.closestIndexOf(palette.colorContrast(reference, 4.5)); + const accessibleIndex2 = + accessibleIndex + direction * Math.abs(restDelta - hoverDelta); + const indexOneIsRest = + direction === 1 + ? restDelta < hoverDelta + : direction * restDelta > direction * hoverDelta; + let restIndex: number; + let hoverIndex: number; + + if (indexOneIsRest) { + restIndex = accessibleIndex; + hoverIndex = accessibleIndex2; + } else { + restIndex = accessibleIndex2; + hoverIndex = accessibleIndex; + } + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(restIndex + direction * activeDelta), + focus: palette.get(restIndex + direction * focusDelta), + }; +} diff --git a/packages/components/src/color/recipes/neutral-fill-input.ts b/packages/components/src/color/recipes/neutral-fill-input.ts new file mode 100644 index 00000000..6f4272ec --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill-input.ts @@ -0,0 +1,26 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** + * @internal + */ +export function neutralFillInput( + palette: Palette, + reference: Swatch, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number +): InteractiveSwatchSet { + const direction = directionByIsDark(reference); + const referenceIndex = palette.closestIndexOf(reference); + + return { + rest: palette.get(referenceIndex - direction * restDelta), + hover: palette.get(referenceIndex - direction * hoverDelta), + active: palette.get(referenceIndex - direction * activeDelta), + focus: palette.get(referenceIndex - direction * focusDelta), + }; +} diff --git a/packages/components/src/color/recipes/neutral-fill-layer.spec.ts b/packages/components/src/color/recipes/neutral-fill-layer.spec.ts new file mode 100644 index 00000000..a166291e --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill-layer.spec.ts @@ -0,0 +1,31 @@ +import { expect } from "chai"; +import { PaletteRGB } from "../palette.js"; +import { SwatchRGB } from "../swatch.js"; +import { middleGrey } from "../utilities/color-constants.js"; +import { neutralFillLayer } from "./neutral-fill-layer.js"; + +const neutralPalette = PaletteRGB.create(middleGrey); + +describe("neutralFillCard", (): void => { + it("should get darker when the index of the backgroundColor is lower than the offset index", (): void => { + const delta = 3 + for (let i: number = 0; i < delta; i++) { + const color = neutralFillLayer(neutralPalette, neutralPalette.get(i), delta) + const resolved = neutralPalette.get(delta + i); + expect( + color + ).to.equal(resolved); + } + }); + it("should return the color at three steps lower than the background color", (): void => { + const delta = 3; + + for (let i: number = delta; i < neutralPalette.swatches.length; i++) { + expect( + neutralPalette.swatches.indexOf( + neutralFillLayer(neutralPalette, neutralPalette.get(i), delta) as SwatchRGB + ) + ).to.equal(i - 3); + } + }); +}); diff --git a/packages/components/src/color/recipes/neutral-fill-layer.ts b/packages/components/src/color/recipes/neutral-fill-layer.ts new file mode 100644 index 00000000..67f6ffac --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill-layer.ts @@ -0,0 +1,15 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; + +/** + * @internal + */ +export function neutralFillLayer( + palette: Palette, + reference: Swatch, + delta: number +): Swatch { + const referenceIndex = palette.closestIndexOf(reference); + + return palette.get(referenceIndex - (referenceIndex < delta ? delta * -1 : delta)); +} diff --git a/packages/components/src/color/recipes/neutral-fill-stealth.ts b/packages/components/src/color/recipes/neutral-fill-stealth.ts new file mode 100644 index 00000000..79019779 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill-stealth.ts @@ -0,0 +1,40 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; + +/** + * @internal + */ +export function neutralFillStealth( + palette: Palette, + reference: Swatch, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number, + fillRestDelta: number, + fillHoverDelta: number, + fillActiveDelta: number, + fillFocusDelta: number +): InteractiveSwatchSet { + const swapThreshold = Math.max( + restDelta, + hoverDelta, + activeDelta, + focusDelta, + fillRestDelta, + fillHoverDelta, + fillActiveDelta, + fillFocusDelta + ); + + const referenceIndex = palette.closestIndexOf(reference); + const direction: 1 | -1 = referenceIndex >= swapThreshold ? -1 : 1; + + return { + rest: palette.get(referenceIndex + direction * restDelta), + hover: palette.get(referenceIndex + direction * hoverDelta), + active: palette.get(referenceIndex + direction * activeDelta), + focus: palette.get(referenceIndex + direction * focusDelta), + }; +} diff --git a/packages/components/src/color/recipes/neutral-fill.ts b/packages/components/src/color/recipes/neutral-fill.ts new file mode 100644 index 00000000..00f6bdbb --- /dev/null +++ b/packages/components/src/color/recipes/neutral-fill.ts @@ -0,0 +1,33 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; + +/** + * + * @param palette - The palette to operate on + * @param reference - The reference color to calculate a color for + * @param delta - The offset from the reference's location + * @param threshold - Determines if a lighter or darker color than the reference will be picked. + * @returns + * + * @internal + */ +export function neutralFill( + palette: Palette, + reference: Swatch, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number +): InteractiveSwatchSet { + const referenceIndex = palette.closestIndexOf(reference); + const threshold = Math.max(restDelta, hoverDelta, activeDelta, focusDelta); + const direction = referenceIndex >= threshold ? -1 : 1; + + return { + rest: palette.get(referenceIndex + direction * restDelta), + hover: palette.get(referenceIndex + direction * hoverDelta), + active: palette.get(referenceIndex + direction * activeDelta), + focus: palette.get(referenceIndex + direction * focusDelta), + }; +} diff --git a/packages/components/src/color/recipes/neutral-foreground-hint.spec.ts b/packages/components/src/color/recipes/neutral-foreground-hint.spec.ts new file mode 100644 index 00000000..5b4d625a --- /dev/null +++ b/packages/components/src/color/recipes/neutral-foreground-hint.spec.ts @@ -0,0 +1,35 @@ +import { expect } from "chai"; +import { PaletteRGB } from "../palette.js"; +import { SwatchRGB } from "../swatch.js"; +import { accentBase, middleGrey } from "../utilities/color-constants.js"; +import { neutralForegroundHint } from "./neutral-foreground-hint.js"; + +describe("neutralForegroundHint", (): void => { + const neutralPalette = PaletteRGB.create(middleGrey); + const accentPalette = PaletteRGB.create(accentBase); + + neutralPalette.swatches.concat(accentPalette.swatches).forEach((swatch): void => { + it(`${swatch} should resolve a color from the neutral palette`, (): void => { + expect( + neutralPalette.swatches.indexOf( + neutralForegroundHint( + neutralPalette, + swatch + ) as SwatchRGB + ) + ).not.to.equal(-1); + }); + }); + + neutralPalette.swatches.concat(accentPalette.swatches).forEach((swatch): void => { + it(`${swatch} should always be at least 4.5 : 1 against the background`, (): void => { + expect( + swatch.contrast(neutralForegroundHint(neutralPalette, swatch)) + // retrieveContrast(swatch, neutralForegroundHint_DEPRECATED) + // Because neutralForegroundHint follows the direction patterns of neutralForeground, + // a backgroundColor #777777 is impossible to hit 4.5 against. + ).to.be.gte(swatch.toColorString().toUpperCase() === "#777777" ? 4.48 : 4.5); + expect(swatch.contrast(neutralForegroundHint(neutralPalette, swatch))).to.be.lessThan(5); + }); + }); +}); diff --git a/packages/components/src/color/recipes/neutral-foreground-hint.ts b/packages/components/src/color/recipes/neutral-foreground-hint.ts new file mode 100644 index 00000000..e11696cd --- /dev/null +++ b/packages/components/src/color/recipes/neutral-foreground-hint.ts @@ -0,0 +1,13 @@ +import { Swatch } from "../swatch.js"; +import { Palette } from "../palette.js"; + +/** + * The neutralForegroundHint color recipe + * @param palette - The palette to operate on + * @param reference - The reference color + * + * @internal + */ +export function neutralForegroundHint(palette: Palette, reference: Swatch): Swatch { + return palette.colorContrast(reference, 4.5); +} diff --git a/packages/components/src/color/recipes/neutral-foreground.spec.ts b/packages/components/src/color/recipes/neutral-foreground.spec.ts new file mode 100644 index 00000000..aa71745e --- /dev/null +++ b/packages/components/src/color/recipes/neutral-foreground.spec.ts @@ -0,0 +1,22 @@ +import { parseColorHexRGB } from "@microsoft/fast-colors"; +import { expect } from "chai"; +import { PaletteRGB } from "../palette.js"; +import { neutralForeground } from "./neutral-foreground.js"; +import { SwatchRGB } from "../swatch.js"; +import { middleGrey, white } from "../utilities/color-constants.js"; + +describe("neutralForeground", (): void => { + const neutralPalette = PaletteRGB.create(middleGrey); + + it("should return correct result with default design system values", (): void => { + expect( + neutralForeground(neutralPalette, neutralPalette.get(88)).contrast(neutralPalette.get(neutralPalette.swatches.length - 1)) + ).to.be.gte(14); + }); + + it("should return #FFFFFF with a dark background", (): void => { + expect( + neutralForeground(neutralPalette, white).contrast(white) + ).to.be.gte(14); + }); +}); diff --git a/packages/components/src/color/recipes/neutral-foreground.ts b/packages/components/src/color/recipes/neutral-foreground.ts new file mode 100644 index 00000000..5d1707d1 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-foreground.ts @@ -0,0 +1,9 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; + +/** + * @internal + */ +export function neutralForeground(palette: Palette, reference: Swatch): Swatch { + return palette.colorContrast(reference, 14); +} diff --git a/packages/components/src/color/recipes/neutral-layer-1.ts b/packages/components/src/color/recipes/neutral-layer-1.ts new file mode 100644 index 00000000..b9073fdb --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-1.ts @@ -0,0 +1,9 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { baseLayerLuminanceSwatch } from "../utilities/base-layer-luminance.js"; + +export function neutralLayer1(palette: Palette, baseLayerLuminance: number): Swatch { + return palette.get( + palette.closestIndexOf(baseLayerLuminanceSwatch(baseLayerLuminance)) + ); +} diff --git a/packages/components/src/color/recipes/neutral-layer-2.ts b/packages/components/src/color/recipes/neutral-layer-2.ts new file mode 100644 index 00000000..aeaf440f --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-2.ts @@ -0,0 +1,45 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { baseLayerLuminanceSwatch } from "../utilities/base-layer-luminance.js"; + +/** + * @internal + */ +export function neutralLayer2Index( + palette: Palette, + luminance: number, + layerDelta: number, + fillRestDelta: number, + fillHoverDelta: number, + fillActiveDelta: number +): number { + return Math.max( + palette.closestIndexOf(baseLayerLuminanceSwatch(luminance)) + layerDelta, + fillRestDelta, + fillHoverDelta, + fillActiveDelta + ); +} + +/** + * @internal + */ +export function neutralLayer2( + palette: Palette, + luminance: number, + layerDelta: number, + fillRestDelta: number, + fillHoverDelta: number, + fillActiveDelta: number +): Swatch { + return palette.get( + neutralLayer2Index( + palette, + luminance, + layerDelta, + fillRestDelta, + fillHoverDelta, + fillActiveDelta + ) + ); +} diff --git a/packages/components/src/color/recipes/neutral-layer-3.ts b/packages/components/src/color/recipes/neutral-layer-3.ts new file mode 100644 index 00000000..9075dcc2 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-3.ts @@ -0,0 +1,26 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { neutralLayer2Index } from "./neutral-layer-2.js"; + +/** + * @internal + */ +export function neutralLayer3( + palette: Palette, + luminance: number, + layerDelta: number, + fillRestDelta: number, + fillHoverDelta: number, + fillActiveDelta: number +): Swatch { + return palette.get( + neutralLayer2Index( + palette, + luminance, + layerDelta, + fillRestDelta, + fillHoverDelta, + fillActiveDelta + ) + layerDelta + ); +} diff --git a/packages/components/src/color/recipes/neutral-layer-4.ts b/packages/components/src/color/recipes/neutral-layer-4.ts new file mode 100644 index 00000000..2656bdc0 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-4.ts @@ -0,0 +1,27 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { neutralLayer2Index } from "./neutral-layer-2.js"; + +/** + * @internal + */ +export function neutralLayer4( + palette: Palette, + luminance: number, + layerDelta: number, + fillRestDelta: number, + fillHoverDelta: number, + fillActiveDelta: number +): Swatch { + return palette.get( + neutralLayer2Index( + palette, + luminance, + layerDelta, + fillRestDelta, + fillHoverDelta, + fillActiveDelta + ) + + layerDelta * 2 + ); +} diff --git a/packages/components/src/color/recipes/neutral-layer-card-container.ts b/packages/components/src/color/recipes/neutral-layer-card-container.ts new file mode 100644 index 00000000..1fd1418f --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-card-container.ts @@ -0,0 +1,16 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { baseLayerLuminanceSwatch } from "../utilities/base-layer-luminance.js"; + +/** + * @internal + */ +export function neutralLayerCardContainer( + palette: Palette, + relativeLuminance: number, + layerDelta: number +): Swatch { + return palette.get( + palette.closestIndexOf(baseLayerLuminanceSwatch(relativeLuminance)) + layerDelta + ); +} diff --git a/packages/components/src/color/recipes/neutral-layer-floating.ts b/packages/components/src/color/recipes/neutral-layer-floating.ts new file mode 100644 index 00000000..e84d5039 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer-floating.ts @@ -0,0 +1,16 @@ +import { Palette } from "../palette.js"; +import { Swatch } from "../swatch.js"; +import { baseLayerLuminanceSwatch } from "../utilities/base-layer-luminance.js"; + +/** + * @internal + */ +export function neutralLayerFloating( + palette: Palette, + relativeLuminance: number, + layerDelta: number +): Swatch { + const cardIndex = + palette.closestIndexOf(baseLayerLuminanceSwatch(relativeLuminance)) - layerDelta; + return palette.get(cardIndex - layerDelta); +} diff --git a/packages/components/src/color/recipes/neutral-layer.spec.ts b/packages/components/src/color/recipes/neutral-layer.spec.ts new file mode 100644 index 00000000..6628716c --- /dev/null +++ b/packages/components/src/color/recipes/neutral-layer.spec.ts @@ -0,0 +1,72 @@ +import { expect } from "chai"; +import { PaletteRGB } from "../palette.js"; +import { StandardLuminance } from "../utilities/base-layer-luminance.js"; +import { middleGrey } from "../utilities/color-constants.js"; +import { + neutralLayerFloating +} from './neutral-layer-floating'; +import { neutralLayer1 } from "./neutral-layer-1.js"; +import { neutralLayer2 } from "./neutral-layer-2.js"; +import { neutralLayer3 } from "./neutral-layer-3.js"; +import { neutralLayer4 } from "./neutral-layer-4.js"; +import { SwatchRGB } from "../swatch.js"; + +const neutralPalette = PaletteRGB.create(middleGrey); + +const enum NeutralPaletteLightModeOffsets { + L1 = 0, + L2 = 10, + L3 = 13, + L4 = 16, +} + +const enum NeutralPaletteDarkModeOffsets { + L1 = 76, + L2 = 79, + L3 = 82, + L4 = 85, +} + +describe("neutralLayer", (): void => { + describe("1", (): void => { + it("should return values from 1 when in light mode", (): void => { + expect(neutralLayer1(neutralPalette, StandardLuminance.LightMode)).to.equal(neutralPalette.get(NeutralPaletteLightModeOffsets.L1)) + }); + it("should return values from 1 when in dark mode", (): void => { + expect(neutralLayer1(neutralPalette, StandardLuminance.DarkMode)).to.equal(neutralPalette.get(NeutralPaletteDarkModeOffsets.L1)) + }); + }); + + describe("2", (): void => { + it("should return values from 2 when in light mode", (): void => { + expect(neutralLayer2(neutralPalette, StandardLuminance.LightMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteLightModeOffsets.L2)) + }); + it("should return values from 2 when in dark mode", (): void => { + expect(neutralLayer2(neutralPalette, StandardLuminance.DarkMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteDarkModeOffsets.L2)) + }); + }); + + describe("3", (): void => { + it("should return values from 3 when in light mode", (): void => { + expect(neutralLayer3(neutralPalette, StandardLuminance.LightMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteLightModeOffsets.L3)) + }); + it("should return values from 3 when in dark mode", (): void => { + expect(neutralLayer3(neutralPalette, StandardLuminance.DarkMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteDarkModeOffsets.L3)) + }); + }); + + describe("4", (): void => { + it("should return values from 4 when in light mode", (): void => { + expect(neutralLayer4(neutralPalette, StandardLuminance.LightMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteLightModeOffsets.L4)) + }); + it("should return values from 4 when in dark mode", (): void => { + expect(neutralLayer4(neutralPalette, StandardLuminance.DarkMode, 3, 7, 10, 5)).to.equal(neutralPalette.get(NeutralPaletteDarkModeOffsets.L4)) + }); + }); + + describe("neutralLayerFloating", (): void => { + it("should return a color from the neutral palette", (): void => { + expect(neutralPalette.swatches.includes(neutralLayerFloating(neutralPalette, StandardLuminance.LightMode, 3) as SwatchRGB)).to.be.true; + }); + }); +}); diff --git a/packages/components/src/color/recipes/neutral-stroke-divider.ts b/packages/components/src/color/recipes/neutral-stroke-divider.ts new file mode 100644 index 00000000..d011ff3e --- /dev/null +++ b/packages/components/src/color/recipes/neutral-stroke-divider.ts @@ -0,0 +1,21 @@ +import { Swatch } from "../swatch.js"; +import { Palette } from "../palette.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** + * The neutralStrokeDivider color recipe + * @param palette - The palette to operate on + * @param reference - The reference color + * @param delta - The offset from the reference + * + * @internal + */ +export function neutralStrokeDivider( + palette: Palette, + reference: Swatch, + delta: number +): Swatch { + return palette.get( + palette.closestIndexOf(reference) + directionByIsDark(reference) * delta + ); +} diff --git a/packages/components/src/color/recipes/neutral-stroke.ts b/packages/components/src/color/recipes/neutral-stroke.ts new file mode 100644 index 00000000..1107dee2 --- /dev/null +++ b/packages/components/src/color/recipes/neutral-stroke.ts @@ -0,0 +1,31 @@ +import { Palette } from "../palette.js"; +import { InteractiveSwatchSet } from "../recipe.js"; +import { Swatch } from "../swatch.js"; +import { directionByIsDark } from "../utilities/direction-by-is-dark.js"; + +/** + * @internal + */ +export function neutralStroke( + palette: Palette, + reference: Swatch, + restDelta: number, + hoverDelta: number, + activeDelta: number, + focusDelta: number +): InteractiveSwatchSet { + const referenceIndex = palette.closestIndexOf(reference); + const direction = directionByIsDark(reference); + + const restIndex = referenceIndex + direction * restDelta; + const hoverIndex = restIndex + direction * (hoverDelta - restDelta); + const activeIndex = restIndex + direction * (activeDelta - restDelta); + const focusIndex = restIndex + direction * (focusDelta - restDelta); + + return { + rest: palette.get(restIndex), + hover: palette.get(hoverIndex), + active: palette.get(activeIndex), + focus: palette.get(focusIndex), + }; +} diff --git a/packages/components/src/color/swatch.spec.ts b/packages/components/src/color/swatch.spec.ts new file mode 100644 index 00000000..5e7a763e --- /dev/null +++ b/packages/components/src/color/swatch.spec.ts @@ -0,0 +1,38 @@ + +import { expect } from "chai"; +import { SwatchRGB, isSwatchRGB } from "./swatch.js"; + +const test: SwatchRGB = { + r: 0, + g: 0, + b: 0, + relativeLuminance: 0, + contrast: () => 1, + toColorString: () => "" +} + +describe("isSwatchRGB", () => { + it("should return true when called with the product of SwatchRGB.create()", () => { + expect(isSwatchRGB(SwatchRGB.create(1, 1, 1))).to.be.true; + }); + + it("should return true when called with an object conforming to the interface", () => { + expect(isSwatchRGB(test)).to.be.true; + }) + + for (const key in test ) { + it(`should return false when called with an object missing the ${key} property`, () => { + const _test = {...test}; + delete _test[key]; + + expect(isSwatchRGB(_test)).to.be.false; + }); + + it(`should return false when called with an object with the ${key} property assigned to a mismatching type`, () => { + const _test = {...test}; + _test[key] = "foobar"; + + expect(isSwatchRGB(_test)).to.be.false; + }) + } +}); diff --git a/packages/components/src/color/swatch.ts b/packages/components/src/color/swatch.ts new file mode 100644 index 00000000..9d1b0f9a --- /dev/null +++ b/packages/components/src/color/swatch.ts @@ -0,0 +1,78 @@ +import { ColorRGBA64, rgbToRelativeLuminance } from "@microsoft/fast-colors"; +import { contrast, RelativeLuminance } from "./utilities/relative-luminance.js"; + +/** + * Represents a color in a {@link Palette} + * @public + */ +export interface Swatch extends RelativeLuminance { + toColorString(): string; + contrast(target: RelativeLuminance): number; +} + +/** @public */ +export interface SwatchRGB extends Swatch { + r: number; + g: number; + b: number; +} + +/** @public */ +export const SwatchRGB = Object.freeze({ + create(r: number, g: number, b: number): SwatchRGB { + return new SwatchRGBImpl(r, g, b); + }, + from(obj: { r: number; g: number; b: number }): SwatchRGB { + return new SwatchRGBImpl(obj.r, obj.g, obj.b); + }, +}); + +/** + * Runtime test for an objects conformance with the SwatchRGB interface. + * @internal + */ +export function isSwatchRGB(value: { [key: string]: any }): value is SwatchRGB { + const test = { + r: 0, + g: 0, + b: 0, + toColorString: () => "", + contrast: () => 0, + relativeLuminance: 0, + } satisfies SwatchRGB; + + for (const key in test) { + // @ts-expect-error swatch has no index + if (typeof test[key] !== typeof value[key]) { + return false; + } + } + + return true; +} +/** + * A RGB implementation of {@link Swatch} + * @internal + */ +class SwatchRGBImpl extends ColorRGBA64 implements Swatch { + readonly relativeLuminance: number; + + /** + * + * @param red - Red channel expressed as a number between 0 and 1 + * @param green - Green channel expressed as a number between 0 and 1 + * @param blue - Blue channel expressed as a number between 0 and 1 + */ + constructor(red: number, green: number, blue: number) { + super(red, green, blue, 1); + this.relativeLuminance = rgbToRelativeLuminance(this); + } + + public toColorString = this.toStringHexRGB; + public contrast = contrast.bind(null, this); + public createCSS = this.toColorString; + + static fromObject(obj: { r: number; g: number; b: number }) { + return new SwatchRGBImpl(obj.r, obj.g, obj.b); + } +} diff --git a/packages/components/src/color/utilities/base-layer-luminance.ts b/packages/components/src/color/utilities/base-layer-luminance.ts new file mode 100644 index 00000000..74a0f0d8 --- /dev/null +++ b/packages/components/src/color/utilities/base-layer-luminance.ts @@ -0,0 +1,22 @@ +import { SwatchRGB } from "../swatch.js"; + +export function baseLayerLuminanceSwatch(luminance: number) { + return SwatchRGB.create(luminance, luminance, luminance); +} + +/** + * Recommended values for light and dark mode for {@link @microsoft/fast-components#baseLayerLuminance}. + * + * @public + */ +export const StandardLuminance = { + LightMode: 1, + DarkMode: 0.23, +} as const; + +/** + * Types of recommended values for light and dark mode for {@link @microsoft/fast-components#baseLayerLuminance}. + * + * @public + */ +export type StandardLuminance = typeof StandardLuminance[keyof typeof StandardLuminance]; diff --git a/packages/components/src/color/utilities/binary-search.ts b/packages/components/src/color/utilities/binary-search.ts new file mode 100644 index 00000000..25f3b4cc --- /dev/null +++ b/packages/components/src/color/utilities/binary-search.ts @@ -0,0 +1,31 @@ +/** + * @internal + */ +export function binarySearch( + valuesToSearch: T[] | ReadonlyArray, + searchCondition: (value: T) => boolean, + startIndex: number = 0, + endIndex: number = valuesToSearch.length - 1 +): T { + if (endIndex === startIndex) { + return valuesToSearch[startIndex]; + } + + const middleIndex: number = Math.floor((endIndex - startIndex) / 2) + startIndex; + + // Check to see if this passes on the item in the center of the array + // if it does check the previous values + return searchCondition(valuesToSearch[middleIndex]) + ? binarySearch( + valuesToSearch, + searchCondition, + startIndex, + middleIndex // include this index because it passed the search condition + ) + : binarySearch( + valuesToSearch, + searchCondition, + middleIndex + 1, // exclude this index because it failed the search condition + endIndex + ); +} diff --git a/packages/components/src/color/utilities/color-constants.ts b/packages/components/src/color/utilities/color-constants.ts new file mode 100644 index 00000000..c975d31d --- /dev/null +++ b/packages/components/src/color/utilities/color-constants.ts @@ -0,0 +1,30 @@ +import { parseColorHexRGB } from "@microsoft/fast-colors"; +import { SwatchRGB } from "../swatch.js"; + +/** + * @internal + */ +export const white = SwatchRGB.create(1, 1, 1); +/** + * @internal + */ +export const black = SwatchRGB.create(0, 0, 0); + +/** + * @internal + */ +/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ +export const middleGrey = SwatchRGB.from(parseColorHexRGB("#808080")!); + +/** + * @internal + */ +/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ +export const accentBase = SwatchRGB.from(parseColorHexRGB("#DA1A5F")!); + +/** + * @internal + */ +/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ +export const errorBase = SwatchRGB.from(parseColorHexRGB('#D32F2F')! +); diff --git a/packages/components/src/color/utilities/direction-by-is-dark.ts b/packages/components/src/color/utilities/direction-by-is-dark.ts new file mode 100644 index 00000000..fc6b7092 --- /dev/null +++ b/packages/components/src/color/utilities/direction-by-is-dark.ts @@ -0,0 +1,9 @@ +import { Swatch } from "../swatch.js"; +import { isDark } from "./is-dark.js"; + +/** + * @internal + */ +export function directionByIsDark(color: Swatch): 1 | -1 { + return isDark(color) ? -1 : 1; +} diff --git a/packages/components/src/color/utilities/is-dark.ts b/packages/components/src/color/utilities/is-dark.ts new file mode 100644 index 00000000..b09225e4 --- /dev/null +++ b/packages/components/src/color/utilities/is-dark.ts @@ -0,0 +1,20 @@ +import { Swatch } from "../swatch.js"; + +/* + * A color is in "dark" if there is more contrast between #000000 and a reference + * color than #FFFFFF and the reference color. That threshold can be expressed as a relative luminance + * using the contrast formula as (1 + 0.5) / (R + 0.05) === (R + 0.05) / (0 + 0.05), + * which reduces to the following, where 'R' is the relative luminance of the reference color + */ +const target = (-0.1 + Math.sqrt(0.21)) / 2; + +/** + * Determines if a color should be considered Dark Mode + * @param color - The color to check to mode of + * @returns boolean + * + * @public + */ +export function isDark(color: Swatch): boolean { + return color.relativeLuminance <= target; +} diff --git a/packages/components/src/color/utilities/relative-luminance.ts b/packages/components/src/color/utilities/relative-luminance.ts new file mode 100644 index 00000000..115c1faa --- /dev/null +++ b/packages/components/src/color/utilities/relative-luminance.ts @@ -0,0 +1,19 @@ +/** + * @public + */ +export interface RelativeLuminance { + /** + * A number between 0 and 1, calculated by {@link https://www.w3.org/WAI/GL/wiki/Relative_luminance} + */ + readonly relativeLuminance: number; +} + +/** + * @internal + */ +export function contrast(a: RelativeLuminance, b: RelativeLuminance): number { + const L1 = a.relativeLuminance > b.relativeLuminance ? a : b; + const L2 = a.relativeLuminance > b.relativeLuminance ? b : a; + + return (L1.relativeLuminance + 0.05) / (L2.relativeLuminance + 0.05); +} diff --git a/packages/components/src/combobox/combobox.styles.ts b/packages/components/src/combobox/combobox.styles.ts index c6dd9892..0dd87051 100644 --- a/packages/components/src/combobox/combobox.styles.ts +++ b/packages/components/src/combobox/combobox.styles.ts @@ -1,42 +1,38 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - ComboboxOptions, - disabledCursor, - focusVisible, - FoundationElementTemplate -} from '@microsoft/fast-foundation'; + ComboboxOptions, + disabledCursor, + focusVisible, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; import { accentFillFocus, - focusStrokeWidth, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { selectStyles } from '../select/select.styles'; + focusStrokeWidth, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { selectStyles } from "../select/select.styles.js"; /** * Styles for Combobox * @public */ -export const comboboxStyles: FoundationElementTemplate< - ElementStyles, - ComboboxOptions -> = (context, definition) => css` - ${selectStyles(context, definition)} +export const comboboxStyles: FoundationElementTemplate = ( + context, + definition +) => css` + ${selectStyles(context, definition)} - :host(:empty) .listbox { - display: none; - } + :host(:empty) .listbox { + display: none; + } - :host([disabled]) *, - :host([disabled]) { - cursor: ${disabledCursor}; - user-select: none; - } + :host([disabled]) *, + :host([disabled]) { + cursor: ${disabledCursor}; + user-select: none; + } :host(:focus-within:not([disabled])) { border-color: ${accentFillFocus}; @@ -44,22 +40,22 @@ export const comboboxStyles: FoundationElementTemplate< ${accentFillFocus}; } - .selected-value { - -webkit-appearance: none; - background: transparent; - border: none; - color: inherit; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - height: calc(100% - (${strokeWidth} * 1px)); - margin: auto 0; - width: 100%; - } + .selected-value { + -webkit-appearance: none; + background: transparent; + border: none; + color: inherit; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + height: calc(100% - (${strokeWidth} * 1px)); + margin: auto 0; + width: 100%; + } - .selected-value:hover, - .selected-value:${focusVisible}, - .selected-value:disabled, - .selected-value:active { - outline: none; - } + .selected-value:hover, + .selected-value:${focusVisible}, + .selected-value:disabled, + .selected-value:active { + outline: none; + } `; diff --git a/packages/components/src/combobox/index.ts b/packages/components/src/combobox/index.ts index f4c5ab8e..da2436bd 100644 --- a/packages/components/src/combobox/index.ts +++ b/packages/components/src/combobox/index.ts @@ -1,16 +1,14 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { attr } from '@microsoft/fast-element'; +import { attr, css, ElementStyles } from "@microsoft/fast-element"; +import type { ComboboxOptions } from "@microsoft/fast-foundation"; import { - Combobox as FoundationCombobox, - ComboboxOptions, - comboboxTemplate as template -} from '@microsoft/fast-foundation'; -import { comboboxStyles as styles } from './combobox.styles'; + Combobox as FoundationCombobox, + comboboxTemplate as template, +} from "@microsoft/fast-foundation"; +import { heightNumberAsToken } from "../design-tokens.js"; +import { comboboxStyles as styles } from "./combobox.styles.js"; /** - * Base class for Select + * Base class for Combobox. * @public */ export class Combobox extends FoundationCombobox { @@ -95,6 +93,43 @@ export class Combobox extends FoundationCombobox { Object.assign(this.style, { width: `${listWidth}px` }); } } + + /** + * An internal stylesheet to hold calculated CSS custom properties. + * + * @internal + */ + private computedStylesheet?: ElementStyles; + + /** + * @internal + */ + protected maxHeightChanged(prev: number | undefined, next: number): void { + this.updateComputedStylesheet(); + } + + /** + * Updates an internal stylesheet with calculated CSS custom properties. + * + * @internal + */ + protected updateComputedStylesheet(): void { + if (this.computedStylesheet) { + this.$fastController.removeStyles(this.computedStylesheet); + } + + const popupMaxHeight = Math.floor( + this.maxHeight / heightNumberAsToken.getValueFor(this) + ).toString(); + + this.computedStylesheet = css` + :host { + --listbox-max-height: ${popupMaxHeight}; + } + `; + + this.$fastController.addStyles(this.computedStylesheet); + } } /** @@ -107,25 +142,25 @@ export class Combobox extends FoundationCombobox { * */ export const jpCombobox = Combobox.compose({ - baseName: 'combobox', - baseClass: FoundationCombobox, - template, - styles, - shadowOptions: { - delegatesFocus: true - }, - indicator: /* html */ ` - - - - ` + baseName: 'combobox', + baseClass: FoundationCombobox, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, + indicator: /* html */ ` + + + + `, }); -export { styles as comboboxStyles }; +export { styles as comboboxStyles }; \ No newline at end of file diff --git a/packages/components/src/custom-elements.ts b/packages/components/src/custom-elements.ts index 5c54f482..b3104188 100644 --- a/packages/components/src/custom-elements.ts +++ b/packages/components/src/custom-elements.ts @@ -2,90 +2,103 @@ // Distributed under the terms of the Modified BSD License. import type { Container } from '@microsoft/fast-foundation'; -import { jpAccordion } from './accordion/index'; -import { jpAccordionItem } from './accordion-item/index'; -import { jpAnchor } from './anchor/index'; -import { jpAnchoredRegion } from './anchored-region/index'; -import { jpAvatar } from './avatar/index'; -import { jpBadge } from './badge/index'; -import { jpBreadcrumb } from './breadcrumb/index'; -import { jpBreadcrumbItem } from './breadcrumb-item/index'; -import { jpButton } from './button/index'; -import { jpCard } from './card/index'; -import { jpCheckbox } from './checkbox/index'; -import { jpCombobox } from './combobox/index'; -import { jpDataGrid, jpDataGridCell, jpDataGridRow } from './data-grid/index'; -import { jpDateField } from './date-field/index'; -import { jpDialog } from './dialog/index'; -import { jpDivider } from './divider/index'; -import { jpListbox } from './listbox/index'; -import { jpMenu } from './menu/index'; -import { jpMenuItem } from './menu-item/index'; -import { jpNumberField } from './number-field/index'; -import { jpOption } from './option/index'; -import { jpProgress } from './progress/index'; -import { jpProgressRing } from './progress-ring/index'; -import { jpRadio } from './radio/index'; -import { jpRadioGroup } from './radio-group/index'; -import { jpSearch } from './search/index'; -import { jpSelect } from './select/index'; -import { jpSlider } from './slider/index'; -import { jpSliderLabel } from './slider-label/index'; -import { jpSwitch } from './switch/index'; -import { jpTabPanel } from './tab-panel/index'; -import { jpTab } from './tab/index'; -import { jpTabs } from './tabs/index'; -import { jpTextArea } from './text-area/index'; -import { jpTextField } from './text-field/index'; -import { jpToolbar } from './toolbar/index'; -import { jpTooltip } from './tooltip/index'; -import { jpTreeItem } from './tree-item/index'; -import { jpTreeView } from './tree-view/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 { Accordion } from './accordion/index'; -import type { AccordionItem } from './accordion-item/index'; -import type { Anchor } from './anchor/index'; -import type { AnchoredRegion } from './anchored-region/index'; -import type { Avatar } from './avatar/index'; -import type { Badge } from './badge/index'; -import type { Breadcrumb } from './breadcrumb/index'; -import type { BreadcrumbItem } from './breadcrumb-item/index'; -import type { Button } from './button/index'; -import type { Card } from './card/index'; -import type { Checkbox } from './checkbox/index'; -import type { Combobox } from './combobox/index'; -import type { DataGrid, DataGridCell, DataGridRow } from './data-grid/index'; -import type { DateField } from './date-field/index'; -import type { Dialog } from './dialog/index'; -import type { Divider } from './divider/index'; -import type { ListboxElement } from './listbox/index'; -import type { Menu } from './menu/index'; -import type { MenuItem } from './menu-item/index'; -import type { NumberField } from './number-field/index'; -import type { Option } from './option/index'; -import type { Progress } from './progress/index'; -import type { ProgressRing } from './progress-ring/index'; -import type { Radio } from './radio/index'; -import type { RadioGroup } from './radio-group/index'; -import type { Search } from './search/index'; -import type { Select } from './select/index'; -import type { Slider } from './slider/index'; -import type { SliderLabel } from './slider-label/index'; -import type { Switch } from './switch/index'; -import type { TabPanel } from './tab-panel/index'; -import type { Tab } from './tab/index'; -import type { Tabs } from './tabs/index'; -import type { TextArea } from './text-area/index'; -import type { TextField } from './text-field/index'; -import type { Toolbar } from './toolbar/index'; -import type { Tooltip } from './tooltip/index'; -import type { TreeItem } from './tree-item/index'; -import type { TreeView } from './tree-view/index'; +import type { Accordion } from './accordion/index.js'; +import type { AccordionItem } from './accordion-item/index.js'; +import type { Anchor } from './anchor/index.js'; +import type { AnchoredRegion } from './anchored-region/index.js'; +import type { Avatar } from './avatar/index.js'; +import type { Badge } from './badge/index.js'; +import type { Breadcrumb } from './breadcrumb/index.js'; +import type { BreadcrumbItem } from './breadcrumb-item/index.js'; +import type { Button } from './button/index.js'; +import type { Card } from './card/index.js'; +import type { Checkbox } from './checkbox/index.js'; +import type { Combobox } from './combobox/index.js'; +import type { DataGrid, DataGridCell, DataGridRow } from './data-grid/index.js'; +import type { DateField } from './date-field/index.js'; +import type { Dialog } from './dialog/index.js'; +import type { Divider } from './divider/index.js'; +import type { Listbox } from './listbox/index.js'; +import type { Menu } from './menu/index.js'; +import type { MenuItem } from './menu-item/index.js'; +import type { NumberField } from './number-field/index.js'; +import type { Option } from './option/index.js'; +import type { Progress } from './progress/index.js'; +import type { ProgressRing } from './progress-ring/index.js'; +import type { Radio } from './radio/index.js'; +import type { RadioGroup } from './radio-group/index.js'; +import type { Search } from './search/index.js'; +import type { Select } from './select/index.js'; +import type { Slider } from './slider/index.js'; +import type { SliderLabel } from './slider-label/index.js'; +import type { Switch } from './switch/index.js'; +import type { TabPanel } from './tab-panel/index.js'; +import type { Tab } from './tab/index.js'; +import type { Tabs } from './tabs/index.js'; +import type { TextArea } from './text-area/index.js'; +import type { TextField } from './text-field/index.js'; +import type { Toolbar } from './toolbar/index.js'; +import type { Tooltip } from './tooltip/index.js'; +import type { TreeItem } from './tree-item/index.js'; +import type { TreeView } from './tree-view/index.js'; + +/** + * Export all custom element definitions + */ + +import { jpAccordion } from './accordion/index.js'; +import { jpAccordionItem } from './accordion-item/index.js'; +import { jpAnchor } from './anchor/index.js'; +import { jpAnchoredRegion } from './anchored-region/index.js'; +import { jpAvatar } from './avatar/index.js'; +import { jpBadge } from './badge/index.js'; +import { jpBreadcrumb } from './breadcrumb/index.js'; +import { jpBreadcrumbItem } from './breadcrumb-item/index.js'; +import { jpButton } from './button/index.js'; +import { jpCard } from './card/index.js'; +import { jpCheckbox } from './checkbox/index.js'; +import { jpCombobox } from './combobox/index.js'; +import { jpDataGrid, jpDataGridCell, jpDataGridRow } from './data-grid/index.js'; +import { jpDateField } from './date-field/index.js'; +/** + * Don't remove. This is needed to prevent api-extractor errors. + */ +// import type { DesignSystemProvider } from "./design-system-provider/index.js"; +import { jpDialog } from './dialog/index.js'; +import { jpDivider } from './divider/index.js'; +import { jpListbox } from './listbox/index.js'; +import { jpMenu } from './menu/index.js'; +import { jpMenuItem } from './menu-item/index.js'; +import { jpNumberField } from './number-field/index.js'; +import { jpOption } from './option/index.js'; +import { jpProgress } from './progress/index.js'; +import { jpProgressRing } from './progress-ring/index.js'; +import { jpRadio } from './radio/index.js'; +import { jpRadioGroup } from './radio-group/index.js'; +import { jpSearch } from './search/index.js'; +import { jpSelect } from './select/index.js'; +import { jpSlider } from './slider/index.js'; +import { jpSliderLabel } from './slider-label/index.js'; +import { jpSwitch } from './switch/index.js'; +import { jpTabPanel } from './tab-panel/index.js'; +import { jpTab } from './tab/index.js'; +import { jpTabs } from './tabs/index.js'; +import { jpTextArea } from './text-area/index.js'; +import { jpTextField } from './text-field/index.js'; +import { jpToolbar } from './toolbar/index.js'; +import { jpTooltip } from './tooltip/index.js'; +import { jpTreeItem } from './tree-item/index.js'; +import { jpTreeView } from './tree-view/index.js'; + +// When adding new components, make sure to add the component to the `allComponents` object +// in addition to exporting the component by name. Ideally we would be able to just add +// `export * as allComponents from "./custom-elements" from src/index.ts but API extractor +// throws for `export * as` expressions. https://github.com/microsoft/rushstack/pull/1796S -// export all components export { jpAccordion, jpAccordionItem, diff --git a/packages/components/src/data-grid/data-grid-cell.styles.ts b/packages/components/src/data-grid/data-grid-cell.styles.ts index 76219d04..12c94c13 100644 --- a/packages/components/src/data-grid/data-grid-cell.styles.ts +++ b/packages/components/src/data-grid/data-grid-cell.styles.ts @@ -1,68 +1,68 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; import { accentFillFocus, - bodyFont, - controlCornerRadius, - designUnit, - focusStrokeWidth, - neutralForegroundRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; + bodyFont, + controlCornerRadius, + designUnit, + focusStrokeOuter, + focusStrokeWidth, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; /** * Styles for Data Grid cell * @public */ export const dataGridCellStyles: FoundationElementTemplate = ( - context, - definition + context, + definition ) => - css` - :host { - padding: calc(${designUnit} * 1px) calc(${designUnit} * 3px); - color: ${neutralForegroundRest}; - box-sizing: border-box; - font-family: ${bodyFont}; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - border: transparent calc(${strokeWidth} * 1px) solid; - font-weight: 400; - overflow: hidden; - white-space: nowrap; - border-radius: calc(${controlCornerRadius} * 1px); - } + css` + :host { + padding: calc(${designUnit} * 1px) calc(${designUnit} * 3px); + color: ${neutralForegroundRest}; + box-sizing: border-box; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + font-weight: 400; + border: transparent calc(${focusStrokeWidth} * 1px) solid; + overflow: hidden; + white-space: nowrap; + border-radius: calc(${controlCornerRadius} * 1px); + } - :host(.column-header) { - font-weight: 600; - } + :host(.column-header) { + font-weight: 600; + } - :host(:${focusVisible}) { - outline: calc(${focusStrokeWidth} * 1px) solid ${accentFillFocus}; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host { - forced-color-adjust: none; - border-color: transparent; - background: ${SystemColors.Field}; - color: ${SystemColors.FieldText}; - } + :host(:${focusVisible}) { + outline: calc(${focusStrokeWidth} * 1px) solid ${accentFillFocus}; + color: ${neutralForegroundRest}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + forced-color-adjust: none; + border-color: transparent; + background: ${SystemColors.Field}; + color: ${SystemColors.FieldText}; + } - :host(:${focusVisible}) { - border-color: ${SystemColors.FieldText}; - box-shadow: 0 0 0 2px inset ${SystemColors.Field}; - } - `) - ); + :host(:${focusVisible}) { + border-color: ${SystemColors.FieldText}; + box-shadow: 0 0 0 2px inset ${SystemColors.Field}; + color: ${SystemColors.FieldText}; + } + ` + ) + ); diff --git a/packages/components/src/data-grid/data-grid-row.styles.ts b/packages/components/src/data-grid/data-grid-row.styles.ts new file mode 100644 index 00000000..3fc4da5a --- /dev/null +++ b/packages/components/src/data-grid/data-grid-row.styles.ts @@ -0,0 +1,33 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + neutralFillRest, + neutralStrokeDividerRest, + strokeWidth, +} from "../design-tokens.js"; + +/** + * Styles for Data Grid row + * @public + */ +export const dataGridRowStyles: FoundationElementTemplate = ( + context, + definition +) => css` + :host { + display: grid; + padding: 1px 0; + box-sizing: border-box; + width: 100%; + border-bottom: calc(${strokeWidth} * 1px) solid ${neutralStrokeDividerRest}; + } + + :host(.header) { + } + + :host(.sticky-header) { + background: ${neutralFillRest}; + position: sticky; + top: 0; + } +`; diff --git a/packages/components/src/data-grid/data-grid.styles.ts b/packages/components/src/data-grid/data-grid.styles.ts new file mode 100644 index 00000000..82976b68 --- /dev/null +++ b/packages/components/src/data-grid/data-grid.styles.ts @@ -0,0 +1,17 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; + +/** + * Styles for Data Grid + * @public + */ +export const dataGridStyles: FoundationElementTemplate = ( + context, + definition +) => css` + :host { + display: flex; + position: relative; + flex-direction: column; + } +`; diff --git a/packages/components/src/data-grid/index.ts b/packages/components/src/data-grid/index.ts index 1b0251d3..13026ee1 100644 --- a/packages/components/src/data-grid/index.ts +++ b/packages/components/src/data-grid/index.ts @@ -1,16 +1,14 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - DataGrid, - DataGridCell, - dataGridCellTemplate, - DataGridRow, - dataGridRowTemplate, - dataGridTemplate -} from '@microsoft/fast-foundation'; -import { dataGridStyles, dataGridRowStyles } from '@microsoft/fast-components'; -import { dataGridCellStyles } from './data-grid-cell.styles'; + DataGrid, + DataGridCell, + dataGridCellTemplate, + DataGridRow, + dataGridRowTemplate, + dataGridTemplate, +} from "@microsoft/fast-foundation"; +import { dataGridStyles } from "./data-grid.styles.js"; +import { dataGridRowStyles } from "./data-grid-row.styles.js"; +import { dataGridCellStyles } from "./data-grid-cell.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#DataGridCell} registration for configuring the component with a DesignSystem. @@ -20,9 +18,9 @@ import { dataGridCellStyles } from './data-grid-cell.styles'; * Generates HTML Element: `` */ export const jpDataGridCell = DataGridCell.compose({ - baseName: 'data-grid-cell', - template: dataGridCellTemplate, - styles: dataGridCellStyles + baseName: 'data-grid-cell', + template: dataGridCellTemplate, + styles: dataGridCellStyles, }); /** @@ -33,9 +31,9 @@ export const jpDataGridCell = DataGridCell.compose({ * Generates HTML Element: `` */ export const jpDataGridRow = DataGridRow.compose({ - baseName: 'data-grid-row', - template: dataGridRowTemplate, - styles: dataGridRowStyles + baseName: 'data-grid-row', + template: dataGridRowTemplate, + styles: dataGridRowStyles, }); /** @@ -46,9 +44,9 @@ export const jpDataGridRow = DataGridRow.compose({ * Generates HTML Element: `` */ export const jpDataGrid = DataGrid.compose({ - baseName: 'data-grid', - template: dataGridTemplate, - styles: dataGridStyles + baseName: 'data-grid', + template: dataGridTemplate, + styles: dataGridStyles, }); /** diff --git a/packages/components/src/design-system-provider/README.md b/packages/components/src/design-system-provider/README.md new file mode 100644 index 00000000..1160d215 --- /dev/null +++ b/packages/components/src/design-system-provider/README.md @@ -0,0 +1,68 @@ +# Design System Provider +For more information view the [design system provider readme](https://github.com/microsoft/fast/tree/master/packages/components/fast-foundation/src/design-system-provider/README.md). + +### Jupyter Design System Properties +|Property Name|Type|Attribute Name|CSS Custom property| +|---|---|---|---| +|fillColor|string| fill-color | fill-color | +|neutralPalette|string[]| N/A | N/A | +|accentPalette|string[]| N/A | N/A | +|density|DensityOffset| number | density | +|designUnit|number| number | design-unit | +|baseHeightMultiplier|number| base-height-multiplier | base-height-multiplier | +|baseHorizontalSpacingMultiplier|number| base-horizontal-spacing-multiplier | base-horizontal-spacing-multiplier | +|controlCornerRadius|number| corner-radius | corner-radius | +|strokeWidth|number| stroke-width | stroke-width | +|focusStrokeWidth|number| focus-stroke-width | focus-stroke-width | +|disabledOpacity|number| disabled-opacity | disabled-opacity | +|typeRampMinus2FontSize | string | type-ramp-minus-2-font-size | type-ramp-minus-2-font-size | +|typeRampMinus2LineHeight | string | type-ramp-minus-2-line-height | type-ramp-minus-2-line-height | +|typeRampMinus1FontSize | string | type-ramp-minus-1-font-size | type-ramp-minus-1-font-size | +|typeRampMinus1LineHeight | string | type-ramp-minus-1-line-height | type-ramp-minus-1-line-height | +|typeRampBaseFontSize | string | type-ramp-base-font-size | type-ramp-base-font-size | +|typeRampBaseLineHeight | string | type-ramp-base-line-height | type-ramp-base-line-height | +|typeRampPlus1FontSize | string | type-ramp-plus-1-font-size | type-ramp-plus-1-font-size | +|typeRampPlus1LineHeight | string | type-ramp-plus-1-line-height | type-ramp-plus-1-line-height | +|typeRampPlus2FontSize | string | type-ramp-plus-2-font-size | type-ramp-plus-2-font-size | +|typeRampPlus2LineHeight | string | type-ramp-plus-2-line-height | type-ramp-plus-2-line-height | +|typeRampPlus3FontSize | string | type-ramp-plus-3-font-size | type-ramp-plus-3-font-size | +|typeRampPlus3LineHeight | string | type-ramp-plus-3-line-height | type-ramp-plus-3-line-height | +|typeRampPlus4FontSize | string | type-ramp-plus-4-font-size | type-ramp-plus-4-font-size | +|typeRampPlus4LineHeight | string | type-ramp-plus-4-line-height | type-ramp-plus-4-line-height | +|typeRampPlus5FontSize | string | type-ramp-plus-5-font-size | type-ramp-plus-5-font-size | +|typeRampPlus5LineHeight | string | type-ramp-plus-5-line-height | type-ramp-plus-5-line-height | +|typeRampPlus6FontSize | string | type-ramp-plus-6-font-size | type-ramp-plus-6-font-size | +|typeRampPlus6LineHeight | string | type-ramp-plus-6-line-height | type-ramp-plus-6-line-height | +|accentFillRestDelta|number| accent-fill-rest-delta | N/A | +|accentFillHoverDelta|number| accent-fill-hover-delta | N/A | +|accentFillActiveDelta|number| accent-fill-active-delta | N/A | +|accentFillFocusDelta|number| accent-fill-focus-delta | N/A | +|accentForegroundRestDelta|number| accent-foreground-rest-delta | N/A | +|accentForegroundHoverDelta|number| accent-foreground-hover-delta | N/A | +|accentForegroundActiveDelta|number| accent-foreground-active-delta | N/A | +|accentForegroundFocusDelta|number| accent-foreground-focus-delta | N/A | +|neutralFillRestDelta|number| neutral-fill-rest-delta | N/A | +|neutralFillHoverDelta|number| neutral-fill-hover-delta | N/A | +|neutralFillActiveDelta|number| neutral-fill-active-delta | N/A | +|neutralFillFocusDelta|number| neutral-fill-focus-delta | N/A | +|neutralFillInputRestDelta|number| neutral-fill-input-rest-delta | N/A | +|neutralFillInputHoverDelta|number| neutral-fill-input-hover-delta | N/A | +|neutralFillInputActiveDelta|number| neutral-fill-input-active-delta | N/A | +|neutralFillInputFocusDelta|number| neutral-fill-input-focus-delta | N/A | +|neutralFillStealthRestDelta|number| neutral-fill-stealth-rest-delta | N/A | +|neutralFillStealthHoverDelta|number| neutral-fill-stealth-hover-delta | N/A | +|neutralFillStealthActiveDelta|number| neutral-fill-stealth-active-delta | N/A | +|neutralFillStealthFocusDelta|number| neutral-fill-stealth-focus-delta | N/A | +|neutralFillStrongHoverDelta|number| neutral-fill-strong-hover-delta | N/A | +|neutralFillStrongActiveDelta|number| neutral-fill-strong-hover-active | N/A | +|neutralFillStrongFocusDelta|number| neutral-fill-strong-hover-focus | N/A | +|baseLayerLuminance|number base-layer-luminance| | N/A | +|neutralFillLayerRestDelta|number| neutral-fill-layer-rest-delta | N/A | +|neutralForegroundHoverDelta|number| neutral-foreground-hover-delta | N/A | +|neutralForegroundActiveDelta|number| neutral-foreground-active-delta | N/A | +|neutralForegroundFocusDelta|number| neutral-foreground-focus-delta | N/A | +|neutralStrokeDividerRestDelta|number| neutral-stroke-divider-rest-delta | N/A | +|neutralStrokeRestDelta|number| neutral-stroke-rest-delta | N/A | +|neutralStrokeHoverDelta|number| neutral-stroke-hover-delta | N/A | +|neutralStrokeActiveDelta|number| neutral-stroke-active-delta | N/A | +|neutralStrokeFocusDelta|number| neutral-stroke-focus-delta | N/A | diff --git a/packages/components/src/design-system-provider/index.ts b/packages/components/src/design-system-provider/index.ts new file mode 100644 index 00000000..b57f7778 --- /dev/null +++ b/packages/components/src/design-system-provider/index.ts @@ -0,0 +1,1088 @@ +import { parseColorHexRGB } from "@microsoft/fast-colors"; +import { + attr, + css, + html, + nullableNumberConverter, + Observable, + ValueConverter, +} from "@microsoft/fast-element"; +import { + DesignToken, + DesignTokenValue, + display, + ElementDefinitionContext, + forcedColorsStylesheetBehavior, + FoundationElement, + FoundationElementDefinition, +} from "@microsoft/fast-foundation"; +import { Direction, SystemColors } from "@microsoft/fast-web-utilities"; +import { Swatch, SwatchRGB } from "../color/swatch.js"; +import { + accentColor, + accentFillActiveDelta, + accentFillFocusDelta, + accentFillHoverDelta, + accentFillRestDelta, + accentForegroundActiveDelta, + accentForegroundFocusDelta, + accentForegroundHoverDelta, + accentForegroundRestDelta, + baseHeightMultiplier, + baseHorizontalSpacingMultiplier, + baseLayerLuminance, + controlCornerRadius, + density, + designUnit, + direction, + disabledOpacity, + fillColor, + focusStrokeWidth, + neutralColor, + neutralFillActiveDelta, + neutralFillFocusDelta, + neutralFillHoverDelta, + neutralFillInputActiveDelta, + neutralFillInputFocusDelta, + neutralFillInputHoverDelta, + neutralFillInputRestDelta, + neutralFillLayerRestDelta, + neutralFillRestDelta, + neutralFillStealthActiveDelta, + neutralFillStealthFocusDelta, + neutralFillStealthHoverDelta, + neutralFillStealthRestDelta, + neutralFillStrongActiveDelta, + neutralFillStrongFocusDelta, + neutralFillStrongHoverDelta, + neutralForegroundRest, + neutralStrokeActiveDelta, + neutralStrokeDividerRestDelta, + neutralStrokeFocusDelta, + neutralStrokeHoverDelta, + neutralStrokeRestDelta, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, + typeRampMinus1FontSize, + typeRampMinus1LineHeight, + typeRampMinus2FontSize, + typeRampMinus2LineHeight, + typeRampPlus1FontSize, + typeRampPlus1LineHeight, + typeRampPlus2FontSize, + typeRampPlus2LineHeight, + typeRampPlus3FontSize, + typeRampPlus3LineHeight, + typeRampPlus4FontSize, + typeRampPlus4LineHeight, + typeRampPlus5FontSize, + typeRampPlus5LineHeight, + typeRampPlus6FontSize, + typeRampPlus6LineHeight, +} from "../design-tokens.js"; + +/** + * A {@link ValueConverter} that converts to and from `Swatch` values. + * @remarks + * This converter allows for colors represented as string hex values, returning `null` if the + * input was `null` or `undefined`. + * @internal + */ +const swatchConverter: ValueConverter = { + toView(value: any): string | null { + if (value === null || value === undefined) { + return null; + } + return (value as Swatch)?.toColorString(); + }, + + fromView(value: any): any { + if (value === null || value === undefined) { + return null; + } + const color = parseColorHexRGB(value); + return color ? SwatchRGB.create(color.r, color.g, color.b) : null; + }, +}; + +const backgroundStyles = css` + :host { + background-color: ${fillColor}; + color: ${neutralForegroundRest}; + } +`.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + background-color: ${SystemColors.ButtonFace}; + box-shadow: 0 0 0 1px ${SystemColors.CanvasText}; + color: ${SystemColors.ButtonText}; + } + ` + ) +); + +function designToken(token: DesignToken) { + return (source: DesignSystemProvider, key: string) => { + // @ts-expect-error source as no string index + source[key + "Changed"] = function ( + this: DesignSystemProvider, + prev: T | undefined, + next: T | undefined + ) { + if (next !== undefined && next !== null) { + token.setValueFor(this, next as DesignTokenValue); + } else { + token.deleteValueFor(this); + } + }; + }; +} + +/** + * The FAST DesignSystemProvider Element. + * @internal + */ +export class DesignSystemProvider extends FoundationElement { + constructor() { + super(); + + // If fillColor or baseLayerLuminance change, we need to + // re-evaluate whether we should have paint styles applied + const subscriber = { + handleChange: this.noPaintChanged.bind(this), + }; + Observable.getNotifier(this).subscribe(subscriber, "fillColor"); + Observable.getNotifier(this).subscribe(subscriber, "baseLayerLuminance"); + } + /** + * Used to instruct the jpDesignSystemProvider + * that it should not set the CSS + * background-color and color properties + * + * @remarks + * HTML boolean attribute: no-paint + */ + @attr({ attribute: "no-paint", mode: "boolean" }) + public noPaint = false; + private noPaintChanged() { + if (!this.noPaint && (this.fillColor !== void 0 || this.baseLayerLuminance)) { + this.$fastController.addStyles(backgroundStyles); + } else { + this.$fastController.removeStyles(backgroundStyles); + } + } + + /** + * Define design system property attributes + * @remarks + * HTML attribute: background-color + * + * CSS custom property: --fill-color + */ + @attr({ + attribute: "fill-color", + converter: swatchConverter, + }) + @designToken(fillColor) + public fillColor?: Swatch; + + /** + * Set the accent color + * @remarks + * HTML attribute: accent-color + */ + @attr({ + attribute: "accent-color", + converter: swatchConverter, + mode: "fromView", + }) + @designToken(accentColor) + public accentColor?: Swatch; + + /** + * Set the neutral color + * @remarks + * HTML attribute: neutral-color + */ + @attr({ + attribute: "neutral-color", + converter: swatchConverter, + mode: "fromView", + }) + @designToken(neutralColor) + public neutralColor?: Swatch; + + /** + * + * The density offset, used with designUnit to calculate height and spacing. + * + * @remarks + * HTML attribute: density + * + * CSS custom property: --density + */ + @attr({ + converter: nullableNumberConverter, + }) + @designToken(density) + public density?: number; + + /** + * The grid-unit that UI dimensions are derived from in pixels. + * + * @remarks + * HTML attribute: design-unit + * + * CSS custom property: --design-unit + */ + @attr({ + attribute: "design-unit", + converter: nullableNumberConverter, + }) + @designToken(designUnit) + public designUnit?: number; + + /** + * The primary document direction. + * + * @remarks + * HTML attribute: direction + * + * CSS custom property: N/A + */ + @attr({ + attribute: "direction", + }) + @designToken(direction) + public direction?: Direction; + + /** + * The number of designUnits used for component height at the base density. + * + * @remarks + * HTML attribute: base-height-multiplier + * + * CSS custom property: --base-height-multiplier + */ + @attr({ + attribute: "base-height-multiplier", + converter: nullableNumberConverter, + }) + @designToken(baseHeightMultiplier) + public baseHeightMultiplier?: number; + + /** + * The number of designUnits used for horizontal spacing at the base density. + * + * @remarks + * HTML attribute: base-horizontal-spacing-multiplier + * + * CSS custom property: --base-horizontal-spacing-multiplier + */ + @attr({ + attribute: "base-horizontal-spacing-multiplier", + converter: nullableNumberConverter, + }) + @designToken(baseHorizontalSpacingMultiplier) + public baseHorizontalSpacingMultiplier?: number; + + /** + * The corner radius applied to controls. + * + * @remarks + * HTML attribute: control-corner-radius + * + * CSS custom property: --control-corner-radius + */ + @attr({ + attribute: "control-corner-radius", + converter: nullableNumberConverter, + }) + @designToken(controlCornerRadius) + public controlCornerRadius?: number; + + /** + * The width of the standard stroke applied to stroke components in pixels. + * + * @remarks + * HTML attribute: stroke-width + * + * CSS custom property: --stroke-width + */ + @attr({ + attribute: "stroke-width", + converter: nullableNumberConverter, + }) + @designToken(strokeWidth) + public strokeWidth?: number; + + /** + * The width of the standard focus stroke in pixels. + * + * @remarks + * HTML attribute: focus-stroke-width + * + * CSS custom property: --focus-stroke-width + */ + @attr({ + attribute: "focus-stroke-width", + converter: nullableNumberConverter, + }) + @designToken(focusStrokeWidth) + public focusStrokeWidth?: number; + + /** + * The opacity of a disabled control. + * + * @remarks + * HTML attribute: disabled-opacity + * + * CSS custom property: --disabled-opacity + */ + @attr({ + attribute: "disabled-opacity", + converter: nullableNumberConverter, + }) + @designToken(disabledOpacity) + public disabledOpacity?: number; + + /** + * The font-size two steps below the base font-size + * + * @remarks + * HTML attribute: type-ramp-minus-2-font-size + * + * CSS custom property: --type-ramp-minus-2-font-size + */ + @attr({ + attribute: "type-ramp-minus-2-font-size", + }) + @designToken(typeRampMinus2FontSize) + public typeRampMinus2FontSize?: string; + + /** + * The line-height two steps below the base line-height + * + * @remarks + * HTML attribute: type-ramp-minus-2-line-height + * + * CSS custom property: --type-ramp-minus-2-line-height + */ + @attr({ + attribute: "type-ramp-minus-2-line-height", + }) + @designToken(typeRampMinus2LineHeight) + public typeRampMinus2LineHeight?: string; + + /** + * The font-size one step below the base font-size + * + * @remarks + * HTML attribute: type-ramp-minus-1-font-size + * + * CSS custom property: --type-ramp-minus-1-font-size + */ + @attr({ + attribute: "type-ramp-minus-1-font-size", + }) + @designToken(typeRampMinus1FontSize) + public typeRampMinus1FontSize?: string; + + /** + * The line-height one step below the base line-height + * + * @remarks + * HTML attribute: type-ramp-minus-1-line-height + * + * CSS custom property: --type-ramp-minus-1-line-height + */ + @attr({ + attribute: "type-ramp-minus-1-line-height", + }) + @designToken(typeRampMinus1LineHeight) + public typeRampMinus1LineHeight?: string; + + /** + * The base font-size of the relative type-ramp scale + * + * @remarks + * HTML attribute: type-ramp-base-font-size + * + * CSS custom property: --type-ramp-base-font-size + */ + @attr({ + attribute: "type-ramp-base-font-size", + }) + @designToken(typeRampBaseFontSize) + public typeRampBaseFontSize?: string; + + /** + * The base line-height of the relative type-ramp scale + * + * @remarks + * HTML attribute: type-ramp-base-line-height + * + * CSS custom property: --type-ramp-base-line-height + */ + @attr({ + attribute: "type-ramp-base-line-height", + }) + @designToken(typeRampBaseLineHeight) + public typeRampBaseLineHeight?: string; + + /** + * The font-size one step above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-1-font-size + * + * CSS custom property: --type-ramp-plus-1-font-size + */ + @attr({ + attribute: "type-ramp-plus-1-font-size", + }) + @designToken(typeRampPlus1FontSize) + public typeRampPlus1FontSize?: string; + + /** + * The line-height one step above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-1-line-height + * + * CSS custom property: --type-ramp-plus-1-line-height + */ + @attr({ + attribute: "type-ramp-plus-1-line-height", + }) + @designToken(typeRampPlus1LineHeight) + public typeRampPlus1LineHeight?: string; + + /** + * The font-size two steps above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-2-font-size + * + * CSS custom property: --type-ramp-plus-2-font-size + */ + @attr({ + attribute: "type-ramp-plus-2-font-size", + }) + @designToken(typeRampPlus2FontSize) + public typeRampPlus2FontSize?: string; + + /** + * The line-height two steps above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-2-line-height + * + * CSS custom property: --type-ramp-plus-2-line-height + */ + @attr({ + attribute: "type-ramp-plus-2-line-height", + }) + @designToken(typeRampPlus2LineHeight) + public typeRampPlus2LineHeight?: string; + + /** + * The font-size three steps above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-3-font-size + * + * CSS custom property: --type-ramp-plus-3-font-size + */ + @attr({ + attribute: "type-ramp-plus-3-font-size", + }) + @designToken(typeRampPlus3FontSize) + public typeRampPlus3FontSize?: string; + + /** + * The line-height three steps above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-3-line-height + * + * CSS custom property: --type-ramp-plus-3-line-height + */ + @attr({ + attribute: "type-ramp-plus-3-line-height", + }) + @designToken(typeRampPlus3LineHeight) + public typeRampPlus3LineHeight?: string; + + /** + * The font-size four steps above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-4-font-size + * + * CSS custom property: --type-ramp-plus-4-font-size + */ + @attr({ + attribute: "type-ramp-plus-4-font-size", + }) + @designToken(typeRampPlus4FontSize) + public typeRampPlus4FontSize?: string; + + /** + * The line-height four steps above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-4-line-height + * + * CSS custom property: --type-ramp-plus-4-line-height + */ + @attr({ + attribute: "type-ramp-plus-4-line-height", + }) + @designToken(typeRampPlus4LineHeight) + public typeRampPlus4LineHeight?: string; + + /** + * The font-size five steps above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-5-font-size + * + * CSS custom property: --type-ramp-plus-5-font-size + */ + @attr({ + attribute: "type-ramp-plus-5-font-size", + }) + @designToken(typeRampPlus5FontSize) + public typeRampPlus5FontSize?: string; + + /** + * The line-height five steps above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-5-line-height + * + * CSS custom property: --type-ramp-plus-5-line-height + */ + @attr({ + attribute: "type-ramp-plus-5-line-height", + }) + @designToken(typeRampPlus5LineHeight) + public typeRampPlus5LineHeight?: string; + + /** + * The font-size six steps above the base font-size + * + * @remarks + * HTML attribute: type-ramp-plus-6-font-size + * + * CSS custom property: --type-ramp-plus-6-font-size + */ + @attr({ + attribute: "type-ramp-plus-6-font-size", + }) + @designToken(typeRampPlus6FontSize) + public typeRampPlus6FontSize?: string; + + /** + * The line-height six steps above the base line-height + * + * @remarks + * HTML attribute: type-ramp-plus-6-line-height + * + * CSS custom property: --type-ramp-plus-6-line-height + */ + @attr({ + attribute: "type-ramp-plus-6-line-height", + }) + @designToken(typeRampPlus6LineHeight) + public typeRampPlus6LineHeight?: string; + + /** + * The distance from the resolved accent fill color for the rest state of the accent-fill recipe. See {@link @microsoft/fast-components#accentFillRest} for usage in CSS. + * + * @remarks + * HTML attribute: accent-fill-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-fill-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(accentFillRestDelta) + public accentFillRestDelta?: number; + + /** + * The distance from the resolved accent fill color for the hover state of the accent-fill recipe. See {@link @microsoft/fast-components#accentFillHover} for usage in CSS. + * + * @remarks + * HTML attribute: accent-fill-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-fill-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(accentFillHoverDelta) + public accentFillHoverDelta?: number; + + /** + * The distance from the resolved accent fill color for the active state of the accent-fill recipe. See {@link @microsoft/fast-components#accentFillActive} for usage in CSS. + * + * @remarks + * HTML attribute: accent-fill-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-fill-active-delta", + converter: nullableNumberConverter, + }) + @designToken(accentFillActiveDelta) + public accentFillActiveDelta?: number; + + /** + * The distance from the resolved accent fill color for the focus state of the accent-fill recipe. See {@link @microsoft/fast-components#accentFillFocus} for usage in CSS. + * + * @remarks + * HTML attribute: accent-fill-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-fill-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(accentFillFocusDelta) + public accentFillFocusDelta?: number; + + /** + * The distance from the resolved accent foreground color for the rest state of the accent-foreground recipe. See {@link @microsoft/fast-components#accentForegroundRest} for usage in CSS. + * + * @remarks + * HTML attribute: accent-foreground-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-foreground-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(accentForegroundRestDelta) + public accentForegroundRestDelta?: number; + + /** + * The distance from the resolved accent foreground color for the hover state of the accent-foreground recipe. See {@link @microsoft/fast-components#accentForegroundHover} for usage in CSS. + * + * @remarks + * HTML attribute: accent-foreground-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-foreground-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(accentForegroundHoverDelta) + public accentForegroundHoverDelta?: number; + + /** + * The distance from the resolved accent foreground color for the active state of the accent-foreground recipe. See {@link @microsoft/fast-components#accentForegroundActive} for usage in CSS. + * + * @remarks + * HTML attribute: accent-foreground-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-foreground-active-delta", + converter: nullableNumberConverter, + }) + @designToken(accentForegroundActiveDelta) + public accentForegroundActiveDelta?: number; + + /** + * The distance from the resolved accent foreground color for the focus state of the accent-foreground recipe. See {@link @microsoft/fast-components#accentForegroundFocus} for usage in CSS. + * + * @remarks + * HTML attribute: accent-foreground-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "accent-foreground-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(accentForegroundFocusDelta) + public accentForegroundFocusDelta?: number; + + /** + * The distance from the resolved neutral fill color for the rest state of the neutral-fill recipe. See {@link @microsoft/fast-components#neutralFillRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillRestDelta) + public neutralFillRestDelta?: number; + + /** + * The distance from the resolved neutral fill color for the hover state of the neutral-fill recipe. See {@link @microsoft/fast-components#neutralFillHover} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillHoverDelta) + public neutralFillHoverDelta?: number; + + /** + * The distance from the resolved neutral fill color for the active state of the neutral-fill recipe. See {@link @microsoft/fast-components#neutralFillActive} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-active-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillActiveDelta) + public neutralFillActiveDelta?: number; + + /** + * The distance from the resolved neutral fill color for the focus state of the neutral-fill recipe. See {@link @microsoft/fast-components#neutralFillFocus} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillFocusDelta) + public neutralFillFocusDelta?: number; + + /** + * The distance from the resolved neutral fill input color for the rest state of the neutral-fill-input recipe. See {@link @microsoft/fast-components#neutralFillInputRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-input-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-input-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillInputRestDelta) + public neutralFillInputRestDelta?: number; + + /** + * The distance from the resolved neutral fill input color for the hover state of the neutral-fill-input recipe. See {@link @microsoft/fast-components#neutralFillInputHover} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-input-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-input-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillInputHoverDelta) + public neutralFillInputHoverDelta?: number; + + /** + * The distance from the resolved neutral fill input color for the active state of the neutral-fill-input recipe. See {@link @microsoft/fast-components#neutralFillInputActive} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-input-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-input-active-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillInputActiveDelta) + public neutralFillInputActiveDelta?: number; + + /** + * The distance from the resolved neutral fill input color for the focus state of the neutral-fill-input recipe. See {@link @microsoft/fast-components#neutralFillInputFocus} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-input-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-input-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillInputFocusDelta) + public neutralFillInputFocusDelta?: number; + + /** + * The distance from the resolved neutral fill stealth color for the rest state of the neutral-fill-stealth recipe. See {@link @microsoft/fast-components#neutralFillStealthRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-stealth-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-stealth-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStealthRestDelta) + public neutralFillStealthRestDelta?: number; + + /** + * The distance from the resolved neutral fill stealth color for the hover state of the neutral-fill-stealth recipe. See {@link @microsoft/fast-components#neutralFillStealthHover} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-stealth-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-stealth-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStealthHoverDelta) + public neutralFillStealthHoverDelta?: number; + + /** + * The distance from the resolved neutral fill stealth color for the active state of the neutral-fill-stealth recipe. See {@link @microsoft/fast-components#neutralFillStealthActive} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-stealth-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-stealth-active-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStealthActiveDelta) + public neutralFillStealthActiveDelta?: number; + + /** + * The distance from the resolved neutral fill stealth color for the focus state of the neutral-fill-stealth recipe. See {@link @microsoft/fast-components#neutralFillStealthFocus} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-stealth-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-stealth-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStealthFocusDelta) + public neutralFillStealthFocusDelta?: number; + + /** + * The distance from the resolved neutral fill strong color for the hover state of the neutral-fill-strong recipe. See {@link @microsoft/fast-components#neutralFillStrongHover} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-strong-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-strong-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStrongHoverDelta) + public neutralFillStrongHoverDelta?: number; + + /** + * The distance from the resolved neutral fill strong color for the active state of the neutral-fill-strong recipe. See {@link @microsoft/fast-components#neutralFillStrongActive} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-strong-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-strong-active-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStrongActiveDelta) + public neutralFillStrongActiveDelta?: number; + + /** + * The distance from the resolved neutral fill strong color for the focus state of the neutral-fill-strong recipe. See {@link @microsoft/fast-components#neutralFillStrongFocus} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-strong-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-strong-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillStrongFocusDelta) + public neutralFillStrongFocusDelta?: number; + + /** + * The {@link https://www.w3.org/WAI/GL/wiki/Relative_luminance#:~:text=WCAG%20definition%20of%20relative%20luminance,and%201%20for%20lightest%20white|relative luminance} of the base layer of the application. + * + * @remarks + * When set to a number between 0 and 1, this values controls the output of {@link @microsoft/fast-components#neutralFillLayerRest}, {@link @microsoft/fast-components#neutralLayerCardContainer}, {@link @microsoft/fast-components#neutralLayerFloating}, {@link @microsoft/fast-components#neutralLayer1}, {@link @microsoft/fast-components#neutralLayer2}, {@link @microsoft/fast-components#neutralLayer3}, {@link @microsoft/fast-components#neutralLayer4}. + * + * HTML attribute: base-layer-luminance + * + * CSS custom property: N/A + */ + @attr({ + attribute: "base-layer-luminance", + converter: nullableNumberConverter, + }) + @designToken(baseLayerLuminance) + public baseLayerLuminance?: number; // 0...1 + + /** + * The distance from the background-color to resolve the card background. See {@link @microsoft/fast-components#neutralFillLayerRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-fill-layer-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-fill-layer-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralFillLayerRestDelta) + public neutralFillLayerRestDelta?: number; + + /** + * The distance from the resolved neutral divider color for the rest state of the neutral-foreground recipe. See {@link @microsoft/fast-components#neutralStrokeDividerRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-stroke-divider-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-stroke-divider-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralStrokeDividerRestDelta) + public neutralStrokeDividerRestDelta?: number; + + /** + * The distance from the resolved neutral stroke color for the rest state of the neutral-stroke recipe. See {@link @microsoft/fast-components#neutralStrokeRest} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-stroke-rest-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-stroke-rest-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralStrokeRestDelta) + public neutralStrokeRestDelta?: number; + + /** + * The distance from the resolved neutral stroke color for the hover state of the neutral-stroke recipe. See {@link @microsoft/fast-components#neutralStrokeHover} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-stroke-hover-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-stroke-hover-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralStrokeHoverDelta) + public neutralStrokeHoverDelta?: number; + + /** + * The distance from the resolved neutral stroke color for the active state of the neutral-stroke recipe. See {@link @microsoft/fast-components#neutralStrokeActive} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-stroke-active-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-stroke-active-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralStrokeActiveDelta) + public neutralStrokeActiveDelta?: number; + + /** + * The distance from the resolved neutral stroke color for the focus state of the neutral-stroke recipe. See {@link @microsoft/fast-components#neutralStrokeFocus} for usage in CSS. + * + * @remarks + * HTML attribute: neutral-stroke-focus-delta + * + * CSS custom property: N/A + */ + @attr({ + attribute: "neutral-stroke-focus-delta", + converter: nullableNumberConverter, + }) + @designToken(neutralStrokeFocusDelta) + public neutralStrokeFocusDelta?: number; +} + +/** + * Template for DesignSystemProvider. + * @public + */ +export const designSystemProviderTemplate = ( + context: ElementDefinitionContext, + definition: FoundationElementDefinition +) => html` + +`; + +/** + * Styles for DesignSystemProvider. + * @public + */ +export const designSystemProviderStyles = ( + context: ElementDefinitionContext, + definition: FoundationElementDefinition +) => css` + ${display("block")} +`; + +/** + * A function that returns a {@link DesignSystemProvider} registration for configuring the component with a DesignSystem. + * @public + * @remarks + * Generates HTML Element: `` + */ +export const fastDesignSystemProvider = DesignSystemProvider.compose({ + baseName: "design-system-provider", + template: designSystemProviderTemplate, + styles: designSystemProviderStyles, +}); diff --git a/packages/components/src/design-tokens.ts b/packages/components/src/design-tokens.ts index f2bbc2d9..8b1d4ed0 100644 --- a/packages/components/src/design-tokens.ts +++ b/packages/components/src/design-tokens.ts @@ -1,381 +1,1187 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { - accentFillActiveDelta, - accentFillFocusDelta, - accentFillHoverDelta, - accentForegroundActiveDelta, - accentForegroundFocusDelta, - accentForegroundHoverDelta, - accentForegroundRestDelta, - ColorRecipe, - disabledOpacity, - fillColor, - InteractiveColorRecipe, - InteractiveSwatchSet, - neutralFillActiveDelta, - neutralFillHoverDelta, - neutralFillRestDelta, - neutralPalette, - Palette, - PaletteRGB, - Swatch -} from '@microsoft/fast-components'; -import { DesignToken } from '@microsoft/fast-foundation'; +import { DesignToken } from "@microsoft/fast-foundation"; +import { Direction } from "@microsoft/fast-web-utilities"; +import { Palette, PaletteRGB } from "./color/palette.js"; +import { Swatch, SwatchRGB } from "./color/swatch.js"; +import { accentFill as accentFillAlgorithm } from "./color/recipes/accent-fill.js"; +import { accentForeground as accentForegroundAlgorithm } from "./color/recipes/accent-foreground.js"; +import { foregroundOnAccent as foregroundOnAccentAlgorithm } from "./color/recipes/foreground-on-accent.js"; +import { neutralFill as neutralFillAlgorithm } from "./color/recipes/neutral-fill.js"; +import { neutralFillInput as neutralFillInputAlgorithm } from "./color/recipes/neutral-fill-input.js"; +import { neutralFillLayer as neutralFillLayerAlgorithm } from "./color/recipes/neutral-fill-layer.js"; +import { neutralFillStealth as neutralFillStealthAlgorithm } from "./color/recipes/neutral-fill-stealth.js"; +import { neutralFillContrast as neutralFillContrastAlgorithm } from "./color/recipes/neutral-fill-contrast.js"; import { - ContrastTarget, - errorBase, - errorFillAlgorithm, - errorForegroundAlgorithm, - foregroundOnErrorAlgorithm -} from './color'; - -// Export design token from @microsoft/fast-components -// to encapsulate them. - -export { - accentColor, - accentFillActive, - accentFillActiveDelta, - accentFillFocus, - accentFillFocusDelta, - accentFillHover, - accentFillHoverDelta, - accentFillRecipe, - accentFillRest, - accentFillRestDelta, - accentForegroundActive, - accentForegroundActiveDelta, - accentForegroundFocus, - accentForegroundFocusDelta, - accentForegroundHover, - accentForegroundHoverDelta, - accentForegroundRecipe, - accentForegroundRest, - accentForegroundRestDelta, - accentPalette, - baseHeightMultiplier, - baseHorizontalSpacingMultiplier, - baseLayerLuminance, - bodyFont, - ColorRecipe, - controlCornerRadius, - density, - designUnit, - direction, - DirectionalStyleSheetBehavior, - disabledOpacity, - fillColor, - focusStrokeInner, - focusStrokeInnerRecipe, - focusStrokeOuter, - focusStrokeOuterRecipe, - focusStrokeWidth, - foregroundOnAccentActive, - foregroundOnAccentActiveLarge, - foregroundOnAccentFocus, - foregroundOnAccentFocusLarge, - foregroundOnAccentHover, - foregroundOnAccentHoverLarge, - foregroundOnAccentLargeRecipe, - foregroundOnAccentRecipe, - foregroundOnAccentRest, - foregroundOnAccentRestLarge, - InteractiveColorRecipe, - neutralColor, - neutralFillActive, - neutralFillActiveDelta, - neutralFillFocus, - neutralFillFocusDelta, - neutralFillHover, - neutralFillHoverDelta, - neutralFillInputActive, - neutralFillInputActiveDelta, - neutralFillInputFocus, - neutralFillInputFocusDelta, - neutralFillInputHover, - neutralFillInputHoverDelta, - neutralFillInputRecipe, - neutralFillInputRest, - neutralFillInputRestDelta, - neutralFillLayerRecipe, - neutralFillLayerRest, - neutralFillLayerRestDelta, - neutralFillRecipe, - neutralFillRest, - neutralFillRestDelta, - neutralFillStealthActive, - neutralFillStealthActiveDelta, - neutralFillStealthFocus, - neutralFillStealthFocusDelta, - neutralFillStealthHover, - neutralFillStealthHoverDelta, - neutralFillStealthRecipe, - neutralFillStealthRest, - neutralFillStealthRestDelta, - neutralFillStrongActive, - neutralFillStrongActiveDelta, - neutralFillStrongFocus, - neutralFillStrongFocusDelta, - neutralFillStrongHover, - neutralFillStrongHoverDelta, - neutralFillStrongRecipe, - neutralFillStrongRest, - neutralFillStrongRestDelta, - neutralForegroundHint, - neutralForegroundHintRecipe, - neutralForegroundRecipe, - neutralForegroundRest, - neutralLayer1, - neutralLayer1Recipe, - neutralLayer2, - neutralLayer2Recipe, - neutralLayer3, - neutralLayer3Recipe, - neutralLayer4, - neutralLayer4Recipe, - neutralLayerCardContainer, - neutralLayerCardContainerRecipe, - neutralLayerFloating, - neutralLayerFloatingRecipe, - neutralPalette, - neutralStrokeActive, - neutralStrokeActiveDelta, - neutralStrokeDividerRecipe, - neutralStrokeDividerRest, - neutralStrokeDividerRestDelta, - neutralStrokeFocus, - neutralStrokeFocusDelta, - neutralStrokeHover, - neutralStrokeHoverDelta, - neutralStrokeRecipe, - neutralStrokeRest, - neutralStrokeRestDelta, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight, - typeRampMinus1FontSize, - typeRampMinus1LineHeight, - typeRampMinus2FontSize, - typeRampMinus2LineHeight, - typeRampPlus1FontSize, - typeRampPlus1LineHeight, - typeRampPlus2FontSize, - typeRampPlus2LineHeight, - typeRampPlus3FontSize, - typeRampPlus3LineHeight, - typeRampPlus4FontSize, - typeRampPlus4LineHeight, - typeRampPlus5FontSize, - typeRampPlus5LineHeight, - typeRampPlus6FontSize, - typeRampPlus6LineHeight -} from '@microsoft/fast-components'; + focusStrokeInner as focusStrokeInnerAlgorithm, + focusStrokeOuter as focusStrokeOuterAlgorithm, +} from "./color/recipes/focus-stroke.js"; +import { neutralForeground as neutralForegroundAlgorithm } from "./color/recipes/neutral-foreground.js"; +import { neutralForegroundHint as neutralForegroundHintAlgorithm } from "./color/recipes/neutral-foreground-hint.js"; +import { neutralLayerCardContainer as neutralLayerCardContainerAlgorithm } from "./color/recipes/neutral-layer-card-container.js"; +import { neutralLayerFloating as neutralLayerFloatingAlgorithm } from "./color/recipes/neutral-layer-floating.js"; +import { neutralLayer1 as neutralLayer1Algorithm } from "./color/recipes/neutral-layer-1.js"; +import { neutralLayer2 as neutralLayer2Algorithm } from "./color/recipes/neutral-layer-2.js"; +import { neutralLayer3 as neutralLayer3Algorithm } from "./color/recipes/neutral-layer-3.js"; +import { neutralLayer4 as neutralLayer4Algorithm } from "./color/recipes/neutral-layer-4.js"; +import { neutralStroke as neutralStrokeAlgorithm } from "./color/recipes/neutral-stroke.js"; +import { neutralStrokeDivider as neutralStrokeDividerAlgorithm } from "./color/recipes/neutral-stroke-divider.js"; +import { StandardLuminance } from "./color/utilities/base-layer-luminance.js"; +import { accentBase, errorBase, middleGrey } from "./color/utilities/color-constants.js"; +import { InteractiveSwatchSet } from "./color/recipe.js"; +import { errorFillAlgorithm, errorForegroundAlgorithm } from "./color/recipes/error-fill.js"; +import { foregroundOnErrorAlgorithm } from "./color/recipes/foreground-on-error.js"; + +/** @public @deprecated Use ColorRecipe instead */ +export interface Recipe { + evaluate(element: HTMLElement, reference?: Swatch): T; +} + +/** @public */ +export interface ColorRecipe { + evaluate(element: HTMLElement, reference?: Swatch): Swatch; +} + +/** @public */ +export interface InteractiveColorRecipe { + evaluate(element: HTMLElement, reference?: Swatch): InteractiveSwatchSet; +} const { create } = DesignToken; -// Changing the default to increase contrast -disabledOpacity.withDefault(0.4); +function createNonCss(name: string): DesignToken { + return DesignToken.create({ name, cssCustomPropertyName: null }); +} -/* - * The error palette is built using the same color algorithm as the accent palette - * But by copying the algorithm from @microsoft/fast-components at commit 03d711f222bd816834a5e1d60256d3e083b27c27 - * as some helpers are not exported. - * The delta used are those of the accent palette. - */ +// General tokens -/** - * Error palette - */ -export const errorPalette = create({ - name: 'error-palette', - cssCustomPropertyName: null -}).withDefault(PaletteRGB.from(errorBase)); +/** @public */ +export const bodyFont = create("body-font").withDefault( + 'aktiv-grotesk, "Segoe UI", Arial, Helvetica, sans-serif' +); +/** @public */ +export const baseHeightMultiplier = create("base-height-multiplier").withDefault( + 10 +); +/** @public */ +export const baseHorizontalSpacingMultiplier = create( + "base-horizontal-spacing-multiplier" +).withDefault(3); +/** @public */ +export const baseLayerLuminance = create("base-layer-luminance").withDefault( + StandardLuminance.DarkMode +); +/** @public */ +export const controlCornerRadius = create("control-corner-radius").withDefault(4); +/** @public */ +export const density = create("density").withDefault(0); +/** @public */ +export const designUnit = create("design-unit").withDefault(4); +/** @public */ +export const direction = create("direction").withDefault(Direction.ltr); +/** @public */ +export const disabledOpacity = create("disabled-opacity").withDefault(0.4); +/** @public */ +export const strokeWidth = create("stroke-width").withDefault(1); +/** @public */ +export const focusStrokeWidth = create("focus-stroke-width").withDefault(2); + +// Typography values + +/** @public */ +export const typeRampBaseFontSize = create( + "type-ramp-base-font-size" +).withDefault("14px"); +/** @public */ +export const typeRampBaseLineHeight = create( + "type-ramp-base-line-height" +).withDefault("20px"); +/** @public */ +export const typeRampMinus1FontSize = create( + "type-ramp-minus-1-font-size" +).withDefault("12px"); +/** @public */ +export const typeRampMinus1LineHeight = create( + "type-ramp-minus-1-line-height" +).withDefault("16px"); +/** @public */ +export const typeRampMinus2FontSize = create( + "type-ramp-minus-2-font-size" +).withDefault("10px"); +/** @public */ +export const typeRampMinus2LineHeight = create( + "type-ramp-minus-2-line-height" +).withDefault("16px"); +/** @public */ +export const typeRampPlus1FontSize = create( + "type-ramp-plus-1-font-size" +).withDefault("16px"); +/** @public */ +export const typeRampPlus1LineHeight = create( + "type-ramp-plus-1-line-height" +).withDefault("24px"); +/** @public */ +export const typeRampPlus2FontSize = create( + "type-ramp-plus-2-font-size" +).withDefault("20px"); +/** @public */ +export const typeRampPlus2LineHeight = create( + "type-ramp-plus-2-line-height" +).withDefault("28px"); +/** @public */ +export const typeRampPlus3FontSize = create( + "type-ramp-plus-3-font-size" +).withDefault("28px"); +/** @public */ +export const typeRampPlus3LineHeight = create( + "type-ramp-plus-3-line-height" +).withDefault("36px"); +/** @public */ +export const typeRampPlus4FontSize = create( + "type-ramp-plus-4-font-size" +).withDefault("34px"); +/** @public */ +export const typeRampPlus4LineHeight = create( + "type-ramp-plus-4-line-height" +).withDefault("44px"); +/** @public */ +export const typeRampPlus5FontSize = create( + "type-ramp-plus-5-font-size" +).withDefault("46px"); +/** @public */ +export const typeRampPlus5LineHeight = create( + "type-ramp-plus-5-line-height" +).withDefault("56px"); +/** @public */ +export const typeRampPlus6FontSize = create( + "type-ramp-plus-6-font-size" +).withDefault("60px"); +/** @public */ +export const typeRampPlus6LineHeight = create( + "type-ramp-plus-6-line-height" +).withDefault("72px"); + +// Color recipe values + +/** @public */ +export const accentFillRestDelta = createNonCss( + "accent-fill-rest-delta" +).withDefault(0); +/** @public */ +export const accentFillHoverDelta = createNonCss( + "accent-fill-hover-delta" +).withDefault(4); +/** @public */ +export const accentFillActiveDelta = createNonCss( + "accent-fill-active-delta" +).withDefault(-5); +/** @public */ +export const accentFillFocusDelta = createNonCss( + "accent-fill-focus-delta" +).withDefault(0); + +/** @public */ +export const accentForegroundRestDelta = createNonCss( + "accent-foreground-rest-delta" +).withDefault(0); +/** @public */ +export const accentForegroundHoverDelta = createNonCss( + "accent-foreground-hover-delta" +).withDefault(6); +/** @public */ +export const accentForegroundActiveDelta = createNonCss( + "accent-foreground-active-delta" +).withDefault(-4); +/** @public */ +export const accentForegroundFocusDelta = createNonCss( + "accent-foreground-focus-delta" +).withDefault(0); + +/** @public */ +export const neutralFillRestDelta = createNonCss( + "neutral-fill-rest-delta" +).withDefault(7); +/** @public */ +export const neutralFillHoverDelta = createNonCss( + "neutral-fill-hover-delta" +).withDefault(10); +/** @public */ +export const neutralFillActiveDelta = createNonCss( + "neutral-fill-active-delta" +).withDefault(5); +/** @public */ +export const neutralFillFocusDelta = createNonCss( + "neutral-fill-focus-delta" +).withDefault(0); + +/** @public */ +export const neutralFillInputRestDelta = createNonCss( + "neutral-fill-input-rest-delta" +).withDefault(0); +/** @public */ +export const neutralFillInputHoverDelta = createNonCss( + "neutral-fill-input-hover-delta" +).withDefault(0); +/** @public */ +export const neutralFillInputActiveDelta = createNonCss( + "neutral-fill-input-active-delta" +).withDefault(0); +/** @public */ +export const neutralFillInputFocusDelta = createNonCss( + "neutral-fill-input-focus-delta" +).withDefault(0); + +/** @public */ +export const neutralFillStealthRestDelta = createNonCss( + "neutral-fill-stealth-rest-delta" +).withDefault(0); +/** @public */ +export const neutralFillStealthHoverDelta = createNonCss( + "neutral-fill-stealth-hover-delta" +).withDefault(5); +/** @public */ +export const neutralFillStealthActiveDelta = createNonCss( + "neutral-fill-stealth-active-delta" +).withDefault(3); +/** @public */ +export const neutralFillStealthFocusDelta = createNonCss( + "neutral-fill-stealth-focus-delta" +).withDefault(0); + +/** @public */ +export const neutralFillStrongRestDelta = createNonCss( + "neutral-fill-strong-rest-delta" +).withDefault(0); +/** @public */ +export const neutralFillStrongHoverDelta = createNonCss( + "neutral-fill-strong-hover-delta" +).withDefault(8); +/** @public */ +export const neutralFillStrongActiveDelta = createNonCss( + "neutral-fill-strong-active-delta" +).withDefault(-5); +/** @public */ +export const neutralFillStrongFocusDelta = createNonCss( + "neutral-fill-strong-focus-delta" +).withDefault(0); + +/** @public */ +export const neutralFillLayerRestDelta = createNonCss( + "neutral-fill-layer-rest-delta" +).withDefault(3); + +/** @public */ +export const neutralStrokeRestDelta = createNonCss( + "neutral-stroke-rest-delta" +).withDefault(25); +/** @public */ +export const neutralStrokeHoverDelta = createNonCss( + "neutral-stroke-hover-delta" +).withDefault(40); +/** @public */ +export const neutralStrokeActiveDelta = createNonCss( + "neutral-stroke-active-delta" +).withDefault(16); +/** @public */ +export const neutralStrokeFocusDelta = createNonCss( + "neutral-stroke-focus-delta" +).withDefault(25); + +/** @public */ +export const neutralStrokeDividerRestDelta = createNonCss( + "neutral-stroke-divider-rest-delta" +).withDefault(8); + +// Color recipes + +/** @public */ +export const neutralColor = create("neutral-color").withDefault(middleGrey); + +/** @public */ +export const neutralPalette = createNonCss( + "neutral-palette" +).withDefault((element: HTMLElement) => + PaletteRGB.from(neutralColor.getValueFor(element) as SwatchRGB) +); + +/** @public */ +export const accentColor = create("accent-color").withDefault(accentBase); + +/** @public */ +export const accentPalette = createNonCss( + "accent-palette" +).withDefault((element: HTMLElement) => + PaletteRGB.from(accentColor.getValueFor(element) as SwatchRGB) +); + +// Neutral Layer Card Container +/** @public */ +export const neutralLayerCardContainerRecipe = createNonCss( + "neutral-layer-card-container-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayerCardContainerAlgorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralLayerCardContainer = create( + "neutral-layer-card-container" +).withDefault((element: HTMLElement) => + neutralLayerCardContainerRecipe.getValueFor(element).evaluate(element) +); + +// Neutral Layer Floating +/** @public */ +export const neutralLayerFloatingRecipe = createNonCss( + "neutral-layer-floating-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayerFloatingAlgorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralLayerFloating = create( + "neutral-layer-floating" +).withDefault((element: HTMLElement) => + neutralLayerFloatingRecipe.getValueFor(element).evaluate(element) +); + +// Neutral Layer 1 +/** @public */ +export const neutralLayer1Recipe = createNonCss( + "neutral-layer-1-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayer1Algorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element) + ), +}); + +/** @public */ +export const neutralLayer1 = create( + "neutral-layer-1" +).withDefault((element: HTMLElement) => + neutralLayer1Recipe.getValueFor(element).evaluate(element) +); + +// Neutral Layer 2 +/** @public */ +export const neutralLayer2Recipe = createNonCss( + "neutral-layer-2-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayer2Algorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralLayer2 = create( + "neutral-layer-2" +).withDefault((element: HTMLElement) => + neutralLayer2Recipe.getValueFor(element).evaluate(element) +); + +// Neutral Layer 3 +/** @public */ +export const neutralLayer3Recipe = createNonCss( + "neutral-layer-3-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayer3Algorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralLayer3 = create( + "neutral-layer-3" +).withDefault((element: HTMLElement) => + neutralLayer3Recipe.getValueFor(element).evaluate(element) +); + +// Neutral Layer 4 +/** @public */ +export const neutralLayer4Recipe = createNonCss( + "neutral-layer-4-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralLayer4Algorithm( + neutralPalette.getValueFor(element), + baseLayerLuminance.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element) + ), +}); -// Error Fill /** @public */ -export const errorFillRecipe = create({ - name: 'error-fill-recipe', - cssCustomPropertyName: null +export const neutralLayer4 = create( + "neutral-layer-4" +).withDefault((element: HTMLElement) => + neutralLayer4Recipe.getValueFor(element).evaluate(element) +); + +/** @public */ +export const fillColor = create("fill-color").withDefault(element => + neutralLayer1.getValueFor(element) +); + +enum ContrastTarget { + normal = 4.5, + large = 7, +} + +// Accent Fill +/** @public */ +export const accentFillRecipe = create({ + name: "accent-fill-recipe", + cssCustomPropertyName: null, }).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => - errorFillAlgorithm( - errorPalette.getValueFor(element), - neutralPalette.getValueFor(element), - reference || fillColor.getValueFor(element), - accentFillHoverDelta.getValueFor(element), - accentFillActiveDelta.getValueFor(element), - accentFillFocusDelta.getValueFor(element), - neutralFillRestDelta.getValueFor(element), - neutralFillHoverDelta.getValueFor(element), - neutralFillActiveDelta.getValueFor(element) - ) + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + accentFillAlgorithm( + accentPalette.getValueFor(element), + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + accentFillHoverDelta.getValueFor(element), + accentFillActiveDelta.getValueFor(element), + accentFillFocusDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element) + ), }); /** @public */ -export const errorFillRest = create('error-fill-rest').withDefault( - (element: HTMLElement) => { - return errorFillRecipe.getValueFor(element).evaluate(element).rest; - } +export const accentFillRest = create("accent-fill-rest").withDefault( + (element: HTMLElement) => { + return accentFillRecipe.getValueFor(element).evaluate(element).rest; + } ); /** @public */ -export const errorFillHover = create('error-fill-hover').withDefault( - (element: HTMLElement) => { - return errorFillRecipe.getValueFor(element).evaluate(element).hover; - } +export const accentFillHover = create("accent-fill-hover").withDefault( + (element: HTMLElement) => { + return accentFillRecipe.getValueFor(element).evaluate(element).hover; + } ); /** @public */ -export const errorFillActive = create('error-fill-active').withDefault( - (element: HTMLElement) => { - return errorFillRecipe.getValueFor(element).evaluate(element).active; - } +export const accentFillActive = create("accent-fill-active").withDefault( + (element: HTMLElement) => { + return accentFillRecipe.getValueFor(element).evaluate(element).active; + } ); /** @public */ -export const errorFillFocus = create('error-fill-focus').withDefault( - (element: HTMLElement) => { - return errorFillRecipe.getValueFor(element).evaluate(element).focus; - } +export const accentFillFocus = create("accent-fill-focus").withDefault( + (element: HTMLElement) => { + return accentFillRecipe.getValueFor(element).evaluate(element).focus; + } ); -// Foreground On Error -const foregroundOnErrorByContrast = - (contrast: number) => (element: HTMLElement, reference?: Swatch) => { - return foregroundOnErrorAlgorithm( - reference || errorFillRest.getValueFor(element), - contrast +// Foreground On Accent +const foregroundOnAccentByContrast = (contrast: number) => ( + element: HTMLElement, + reference?: Swatch +) => { + return foregroundOnAccentAlgorithm( + reference || accentFillRest.getValueFor(element), + contrast ); - }; +}; /** @public */ -export const foregroundOnErrorRecipe = create({ - name: 'foreground-on-error-recipe', - cssCustomPropertyName: null -}).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): Swatch => - foregroundOnErrorByContrast(ContrastTarget.normal)(element, reference) +export const foregroundOnAccentRecipe = createNonCss( + "foreground-on-accent-recipe" +).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnAccentByContrast(ContrastTarget.normal)(element, reference), }); /** @public */ -export const foregroundOnErrorRest = create( - 'foreground-on-error-rest' +export const foregroundOnAccentRest = create( + "foreground-on-accent-rest" ).withDefault((element: HTMLElement) => - foregroundOnErrorRecipe - .getValueFor(element) - .evaluate(element, errorFillRest.getValueFor(element)) + foregroundOnAccentRecipe + .getValueFor(element) + .evaluate(element, accentFillRest.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorHover = create( - 'foreground-on-error-hover' +export const foregroundOnAccentHover = create( + "foreground-on-accent-hover" ).withDefault((element: HTMLElement) => - foregroundOnErrorRecipe - .getValueFor(element) - .evaluate(element, errorFillHover.getValueFor(element)) + foregroundOnAccentRecipe + .getValueFor(element) + .evaluate(element, accentFillHover.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorActive = create( - 'foreground-on-error-active' +export const foregroundOnAccentActive = create( + "foreground-on-accent-active" ).withDefault((element: HTMLElement) => - foregroundOnErrorRecipe - .getValueFor(element) - .evaluate(element, errorFillActive.getValueFor(element)) + foregroundOnAccentRecipe + .getValueFor(element) + .evaluate(element, accentFillActive.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorFocus = create( - 'foreground-on-error-focus' +export const foregroundOnAccentFocus = create( + "foreground-on-accent-focus" ).withDefault((element: HTMLElement) => - foregroundOnErrorRecipe - .getValueFor(element) - .evaluate(element, errorFillFocus.getValueFor(element)) + foregroundOnAccentRecipe + .getValueFor(element) + .evaluate(element, accentFillFocus.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorLargeRecipe = create({ - name: 'foreground-on-error-large-recipe', - cssCustomPropertyName: null -}).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): Swatch => - foregroundOnErrorByContrast(ContrastTarget.large)(element, reference) +export const foregroundOnAccentLargeRecipe = createNonCss( + "foreground-on-accent-large-recipe" +).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnAccentByContrast(ContrastTarget.large)(element, reference), }); /** @public */ -export const foregroundOnErrorRestLarge = create( - 'foreground-on-error-rest-large' +export const foregroundOnAccentRestLarge = create( + "foreground-on-accent-rest-large" ).withDefault((element: HTMLElement) => - foregroundOnErrorLargeRecipe - .getValueFor(element) - .evaluate(element, errorFillRest.getValueFor(element)) + foregroundOnAccentLargeRecipe + .getValueFor(element) + .evaluate(element, accentFillRest.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorHoverLarge = create( - 'foreground-on-error-hover-large' +export const foregroundOnAccentHoverLarge = create( + "foreground-on-accent-hover-large" ).withDefault((element: HTMLElement) => - foregroundOnErrorLargeRecipe - .getValueFor(element) - .evaluate(element, errorFillHover.getValueFor(element)) + foregroundOnAccentLargeRecipe + .getValueFor(element) + .evaluate(element, accentFillHover.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorActiveLarge = create( - 'foreground-on-error-active-large' +export const foregroundOnAccentActiveLarge = create( + "foreground-on-accent-active-large" ).withDefault((element: HTMLElement) => - foregroundOnErrorLargeRecipe - .getValueFor(element) - .evaluate(element, errorFillActive.getValueFor(element)) + foregroundOnAccentLargeRecipe + .getValueFor(element) + .evaluate(element, accentFillActive.getValueFor(element)) ); /** @public */ -export const foregroundOnErrorFocusLarge = create( - 'foreground-on-error-focus-large' +export const foregroundOnAccentFocusLarge = create( + "foreground-on-accent-focus-large" ).withDefault((element: HTMLElement) => - foregroundOnErrorLargeRecipe - .getValueFor(element) - .evaluate(element, errorFillFocus.getValueFor(element)) -); - -// Error Foreground -const errorForegroundByContrast = - (contrast: number) => (element: HTMLElement, reference?: Swatch) => - errorForegroundAlgorithm( - errorPalette.getValueFor(element), - reference || fillColor.getValueFor(element), - contrast, - accentForegroundRestDelta.getValueFor(element), - accentForegroundHoverDelta.getValueFor(element), - accentForegroundActiveDelta.getValueFor(element), - accentForegroundFocusDelta.getValueFor(element) + foregroundOnAccentLargeRecipe + .getValueFor(element) + .evaluate(element, accentFillFocus.getValueFor(element)) +); + +// Accent Foreground +const accentForegroundByContrast = (contrast: number) => ( + element: HTMLElement, + reference?: Swatch +) => + accentForegroundAlgorithm( + accentPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + contrast, + accentForegroundRestDelta.getValueFor(element), + accentForegroundHoverDelta.getValueFor(element), + accentForegroundActiveDelta.getValueFor(element), + accentForegroundFocusDelta.getValueFor(element) ); /** @public */ -export const errorForegroundRecipe = create({ - name: 'error-foreground-recipe', - cssCustomPropertyName: null +export const accentForegroundRecipe = create({ + name: "accent-foreground-recipe", + cssCustomPropertyName: null, }).withDefault({ - evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => - errorForegroundByContrast(ContrastTarget.normal)(element, reference) + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + accentForegroundByContrast(ContrastTarget.normal)(element, reference), }); /** @public */ -export const errorForegroundRest = create( - 'error-foreground-rest' +export const accentForegroundRest = create("accent-foreground-rest").withDefault( + (element: HTMLElement) => + accentForegroundRecipe.getValueFor(element).evaluate(element).rest +); +/** @public */ +export const accentForegroundHover = create( + "accent-foreground-hover" ).withDefault( - (element: HTMLElement) => - errorForegroundRecipe.getValueFor(element).evaluate(element).rest + (element: HTMLElement) => + accentForegroundRecipe.getValueFor(element).evaluate(element).hover ); /** @public */ -export const errorForegroundHover = create( - 'error-foreground-hover' +export const accentForegroundActive = create( + "accent-foreground-active" ).withDefault( - (element: HTMLElement) => - errorForegroundRecipe.getValueFor(element).evaluate(element).hover + (element: HTMLElement) => + accentForegroundRecipe.getValueFor(element).evaluate(element).active +); +/** @public */ +export const accentForegroundFocus = create( + "accent-foreground-focus" +).withDefault( + (element: HTMLElement) => + accentForegroundRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Fill +/** @public */ +export const neutralFillRecipe = create({ + name: "neutral-fill-recipe", + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + neutralFillAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element), + neutralFillFocusDelta.getValueFor(element) + ), +}); +/** @public */ +export const neutralFillRest = create("neutral-fill-rest").withDefault( + (element: HTMLElement) => + neutralFillRecipe.getValueFor(element).evaluate(element).rest +); +/** @public */ +export const neutralFillHover = create("neutral-fill-hover").withDefault( + (element: HTMLElement) => + neutralFillRecipe.getValueFor(element).evaluate(element).hover +); +/** @public */ +export const neutralFillActive = create("neutral-fill-active").withDefault( + (element: HTMLElement) => + neutralFillRecipe.getValueFor(element).evaluate(element).active +); +/** @public */ +export const neutralFillFocus = create("neutral-fill-focus").withDefault( + (element: HTMLElement) => + neutralFillRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Fill Input +/** @public */ +export const neutralFillInputRecipe = create({ + name: "neutral-fill-input-recipe", + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + neutralFillInputAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralFillInputRestDelta.getValueFor(element), + neutralFillInputHoverDelta.getValueFor(element), + neutralFillInputActiveDelta.getValueFor(element), + neutralFillInputFocusDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralFillInputRest = create("neutral-fill-input-rest").withDefault( + (element: HTMLElement) => + neutralFillInputRecipe.getValueFor(element).evaluate(element).rest ); /** @public */ -export const errorForegroundActive = create( - 'error-foreground-active' +export const neutralFillInputHover = create( + "neutral-fill-input-hover" ).withDefault( - (element: HTMLElement) => - errorForegroundRecipe.getValueFor(element).evaluate(element).active + (element: HTMLElement) => + neutralFillInputRecipe.getValueFor(element).evaluate(element).hover ); /** @public */ -export const errorForegroundFocus = create( - 'error-foreground-focus' +export const neutralFillInputActive = create( + "neutral-fill-input-active" ).withDefault( - (element: HTMLElement) => - errorForegroundRecipe.getValueFor(element).evaluate(element).focus + (element: HTMLElement) => + neutralFillInputRecipe.getValueFor(element).evaluate(element).active ); +/** @public */ +export const neutralFillInputFocus = create( + "neutral-fill-input-focus" +).withDefault( + (element: HTMLElement) => + neutralFillInputRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Fill Stealth +/** @public */ +export const neutralFillStealthRecipe = create({ + name: "neutral-fill-stealth-recipe", + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + neutralFillStealthAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralFillStealthRestDelta.getValueFor(element), + neutralFillStealthHoverDelta.getValueFor(element), + neutralFillStealthActiveDelta.getValueFor(element), + neutralFillStealthFocusDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element), + neutralFillFocusDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralFillStealthRest = create( + "neutral-fill-stealth-rest" +).withDefault( + (element: HTMLElement) => + neutralFillStealthRecipe.getValueFor(element).evaluate(element).rest +); +/** @public */ +export const neutralFillStealthHover = create( + "neutral-fill-stealth-hover" +).withDefault( + (element: HTMLElement) => + neutralFillStealthRecipe.getValueFor(element).evaluate(element).hover +); +/** @public */ +export const neutralFillStealthActive = create( + "neutral-fill-stealth-active" +).withDefault( + (element: HTMLElement) => + neutralFillStealthRecipe.getValueFor(element).evaluate(element).active +); +/** @public */ +export const neutralFillStealthFocus = create( + "neutral-fill-stealth-focus" +).withDefault( + (element: HTMLElement) => + neutralFillStealthRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Fill Strong +/** @public */ +export const neutralFillStrongRecipe = create({ + name: "neutral-fill-strong-recipe", + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + neutralFillContrastAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralFillStrongRestDelta.getValueFor(element), + neutralFillStrongHoverDelta.getValueFor(element), + neutralFillStrongActiveDelta.getValueFor(element), + neutralFillStrongFocusDelta.getValueFor(element) + ), +}); + +/** @public */ +export const neutralFillStrongRest = create( + "neutral-fill-strong-rest" +).withDefault( + (element: HTMLElement) => + neutralFillStrongRecipe.getValueFor(element).evaluate(element).rest +); +/** @public */ +export const neutralFillStrongHover = create( + "neutral-fill-strong-hover" +).withDefault( + (element: HTMLElement) => + neutralFillStrongRecipe.getValueFor(element).evaluate(element).hover +); +/** @public */ +export const neutralFillStrongActive = create( + "neutral-fill-strong-active" +).withDefault( + (element: HTMLElement) => + neutralFillStrongRecipe.getValueFor(element).evaluate(element).active +); +/** @public */ +export const neutralFillStrongFocus = create( + "neutral-fill-strong-focus" +).withDefault( + (element: HTMLElement) => + neutralFillStrongRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Fill Layer +/** @public */ +export const neutralFillLayerRecipe = createNonCss( + "neutral-fill-layer-recipe" +).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + neutralFillLayerAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralFillLayerRestDelta.getValueFor(element) + ), +}); +/** @public */ +export const neutralFillLayerRest = create( + "neutral-fill-layer-rest" +).withDefault((element: HTMLElement) => + neutralFillLayerRecipe.getValueFor(element).evaluate(element) +); + +// Focus Stroke Outer +/** @public */ +export const focusStrokeOuterRecipe = createNonCss( + "focus-stroke-outer-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + focusStrokeOuterAlgorithm( + neutralPalette.getValueFor(element), + fillColor.getValueFor(element) + ), +}); + +/** @public */ +export const focusStrokeOuter = create( + "focus-stroke-outer" +).withDefault((element: HTMLElement) => + focusStrokeOuterRecipe.getValueFor(element).evaluate(element) +); + +// Focus Stroke Inner +/** @public */ +export const focusStrokeInnerRecipe = createNonCss( + "focus-stroke-inner-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + focusStrokeInnerAlgorithm( + accentPalette.getValueFor(element), + fillColor.getValueFor(element), + focusStrokeOuter.getValueFor(element) + ), +}); + +/** @public */ +export const focusStrokeInner = create( + "focus-stroke-inner" +).withDefault((element: HTMLElement) => + focusStrokeInnerRecipe.getValueFor(element).evaluate(element) +); + +// Neutral Foreground Hint +/** @public */ +export const neutralForegroundHintRecipe = createNonCss( + "neutral-foreground-hint-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralForegroundHintAlgorithm( + neutralPalette.getValueFor(element), + fillColor.getValueFor(element) + ), +}); + +/** @public */ +export const neutralForegroundHint = create( + "neutral-foreground-hint" +).withDefault((element: HTMLElement) => + neutralForegroundHintRecipe.getValueFor(element).evaluate(element) +); + +// Neutral Foreground +/** @public */ +export const neutralForegroundRecipe = createNonCss( + "neutral-foreground-recipe" +).withDefault({ + evaluate: (element: HTMLElement): Swatch => + neutralForegroundAlgorithm( + neutralPalette.getValueFor(element), + fillColor.getValueFor(element) + ), +}); + +/** @public */ +export const neutralForegroundRest = create( + "neutral-foreground-rest" +).withDefault((element: HTMLElement) => + neutralForegroundRecipe.getValueFor(element).evaluate(element) +); + +// Neutral Stroke +/** @public */ +export const neutralStrokeRecipe = create({ + name: "neutral-stroke-recipe", + cssCustomPropertyName: null, +}).withDefault({ + evaluate: (element: HTMLElement): InteractiveSwatchSet => { + return neutralStrokeAlgorithm( + neutralPalette.getValueFor(element), + fillColor.getValueFor(element), + neutralStrokeRestDelta.getValueFor(element), + neutralStrokeHoverDelta.getValueFor(element), + neutralStrokeActiveDelta.getValueFor(element), + neutralStrokeFocusDelta.getValueFor(element) + ); + }, +}); + +/** @public */ +export const neutralStrokeRest = create("neutral-stroke-rest").withDefault( + (element: HTMLElement) => + neutralStrokeRecipe.getValueFor(element).evaluate(element).rest +); +/** @public */ +export const neutralStrokeHover = create("neutral-stroke-hover").withDefault( + (element: HTMLElement) => + neutralStrokeRecipe.getValueFor(element).evaluate(element).hover +); +/** @public */ +export const neutralStrokeActive = create("neutral-stroke-active").withDefault( + (element: HTMLElement) => + neutralStrokeRecipe.getValueFor(element).evaluate(element).active +); +/** @public */ +export const neutralStrokeFocus = create("neutral-stroke-focus").withDefault( + (element: HTMLElement) => + neutralStrokeRecipe.getValueFor(element).evaluate(element).focus +); + +// Neutral Stroke Divider +/** @public */ +export const neutralStrokeDividerRecipe = createNonCss( + "neutral-stroke-divider-recipe" +).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + neutralStrokeDividerAlgorithm( + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + neutralStrokeDividerRestDelta.getValueFor(element) + ), +}); +/** @public */ +export const neutralStrokeDividerRest = create( + "neutral-stroke-divider-rest" +).withDefault(element => + neutralStrokeDividerRecipe.getValueFor(element).evaluate(element) +); + +/** + * The control height formula expressed as a design token. + * This token does not provide a CSS custom property. + * + * @public + */ +export const heightNumberAsToken = DesignToken.create({ + name: "height-number", + cssCustomPropertyName: null, +}).withDefault( + target => + (baseHeightMultiplier.getValueFor(target) + density.getValueFor(target)) * + designUnit.getValueFor(target) +); + +/* + * The error palette is built using the same color algorithm as the accent palette + * But by copying the algorithm from @microsoft/fast-components at commit 03d711f222bd816834a5e1d60256d3e083b27c27 + * as some helpers are not exported. + * The delta used are those of the accent palette. + */ + +/** + * Error palette + */ +export const errorPalette = create({ + name: 'error-palette', + cssCustomPropertyName: null + }).withDefault(PaletteRGB.from(errorBase)); + + // Error Fill + /** @public */ + export const errorFillRecipe = create({ + name: 'error-fill-recipe', + cssCustomPropertyName: null + }).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + errorFillAlgorithm( + errorPalette.getValueFor(element), + neutralPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + accentFillHoverDelta.getValueFor(element), + accentFillActiveDelta.getValueFor(element), + accentFillFocusDelta.getValueFor(element), + neutralFillRestDelta.getValueFor(element), + neutralFillHoverDelta.getValueFor(element), + neutralFillActiveDelta.getValueFor(element) + ) + }); + + /** @public */ + export const errorFillRest = create('error-fill-rest').withDefault( + (element: HTMLElement) => { + return errorFillRecipe.getValueFor(element).evaluate(element).rest; + } + ); + /** @public */ + export const errorFillHover = create('error-fill-hover').withDefault( + (element: HTMLElement) => { + return errorFillRecipe.getValueFor(element).evaluate(element).hover; + } + ); + /** @public */ + export const errorFillActive = create('error-fill-active').withDefault( + (element: HTMLElement) => { + return errorFillRecipe.getValueFor(element).evaluate(element).active; + } + ); + /** @public */ + export const errorFillFocus = create('error-fill-focus').withDefault( + (element: HTMLElement) => { + return errorFillRecipe.getValueFor(element).evaluate(element).focus; + } + ); + + // Foreground On Error + const foregroundOnErrorByContrast = + (contrast: number) => (element: HTMLElement, reference?: Swatch) => { + return foregroundOnErrorAlgorithm( + reference || errorFillRest.getValueFor(element), + contrast + ); + }; + + /** @public */ + export const foregroundOnErrorRecipe = create({ + name: 'foreground-on-error-recipe', + cssCustomPropertyName: null + }).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnErrorByContrast(ContrastTarget.normal)(element, reference) + }); + /** @public */ + export const foregroundOnErrorRest = create( + 'foreground-on-error-rest' + ).withDefault((element: HTMLElement) => + foregroundOnErrorRecipe + .getValueFor(element) + .evaluate(element, errorFillRest.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorHover = create( + 'foreground-on-error-hover' + ).withDefault((element: HTMLElement) => + foregroundOnErrorRecipe + .getValueFor(element) + .evaluate(element, errorFillHover.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorActive = create( + 'foreground-on-error-active' + ).withDefault((element: HTMLElement) => + foregroundOnErrorRecipe + .getValueFor(element) + .evaluate(element, errorFillActive.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorFocus = create( + 'foreground-on-error-focus' + ).withDefault((element: HTMLElement) => + foregroundOnErrorRecipe + .getValueFor(element) + .evaluate(element, errorFillFocus.getValueFor(element)) + ); + + /** @public */ + export const foregroundOnErrorLargeRecipe = create({ + name: 'foreground-on-error-large-recipe', + cssCustomPropertyName: null + }).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): Swatch => + foregroundOnErrorByContrast(ContrastTarget.large)(element, reference) + }); + /** @public */ + export const foregroundOnErrorRestLarge = create( + 'foreground-on-error-rest-large' + ).withDefault((element: HTMLElement) => + foregroundOnErrorLargeRecipe + .getValueFor(element) + .evaluate(element, errorFillRest.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorHoverLarge = create( + 'foreground-on-error-hover-large' + ).withDefault((element: HTMLElement) => + foregroundOnErrorLargeRecipe + .getValueFor(element) + .evaluate(element, errorFillHover.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorActiveLarge = create( + 'foreground-on-error-active-large' + ).withDefault((element: HTMLElement) => + foregroundOnErrorLargeRecipe + .getValueFor(element) + .evaluate(element, errorFillActive.getValueFor(element)) + ); + /** @public */ + export const foregroundOnErrorFocusLarge = create( + 'foreground-on-error-focus-large' + ).withDefault((element: HTMLElement) => + foregroundOnErrorLargeRecipe + .getValueFor(element) + .evaluate(element, errorFillFocus.getValueFor(element)) + ); + + // Error Foreground + const errorForegroundByContrast = + (contrast: number) => (element: HTMLElement, reference?: Swatch) => + errorForegroundAlgorithm( + errorPalette.getValueFor(element), + reference || fillColor.getValueFor(element), + contrast, + accentForegroundRestDelta.getValueFor(element), + accentForegroundHoverDelta.getValueFor(element), + accentForegroundActiveDelta.getValueFor(element), + accentForegroundFocusDelta.getValueFor(element) + ); + + /** @public */ + export const errorForegroundRecipe = create({ + name: 'error-foreground-recipe', + cssCustomPropertyName: null + }).withDefault({ + evaluate: (element: HTMLElement, reference?: Swatch): InteractiveSwatchSet => + errorForegroundByContrast(ContrastTarget.normal)(element, reference) + }); + + /** @public */ + export const errorForegroundRest = create( + 'error-foreground-rest' + ).withDefault( + (element: HTMLElement) => + errorForegroundRecipe.getValueFor(element).evaluate(element).rest + ); + /** @public */ + export const errorForegroundHover = create( + 'error-foreground-hover' + ).withDefault( + (element: HTMLElement) => + errorForegroundRecipe.getValueFor(element).evaluate(element).hover + ); + /** @public */ + export const errorForegroundActive = create( + 'error-foreground-active' + ).withDefault( + (element: HTMLElement) => + errorForegroundRecipe.getValueFor(element).evaluate(element).active + ); + /** @public */ + export const errorForegroundFocus = create( + 'error-foreground-focus' + ).withDefault( + (element: HTMLElement) => + errorForegroundRecipe.getValueFor(element).evaluate(element).focus + ); + \ No newline at end of file diff --git a/packages/components/src/dialog/dialog.pw.spec.ts b/packages/components/src/dialog/dialog.pw.spec.ts new file mode 100644 index 00000000..8d0cc5b8 --- /dev/null +++ b/packages/components/src/dialog/dialog.pw.spec.ts @@ -0,0 +1,98 @@ +import type { Dialog as JpDialogType } from "@microsoft/fast-foundation"; +import chai from "chai"; + +const { expect } = chai; + +type JpDialog = HTMLElement & JpDialogType; + +describe("JpDialog", function () { + beforeEach(async function () { + if (!this.page && !this.browser) { + this.skip(); + } + + this.documentHandle = await this.page.evaluateHandle(() => document); + + this.setupHandle = await this.page.evaluateHandle( + (document) => { + const element = document.createElement("fast-dialog") as JpDialog; + element.id = "testelement"; + + const button1 = document.createElement("button"); + button1.id = "button1"; + element.appendChild(button1); + + const button2 = document.createElement("button"); + button2.id = "button2"; + element.appendChild(button2); + + document.body.appendChild(element); + }, + this.documentHandle + ); + }); + + afterEach(async function () { + if (this.setupHandle) { + await this.setupHandle.dispose(); + } + }); + + // FASTDialog should render on the page + it("should render on the page", async function () { + const element = await this.page.$("fast-dialog"); + + expect(element).to.exist; + }); + + // FASTDialog should focus on the first element + it("should focus on first element", async function () { + const element = await this.page.$("fast-dialog"); + + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button1"); + }); + + // FASTDialog should trap focus + it("should trap focus", async function () { + const element = await this.page.$("fast-dialog"); + + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button1"); + + await element?.press("Tab"); + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button2"); + + await element?.press("Tab"); + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button1"); + + await element?.press("Shift+Tab"); + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button2"); + + await element?.press("Shift+Tab"); + expect( + await this.page.evaluate( + () => document.activeElement?.id + ) + ).to.equal("button1"); + + }); +}); diff --git a/packages/components/src/dialog/dialog.styles.ts b/packages/components/src/dialog/dialog.styles.ts new file mode 100644 index 00000000..badecfe6 --- /dev/null +++ b/packages/components/src/dialog/dialog.styles.ts @@ -0,0 +1,57 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { controlCornerRadius, fillColor, strokeWidth } from "../design-tokens.js"; +import { elevation } from "../styles/elevation.js"; + +/** + * Styles for Dialog + * @public + */ +export const dialogStyles: FoundationElementTemplate = ( + context, + definition +) => css` + :host([hidden]) { + display: none; + } + + :host { + --elevation: 14; + --dialog-height: 480px; + --dialog-width: 640px; + display: block; + } + + .overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + touch-action: none; + } + + .positioning-region { + display: flex; + justify-content: center; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: auto; + } + + .control { + ${elevation} + margin-top: auto; + margin-bottom: auto; + width: var(--dialog-width); + height: var(--dialog-height); + background-color: ${fillColor}; + z-index: 1; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid transparent; + } +`; diff --git a/packages/components/src/dialog/index.ts b/packages/components/src/dialog/index.ts index ba5c7c43..ce54b2e5 100644 --- a/packages/components/src/dialog/index.ts +++ b/packages/components/src/dialog/index.ts @@ -1,8 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { Dialog, dialogTemplate as template } from '@microsoft/fast-foundation'; -import { dialogStyles as styles } from '@microsoft/fast-components'; +import { Dialog, dialogTemplate as template } from "@microsoft/fast-foundation"; +import { dialogStyles as styles } from "./dialog.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Dialog} registration for configuring the component with a DesignSystem. @@ -12,12 +9,12 @@ import { dialogStyles as styles } from '@microsoft/fast-components'; * @public * @remarks * HTML Element: `` - * +* */ export const jpDialog = Dialog.compose({ - baseName: 'dialog', - template, - styles + baseName: 'dialog', + template, + styles, }); /** diff --git a/packages/components/src/disclosure/disclosure.stories.ts b/packages/components/src/disclosure/disclosure.stories.ts new file mode 100644 index 00000000..8e028b4c --- /dev/null +++ b/packages/components/src/disclosure/disclosure.stories.ts @@ -0,0 +1,8 @@ +import DisclosureTemplate from "./fixtures/disclosure.html"; +import "./index.js"; + +export default { + title: "Disclosure", +}; + +export const Disclosure = () => DisclosureTemplate; diff --git a/packages/components/src/disclosure/disclosure.styles.ts b/packages/components/src/disclosure/disclosure.styles.ts new file mode 100644 index 00000000..80d1f2b1 --- /dev/null +++ b/packages/components/src/disclosure/disclosure.styles.ts @@ -0,0 +1,91 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + accentFillActive, + accentFillHover, + accentFillRest, + accentForegroundActive, + accentForegroundHover, + accentForegroundRest, + bodyFont, + controlCornerRadius, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + strokeWidth, + typeRampBaseFontSize, +} from "../design-tokens.js"; + +/** + * Styles for Disclosure + * @public + */ +export const disclosureStyles: FoundationElementTemplate = ( + context, + definition +) => css` + .disclosure { + transition: height 0.35s; + } + + .disclosure .invoker::-webkit-details-marker { + display: none; + } + + .disclosure .invoker { + list-style-type: none; + } + + :host([appearance="accent"]) .invoker { + background: ${accentFillRest}; + color: ${foregroundOnAccentRest}; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + border-radius: calc(${controlCornerRadius} * 1px); + outline: none; + cursor: pointer; + margin: 16px 0; + padding: 12px; + max-width: max-content; + } + + :host([appearance="accent"]) .invoker:active { + background: ${accentFillActive}; + color: ${foregroundOnAccentActive}; + } + + :host([appearance="accent"]) .invoker:hover { + background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; + } + + :host([appearance="lightweight"]) .invoker { + background: transparent; + color: ${accentForegroundRest}; + border-bottom: calc(${strokeWidth} * 1px) solid ${accentForegroundRest}; + cursor: pointer; + width: max-content; + margin: 16px 0; + } + + :host([appearance="lightweight"]) .invoker:active { + border-bottom-color: ${accentForegroundActive}; + } + + :host([appearance="lightweight"]) .invoker:hover { + border-bottom-color: ${accentForegroundHover}; + } + + .disclosure[open] .invoker ~ * { + animation: fadeIn 0.5s ease-in-out; + } + + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } +`; diff --git a/packages/components/src/disclosure/fixtures/disclosure.html b/packages/components/src/disclosure/fixtures/disclosure.html new file mode 100644 index 00000000..3dbbbf86 --- /dev/null +++ b/packages/components/src/disclosure/fixtures/disclosure.html @@ -0,0 +1,84 @@ + +

Disclosure

+

+ The Flash (or simply Flash) is the name of several superheroes appearing in American + comic books published by DC Comics. +

+ + ⚡️ +
+ Created by writer Gardner Fox and artist Harry Lampert, the original Flash first + appeared in Flash Comics #1 (cover date January 1940/release month November 1939). + Nicknamed the "Scarlet Speedster", all incarnations of the Flash possess "super + speed", which includes the ability to run, move, and think extremely fast, use + superhuman reflexes, and seemingly violate certain laws of physics. +
+
+
+
+ +

Default expanded

+ +
+ Green Arrow is a fictional superhero who appears in comic books published by DC + Comics. Created by Mort Weisinger and designed by George Papp, he first appeared + in More Fun Comics #73 in November 1941. His real name is Oliver Jonas Queen, a + wealthy businessman and owner of Queen Industries who is also a well-known + celebrity in Star City. +
+
+
+
+ +

Helper methods (toggle(), show(), hide())

+ + +
+ Supergirl is an American superhero television series developed by Ali Adler, Greg + Berlanti and Andrew Kreisberg that originally aired on CBS and premiered on + October 26, 2015. It is based on the DC Comics character Supergirl, created by + Otto Binder and Al Plastino, and stars Melissa Benoist in the title role. +
+
+
+
+
+ + Toggle + +  + + Show + +  + + Hide + +
+
+ +

With lightweight (also extra slots)

+ + 👩🏻‍🦳 + Read about White Canary +
+ Sara Lance, also known by her alter-ego White Canary, is a fictional character in + The CW's Arrowverse franchise, first introduced in the 2012 pilot episode of the + television series Arrow, and later starring in Legends of Tomorrow. The character + is an original character to the television series, created by Greg Berlanti, Marc + Guggenheim and Andrew Kreisberg, but incorporates character and plot elements of + the DC Comics character Black Canary. Sara Lance was originally portrayed by + Jacqueline MacInnes Wood in the pilot episode, and has since been continually + portrayed by Caity Lotz. Sara initially goes by the moniker of The Canary, a + translation of her Arabic League of Assassins name الطائر الصافر (Ta-er + al-Sahfer), which translates to "Whistling Bird". She later adopts the code name + of White Canary before joining the Legends of Tomorrow. +
+
diff --git a/packages/components/src/disclosure/index.ts b/packages/components/src/disclosure/index.ts new file mode 100644 index 00000000..28e03939 --- /dev/null +++ b/packages/components/src/disclosure/index.ts @@ -0,0 +1,101 @@ +import { attr } from "@microsoft/fast-element"; +import { + Disclosure as FoundationDisclosure, + disclosureTemplate as template, +} from "@microsoft/fast-foundation"; +import { disclosureStyles as styles } from "./disclosure.styles.js"; +/** + * Types of anchor appearance. + * @public + */ +export type DisclosureAppearance = "accent" | "lightweight"; + +/** + * @internal + */ +export class Disclosure extends FoundationDisclosure { + /** + * Disclosure default height + */ + private height: number = 0; + /** + * Disclosure height after it's expanded + */ + private totalHeight: number = 0; + + public connectedCallback(): void { + super.connectedCallback(); + if (!this.appearance) { + this.appearance = "accent"; + } + } + + /** + * The appearance the anchor should have. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance?: DisclosureAppearance; + public appearanceChanged( + oldValue: DisclosureAppearance, + newValue: DisclosureAppearance + ): void { + if (oldValue !== newValue) { + this.classList.add(newValue); + this.classList.remove(oldValue); + } + } + + /** + * Set disclosure height while transitioning + * @override + */ + protected onToggle() { + super.onToggle(); + this.details.style.setProperty("height", `${this.disclosureHeight}px`); + } + + /** + * Calculate disclosure height before and after expanded + * @override + */ + protected setup() { + super.setup(); + + const getCurrentHeight = () => this.details.getBoundingClientRect().height; + this.show(); + this.totalHeight = getCurrentHeight(); + this.hide(); + this.height = getCurrentHeight(); + + if (this.expanded) { + this.show(); + } + } + + get disclosureHeight(): number { + return this.expanded ? this.totalHeight : this.height; + } +} + +/** + * A function that returns a {@link @microsoft/fast-foundation#Disclosure} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#disclosureTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + * + */ +export const jpDisclosure = Disclosure.compose({ + baseName: "disclosure", + baseClass: FoundationDisclosure, + template, + styles, +}); + +export { styles as disclosureStyles }; diff --git a/packages/components/src/disclosure/scenarios/index.html b/packages/components/src/disclosure/scenarios/index.html new file mode 100644 index 00000000..bd2b7ab2 --- /dev/null +++ b/packages/components/src/disclosure/scenarios/index.html @@ -0,0 +1,17 @@ + diff --git a/packages/components/src/divider/divider.styles.ts b/packages/components/src/divider/divider.styles.ts new file mode 100644 index 00000000..07ff3499 --- /dev/null +++ b/packages/components/src/divider/divider.styles.ts @@ -0,0 +1,28 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { designUnit, neutralStrokeDividerRest, strokeWidth } from "../design-tokens.js"; + +/** + * Styles for Divider + * @public + */ +export const dividerStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("block")} :host { + box-sizing: content-box; + height: 0; + margin: calc(${designUnit} * 1px) 0; + border-top: calc(${strokeWidth} * 1px) solid ${neutralStrokeDividerRest}; + border-left: none; + } + + :host([orientation="vertical"]) { + height: 100%; + margin: 0 calc(${designUnit} * 1px); + border-top: none; + border-left: calc(${strokeWidth} * 1px) solid ${neutralStrokeDividerRest}; + } + `; diff --git a/packages/components/src/divider/index.ts b/packages/components/src/divider/index.ts index fd597f99..0598b473 100644 --- a/packages/components/src/divider/index.ts +++ b/packages/components/src/divider/index.ts @@ -1,11 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { - Divider, - dividerTemplate as template -} from '@microsoft/fast-foundation'; -import { dividerStyles as styles } from '@microsoft/fast-components'; +import { Divider, dividerTemplate as template } from "@microsoft/fast-foundation"; +import { dividerStyles as styles } from "./divider.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Divider} registration for configuring the component with a DesignSystem. @@ -17,9 +11,9 @@ import { dividerStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpDivider = Divider.compose({ - baseName: 'divider', - template, - styles + baseName: 'divider', + template, + styles }); /** diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 46791d77..c435ebff 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,48 +1,61 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -export { - addJupyterLabThemeChangeListener, - applyJupyterTheme -} from './utilities/theme/applyTheme'; +/** + * Export all custom element definitions. + */ + -export * from './color'; -export * from './design-tokens'; -export * from './jupyter-design-system'; -export * from './custom-elements'; +export { + addJupyterLabThemeChangeListener, + applyJupyterTheme + } from './utilities/theme/applyTheme'; -// Export components and classes -export * from './accordion/index'; -export * from './accordion-item/index'; -export * from './anchor/index'; -export * from './anchored-region/index'; -export * from './avatar/index'; -export * from './badge/index'; -export * from './breadcrumb/index'; -export * from './breadcrumb-item/index'; -export * from './button/index'; -export * from './card/index'; -export * from './checkbox/index'; -export * from './combobox/index'; -export * from './date-field/index'; -export * from './data-grid/index'; -export * from './dialog/index'; -export * from './divider/index'; -export * from './listbox/index'; -export * from './menu/index'; -export * from './menu-item/index'; -export * from './number-field/index'; -export * from './option/index'; -export * from './progress/index'; -export * from './radio/index'; -export * from './radio-group/index'; -export * from './search/index'; -export * from './select/index'; -export * from './slider-label/index'; -export * from './tab-panel/index'; -export * from './tab/index'; -export * from './tabs/index'; -export * from './text-area/index'; -export * from './text-field/index'; -export * from './toolbar/index'; -export * from './tooltip/index'; +export * from "./custom-elements.js"; +export * from "./jupyter-design-system.js"; +export * from "./accordion/index.js"; +export * from "./anchor/index.js"; +export * from "./anchored-region/index.js"; +export * from "./avatar/index.js"; +export * from "./badge/index.js"; +export * from "./breadcrumb/index.js"; +export * from "./breadcrumb-item/index.js"; +export * from "./button/index.js"; +export * from "./card/index.js"; +export * from "./checkbox/index.js"; +export * from "./combobox/index.js"; +export * from "./data-grid/index.js"; +export * from "./design-system-provider/index.js"; +export { Palette, PaletteRGB } from "./color/palette.js"; +export { InteractiveSwatchSet } from "./color/recipe.js"; +export { Swatch, SwatchRGB } from "./color/swatch.js"; +export { isDark } from "./color/utilities/is-dark.js"; +export { StandardLuminance } from "./color/utilities/base-layer-luminance.js"; +export * from "./design-tokens.js"; +export * from "./dialog/index.js"; +export * from "./disclosure/index.js"; +export * from "./divider/index.js"; +export * from "./listbox/index.js"; +export * from "./menu/index.js"; +export * from "./menu-item/index.js"; +export * from "./number-field/index.js"; +export * from "./option/index.js"; +export * from "./picker/index.js"; +export * from "./progress/index.js"; +export * from "./progress-ring/index.js"; +export * from "./radio/index.js"; +export * from "./radio-group/index.js"; +export * from "./search/index.js"; +export * from "./select/index.js"; +export * from "./skeleton/index.js"; +export * from "./slider/index.js"; +export * from "./slider-label/index.js"; +export * from "./styles/direction.js"; +export * from "./switch/index.js"; +export * from "./tabs/index.js"; +export * from "./text-area/index.js"; +export * from "./text-field/index.js"; +export * from "./toolbar/index.js"; +export * from "./tooltip/index.js"; +export * from "./tree-view/index.js"; +export * from "./tree-item/index.js"; diff --git a/packages/components/src/listbox/index.ts b/packages/components/src/listbox/index.ts index 69b470eb..3f221f21 100644 --- a/packages/components/src/listbox/index.ts +++ b/packages/components/src/listbox/index.ts @@ -1,32 +1,73 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { css, ElementStyles } from "@microsoft/fast-element"; import { - ListboxElement, - listboxTemplate as template -} from '@microsoft/fast-foundation'; -import { listboxStyles as styles } from './listbox.styles'; + ListboxElement as FoundationListboxElement, + listboxTemplate as template, +} from "@microsoft/fast-foundation"; +import { listboxStyles as styles } from "./listbox.styles.js"; /** - * The Jupyter listbox Custom Element. Implements, {@link @microsoft/fast-foundation#Listbox} - * {@link @microsoft/fast-foundation#ListboxTemplate} - * + * Base class for Listbox. * * @public - * @remarks - * HTML Element: \ - * */ -export const jpListbox = ListboxElement.compose({ - baseName: 'listbox', - template, - styles -}); +export class Listbox extends FoundationListboxElement { + /** + * An internal stylesheet to hold calculated CSS custom properties. + * + * @internal + */ + private computedStylesheet?: ElementStyles; + + /** + * Updates the component dimensions when the size property is changed. + * + * @param prev - the previous size value + * @param next - the current size value + * + * @internal + */ + protected sizeChanged(prev: number | undefined, next: number): void { + super.sizeChanged(prev, next); + this.updateComputedStylesheet(); + } + + /** + * Updates an internal stylesheet with calculated CSS custom properties. + * + * @internal + */ + protected updateComputedStylesheet(): void { + if (this.computedStylesheet) { + this.$fastController.removeStyles(this.computedStylesheet); + } + + const listboxSize = `${this.size}`; + + this.computedStylesheet = css` + :host { + --size: ${listboxSize}; + } + `; + + this.$fastController.addStyles(this.computedStylesheet); + } +} /** - * Base class for ListBox + * A function that returns a {@link @microsoft/fast-foundation#ListboxElement} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#listboxTemplate} + * + * @remarks + * Generates HTML Element: `` + * * @public + * */ -export { ListboxElement }; +export const jpListbox = Listbox.compose({ + baseName: "listbox", + baseClass: FoundationListboxElement, + template, + styles, +}); export { styles as listboxStyles }; diff --git a/packages/components/src/listbox/listbox.pw.spec.ts b/packages/components/src/listbox/listbox.pw.spec.ts new file mode 100644 index 00000000..6b9e3eaf --- /dev/null +++ b/packages/components/src/listbox/listbox.pw.spec.ts @@ -0,0 +1,103 @@ +import type { + ListboxElement as FASTListboxType, + ListboxOption as FASTOption, +} from "@microsoft/fast-foundation"; +import type { ElementHandle } from "playwright"; +import chai from "chai"; + +const { expect } = chai; + +type jpListbox = HTMLElement & FASTListboxType; + +describe("jpListbox", function () { + beforeEach(async function () { + if (!this.page && !this.browser) { + this.skip(); + } + + this.documentHandle = await this.page.evaluateHandle(() => document); + + this.setupHandle = await this.page.evaluateHandle( + (document) => { + const element = document.createElement("jp-listbox") as jpListbox; + + for (let i = 1; i <= 3; i++) { + const option = document.createElement("jp-option") as FASTOption; + option.value = `${i}`; + option.textContent = `option ${i}`; + element.appendChild(option); + } + + document.body.appendChild(element) + }, + this.documentHandle + ); + }); + + afterEach(async function () { + if (this.setupHandle) { + await this.setupHandle.dispose(); + } + }); + + // jpListbox should render on the page + it("should render on the page", async function () { + const element = await this.page.waitForSelector("jp-listbox"); + + expect(element).to.exist; + }); + + describe("should change the `selectedIndex` when focused and receives keyboard interaction", function () { + it("via arrow down key", async function () { + const element = (await this.page.waitForSelector( + "jp-listbox" + )) as ElementHandle; + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(-1); + + await element.press("ArrowDown"); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(0); + }); + + it("via arrow up key", async function () { + const element = (await this.page.waitForSelector( + "jp-listbox" + )) as ElementHandle; + + await element.evaluate(node => (node.selectedIndex = 1)); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(1); + + await element.press("ArrowUp"); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(0); + }); + + it("via home key", async function () { + const element = (await this.page.waitForSelector( + "jp-listbox" + )) as ElementHandle; + + await element.evaluate(node => (node.selectedIndex = 2)); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(2); + + await element.press("Home"); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(0); + }); + + it("via end key", async function () { + const element = (await this.page.waitForSelector( + "jp-listbox" + )) as ElementHandle; + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(-1); + + await element.press("End"); + + expect(await element.evaluate(node => node.selectedIndex)).to.equal(2); + }); + }); +}); diff --git a/packages/components/src/listbox/listbox.styles.ts b/packages/components/src/listbox/listbox.styles.ts index d8a054e9..b66d6ba8 100644 --- a/packages/components/src/listbox/listbox.styles.ts +++ b/packages/components/src/listbox/listbox.styles.ts @@ -1,48 +1,43 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - -import type { ElementStyles } from '@microsoft/fast-element'; -import { css } from '@microsoft/fast-element'; -import type { FoundationElementTemplate } from '@microsoft/fast-foundation'; +import type { ElementStyles } from "@microsoft/fast-element"; +import { css } from "@microsoft/fast-element"; +import type { FoundationElementTemplate } from "@microsoft/fast-foundation"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - ListboxElement, - ListboxOption -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + ListboxElement, + ListboxOption, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - controlCornerRadius, - designUnit, - disabledOpacity, - fillColor, - focusStrokeOuter, - focusStrokeWidth, - neutralStrokeRest, - strokeWidth -} from '@microsoft/fast-components'; -import { heightNumber } from '../styles/size'; + controlCornerRadius, + designUnit, + disabledOpacity, + fillColor, + focusStrokeOuter, + focusStrokeWidth, + neutralStrokeRest, + strokeWidth, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/size.js"; /** * Styles for Listbox * @public */ export const listboxStyles: FoundationElementTemplate = ( - context, - definition + context, + definition ) => { - const ListboxOptionTag = context.tagFor(ListboxOption); - const hostContext = - context.name === context.tagFor(ListboxElement) ? '' : '.listbox'; + const ListboxOptionTag = context.tagFor(ListboxOption); + const hostContext = context.name === context.tagFor(ListboxElement) ? "" : ".listbox"; - // The expression interpolations present in this block cause Prettier to generate - // various formatting bugs. - // prettier-ignore - return css` - ${!hostContext ? display('inline-flex') : ''} + // The expression interpolations present in this block cause Prettier to generate + // various formatting bugs. + // prettier-ignore + return css` + ${!hostContext ? display("inline-flex") : ""} :host ${hostContext} { background: ${fillColor}; @@ -54,7 +49,7 @@ export const listboxStyles: FoundationElementTemplate = ( } ${!hostContext ? css` - :host(:${focusVisible}:not([disabled])) { +:host(:${focusVisible}:not([disabled])) { outline: none; } diff --git a/packages/components/src/menu-item/index.ts b/packages/components/src/menu-item/index.ts index 63d74d36..d13f2f06 100644 --- a/packages/components/src/menu-item/index.ts +++ b/packages/components/src/menu-item/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - MenuItem, - MenuItemOptions, - menuItemTemplate as template -} from '@microsoft/fast-foundation'; -import { menuItemStyles as styles } from './menu-item.styles'; + MenuItem, + MenuItemOptions, + menuItemTemplate as template, +} from "@microsoft/fast-foundation"; +import { menuItemStyles as styles } from "./menu-item.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#MenuItem} registration for configuring the component with a DesignSystem. @@ -18,38 +15,38 @@ import { menuItemStyles as styles } from './menu-item.styles'; * Generates HTML Element: `` */ export const jpMenuItem = MenuItem.compose({ - baseName: 'menu-item', - template, - styles, - checkboxIndicator: /* html */ ` - - - + baseName: 'menu-item', + template, + styles, + checkboxIndicator: /* html */ ` + + + + `, + expandCollapseGlyph: /* html */ ` + + + `, - expandCollapseGlyph: /* html */ ` - - - + radioIndicator: /* html */ ` + `, - radioIndicator: /* html */ ` - - ` }); /** diff --git a/packages/components/src/menu-item/menu-item.styles.ts b/packages/components/src/menu-item/menu-item.styles.ts index 734fd281..20347ffc 100644 --- a/packages/components/src/menu-item/menu-item.styles.ts +++ b/packages/components/src/menu-item/menu-item.styles.ts @@ -1,365 +1,359 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { css, ElementStyles } from '@microsoft/fast-element'; +import { css, ElementStyles } from "@microsoft/fast-element"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - MenuItemOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + MenuItemOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - accentFillRest, - bodyFont, - controlCornerRadius, - designUnit, - DirectionalStyleSheetBehavior, - disabledOpacity, - focusStrokeOuter, - focusStrokeWidth, - foregroundOnAccentRest, - neutralFillStealthRest, - neutralForegroundHint, - neutralForegroundRest, - neutralLayer2, - neutralLayer3, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentRest, + neutralFillRest, + neutralFillStealthActive, + neutralFillStealthFocus, + neutralFillStealthHover, + neutralFillStealthRest, + neutralForegroundHint, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { DirectionalStyleSheetBehavior, heightNumber } from "../styles/index.js"; /** * Styles for Menu item * @public */ -export const menuItemStyles: FoundationElementTemplate< - ElementStyles, - MenuItemOptions -> = (context, definition) => - css` - ${display('grid')} :host { - contain: layout; - overflow: visible; - font-family: ${bodyFont}; - outline: none; - box-sizing: border-box; - height: calc(${heightNumber} * 1px); - grid-template-columns: minmax(42px, auto) 1fr minmax(42px, auto); - grid-template-rows: auto; - justify-items: center; - align-items: center; - padding: 0; - margin: 0 calc(${designUnit} * 1px); - white-space: nowrap; - color: ${neutralForegroundRest}; - fill: currentcolor; - cursor: pointer; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${focusStrokeWidth} * 1px) solid transparent; - } - - :host(:hover) { - position: relative; - z-index: 1; - } - - :host(.indent-0) { - grid-template-columns: auto 1fr minmax(42px, auto); - } - :host(.indent-0) .content { - grid-column: 1; - grid-row: 1; - margin-inline-start: 10px; - } - :host(.indent-0) .expand-collapse-glyph-container { - grid-column: 5; - grid-row: 1; - } - :host(.indent-2) { - grid-template-columns: - minmax(42px, auto) minmax(42px, auto) 1fr minmax(42px, auto) - minmax(42px, auto); - } - :host(.indent-2) .content { - grid-column: 3; - grid-row: 1; - margin-inline-start: 10px; - } - :host(.indent-2) .expand-collapse-glyph-container { - grid-column: 5; - grid-row: 1; - } - :host(.indent-2) .start { - grid-column: 2; - } - :host(.indent-2) .end { - grid-column: 4; - } - - :host(:${focusVisible}) { - border-color: ${focusStrokeOuter}; - background: ${neutralLayer3}; - color: ${neutralForegroundRest}; - } - - :host(:hover) { - background: ${neutralLayer3}; - color: ${neutralForegroundRest}; - } - - :host([aria-checked='true']), - :host(:active), - :host(.expanded) { - background: ${neutralLayer2}; - color: ${neutralForegroundRest}; - } - - :host([disabled]) { - cursor: ${disabledCursor}; - opacity: ${disabledOpacity}; - } - - :host([disabled]:hover) { - color: ${neutralForegroundRest}; - fill: currentcolor; - background: ${neutralFillStealthRest}; - } - - :host([disabled]:hover) .start, - :host([disabled]:hover) .end, - :host([disabled]:hover)::slotted(svg) { - fill: ${neutralForegroundRest}; - } - - .expand-collapse-glyph { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - width: 16px; - height: 16px; - fill: currentcolor; - } - - .content { - grid-column-start: 2; - justify-self: start; - overflow: hidden; - text-overflow: ellipsis; - } - - .start, - .end { - display: flex; - justify-content: center; - } - - ::slotted(svg) { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - width: 16px; - height: 16px; - } - - :host(:hover) .start, - :host(:hover) .end, - :host(:hover)::slotted(svg), - :host(:active) .start, - :host(:active) .end, - :host(:active)::slotted(svg) { - fill: ${neutralForegroundRest}; - } - - :host(.indent-0[aria-haspopup='menu']) { - display: grid; - grid-template-columns: minmax(42px, auto) auto 1fr minmax(42px, auto) minmax( - 42px, - auto - ); - align-items: center; - min-height: 32px; - } - - :host(.indent-1[aria-haspopup='menu']), - :host(.indent-1[role='menuitemcheckbox']), - :host(.indent-1[role='menuitemradio']) { - display: grid; - grid-template-columns: minmax(42px, auto) auto 1fr minmax(42px, auto) minmax( - 42px, - auto - ); - align-items: center; - min-height: 32px; - } - - :host(.indent-2:not([aria-haspopup='menu'])) .end { - grid-column: 5; - } - - :host .input-container, - :host .expand-collapse-glyph-container { - display: none; - } - - :host([aria-haspopup='menu']) .expand-collapse-glyph-container, - :host([role='menuitemcheckbox']) .input-container, - :host([role='menuitemradio']) .input-container { - display: grid; - margin-inline-end: 10px; - } - - :host([aria-haspopup='menu']) .content, - :host([role='menuitemcheckbox']) .content, - :host([role='menuitemradio']) .content { - grid-column-start: 3; - } - - :host([aria-haspopup='menu'].indent-0) .content { - grid-column-start: 1; - } - - :host([aria-haspopup='menu']) .end, - :host([role='menuitemcheckbox']) .end, - :host([role='menuitemradio']) .end { - grid-column-start: 4; - } - - :host .expand-collapse, - :host .checkbox, - :host .radio { - display: flex; - align-items: center; - justify-content: center; - position: relative; - width: 20px; - height: 20px; - box-sizing: border-box; - outline: none; - margin-inline-start: 10px; - } - - :host .checkbox, - :host .radio { - border: calc(${strokeWidth} * 1px) solid ${neutralForegroundRest}; - } - - :host([aria-checked='true']) .checkbox, - :host([aria-checked='true']) .radio { - background: ${accentFillRest}; - border-color: ${accentFillRest}; - } - - :host .checkbox { - border-radius: calc(${controlCornerRadius} * 1px); - } - - :host .radio { - border-radius: 999px; - } - - :host .checkbox-indicator, - :host .radio-indicator, - :host .expand-collapse-indicator, - ::slotted([slot='checkbox-indicator']), - ::slotted([slot='radio-indicator']), - ::slotted([slot='expand-collapse-indicator']) { - display: none; - } - - ::slotted([slot='end']:not(svg)) { - margin-inline-end: 10px; - color: ${neutralForegroundHint}; - } - - :host([aria-checked='true']) .checkbox-indicator, - :host([aria-checked='true']) ::slotted([slot='checkbox-indicator']) { - width: 100%; - height: 100%; - display: block; - fill: ${foregroundOnAccentRest}; - pointer-events: none; - } - - :host([aria-checked='true']) .radio-indicator { - position: absolute; - top: 4px; - left: 4px; - right: 4px; - bottom: 4px; - border-radius: 999px; - display: block; - background: ${foregroundOnAccentRest}; - pointer-events: none; - } - - :host([aria-checked='true']) ::slotted([slot='radio-indicator']) { - display: block; - pointer-events: none; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host { - border-color: transparent; - color: ${SystemColors.ButtonText}; - forced-color-adjust: none; - } - - :host(:hover) { - background: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - :host(:hover) .start, - :host(:hover) .end, - :host(:hover)::slotted(svg), - :host(:active) .start, - :host(:active) .end, - :host(:active)::slotted(svg) { - fill: ${SystemColors.HighlightText}; - } - - :host(.expanded) { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - :host(:${focusVisible}) { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.ButtonText}; - box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) inset - ${SystemColors.HighlightText}; - color: ${SystemColors.HighlightText}; - fill: currentcolor; - } - - :host([disabled]), - :host([disabled]:hover), - :host([disabled]:hover) .start, - :host([disabled]:hover) .end, - :host([disabled]:hover)::slotted(svg) { - background: ${SystemColors.Canvas}; - color: ${SystemColors.GrayText}; - fill: currentcolor; - opacity: 1; - } - - :host .expanded-toggle, - :host .checkbox, - :host .radio { - border-color: ${SystemColors.ButtonText}; - background: ${SystemColors.HighlightText}; - } - - :host([checked='true']) .checkbox, - :host([checked='true']) .radio { - background: ${SystemColors.HighlightText}; - border-color: ${SystemColors.HighlightText}; - } - - :host(:hover) .expanded-toggle, +export const menuItemStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("grid")} :host { + contain: layout; + overflow: visible; + font-family: ${bodyFont}; + outline: none; + box-sizing: border-box; + height: calc(${heightNumber} * 1px); + grid-template-columns: minmax(42px, auto) 1fr minmax(42px, auto); + grid-template-rows: auto; + justify-items: center; + align-items: center; + padding: 0; + margin: 0 calc(${designUnit} * 1px); + white-space: nowrap; + background: ${neutralFillStealthRest}; + color: ${neutralForegroundRest}; + fill: currentcolor; + cursor: pointer; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${focusStrokeWidth} * 1px) solid transparent; + } + + :host(:hover) { + position: relative; + z-index: 1; + } + + :host(.indent-0) { + grid-template-columns: auto 1fr minmax(42px, auto); + } + :host(.indent-0) .content { + grid-column: 1; + grid-row: 1; + margin-inline-start: 10px; + } + :host(.indent-0) .expand-collapse-glyph-container { + grid-column: 5; + grid-row: 1; + } + :host(.indent-2) { + grid-template-columns: minmax(42px, auto) minmax(42px, auto) 1fr minmax(42px, auto) minmax(42px, auto); + } + :host(.indent-2) .content { + grid-column: 3; + grid-row: 1; + margin-inline-start: 10px; + } + :host(.indent-2) .expand-collapse-glyph-container { + grid-column: 5; + grid-row: 1; + } + :host(.indent-2) .start { + grid-column: 2; + } + :host(.indent-2) .end { + grid-column: 4; + } + + :host(:${focusVisible}) { + border-color: ${focusStrokeOuter}; + background: ${neutralFillStealthFocus}; + color: ${neutralForegroundRest}; + } + + :host(:hover) { + background: ${neutralFillStealthHover}; + color: ${neutralForegroundRest}; + } + + :host(:active) { + background: ${neutralFillStealthActive}; + } + + :host([aria-checked="true"]), + :host(.expanded) { + background: ${neutralFillRest}; + color: ${neutralForegroundRest}; + } + + :host([disabled]) { + cursor: ${disabledCursor}; + opacity: ${disabledOpacity}; + } + + :host([disabled]:hover) { + color: ${neutralForegroundRest}; + fill: currentcolor; + background: ${neutralFillStealthRest}; + } + + :host([disabled]:hover) .start, + :host([disabled]:hover) .end, + :host([disabled]:hover)::slotted(svg) { + fill: ${neutralForegroundRest}; + } + + .expand-collapse-glyph { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + fill: currentcolor; + } + + .content { + grid-column-start: 2; + justify-self: start; + overflow: hidden; + text-overflow: ellipsis; + } + + .start, + .end { + display: flex; + justify-content: center; + } + + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + } + + :host(:hover) .start, + :host(:hover) .end, + :host(:hover)::slotted(svg), + :host(:active) .start, + :host(:active) .end, + :host(:active)::slotted(svg) { + fill: ${neutralForegroundRest}; + } + + :host(.indent-0[aria-haspopup="menu"]) { + display: grid; + grid-template-columns: minmax(42px, auto) auto 1fr minmax(42px, auto) minmax(42px, auto); + align-items: center; + min-height: 32px; + } + + :host(.indent-1[aria-haspopup="menu"]), + :host(.indent-1[role="menuitemcheckbox"]), + :host(.indent-1[role="menuitemradio"]) { + display: grid; + grid-template-columns: minmax(42px, auto) auto 1fr minmax(42px, auto) minmax(42px, auto); + align-items: center; + min-height: 32px; + } + + :host(.indent-2:not([aria-haspopup="menu"])) .end { + grid-column: 5; + } + + :host .input-container, + :host .expand-collapse-glyph-container { + display: none; + } + + :host([aria-haspopup="menu"]) .expand-collapse-glyph-container, + :host([role="menuitemcheckbox"]) .input-container, + :host([role="menuitemradio"]) .input-container { + display: grid; + margin-inline-end: 10px; + } + + :host([aria-haspopup="menu"]) .content, + :host([role="menuitemcheckbox"]) .content, + :host([role="menuitemradio"]) .content { + grid-column-start: 3; + } + + :host([aria-haspopup="menu"].indent-0) .content { + grid-column-start: 1; + } + + :host([aria-haspopup="menu"]) .end, + :host([role="menuitemcheckbox"]) .end, + :host([role="menuitemradio"]) .end { + grid-column-start: 4; + } + + :host .expand-collapse, + :host .checkbox, + :host .radio { + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 20px; + height: 20px; + box-sizing: border-box; + outline: none; + margin-inline-start: 10px; + } + + :host .checkbox, + :host .radio { + border: calc(${strokeWidth} * 1px) solid ${neutralForegroundRest}; + } + + :host([aria-checked="true"]) .checkbox, + :host([aria-checked="true"]) .radio { + background: ${accentFillRest}; + border-color: ${accentFillRest}; + } + + :host .checkbox { + border-radius: calc(${controlCornerRadius} * 1px); + } + + :host .radio { + border-radius: 999px; + } + + :host .checkbox-indicator, + :host .radio-indicator, + :host .expand-collapse-indicator, + ::slotted([slot="checkbox-indicator"]), + ::slotted([slot="radio-indicator"]), + ::slotted([slot="expand-collapse-indicator"]) { + display: none; + } + + ::slotted([slot="end"]:not(svg)) { + margin-inline-end: 10px; + color: ${neutralForegroundHint} + } + + :host([aria-checked="true"]) .checkbox-indicator, + :host([aria-checked="true"]) ::slotted([slot="checkbox-indicator"]) { + width: 100%; + height: 100%; + display: block; + fill: ${foregroundOnAccentRest}; + pointer-events: none; + } + + :host([aria-checked="true"]) .radio-indicator { + position: absolute; + top: 4px; + left: 4px; + right: 4px; + bottom: 4px; + border-radius: 999px; + display: block; + background: ${foregroundOnAccentRest}; + pointer-events: none; + } + + :host([aria-checked="true"]) ::slotted([slot="radio-indicator"]) { + display: block; + pointer-events: none; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + border-color: transparent; + color: ${SystemColors.ButtonText}; + forced-color-adjust: none; + } + + :host(:hover) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host(:hover) .start, + :host(:hover) .end, + :host(:hover)::slotted(svg), + :host(:active) .start, + :host(:active) .end, + :host(:active)::slotted(svg) { + fill: ${SystemColors.HighlightText}; + } + + :host(.expanded) { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host(:${focusVisible}) { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.ButtonText}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) inset ${SystemColors.HighlightText}; + color: ${SystemColors.HighlightText}; + fill: currentcolor; + } + + :host([disabled]), + :host([disabled]:hover), + :host([disabled]:hover) .start, + :host([disabled]:hover) .end, + :host([disabled]:hover)::slotted(svg) { + background: ${SystemColors.Canvas}; + color: ${SystemColors.GrayText}; + fill: currentcolor; + opacity: 1; + } + + :host .expanded-toggle, + :host .checkbox, + :host .radio{ + border-color: ${SystemColors.ButtonText}; + background: ${SystemColors.HighlightText}; + } + + :host([checked="true"]) .checkbox, + :host([checked="true"]) .radio { + background: ${SystemColors.HighlightText}; + border-color: ${SystemColors.HighlightText}; + } + + :host(:hover) .expanded-toggle, :host(:hover) .checkbox, :host(:hover) .radio, :host(:${focusVisible}) .expanded-toggle, @@ -369,44 +363,45 @@ export const menuItemStyles: FoundationElementTemplate< :host([checked="true"]:hover) .radio, :host([checked="true"]:${focusVisible}) .checkbox, :host([checked="true"]:${focusVisible}) .radio { - border-color: ${SystemColors.HighlightText}; - } + border-color: ${SystemColors.HighlightText}; + } - :host([aria-checked='true']) { - background: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } + :host([aria-checked="true"]) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } - :host([aria-checked='true']) .checkbox-indicator, - :host([aria-checked='true']) ::slotted([slot='checkbox-indicator']), - :host([aria-checked='true']) ::slotted([slot='radio-indicator']) { - fill: ${SystemColors.Highlight}; - } + :host([aria-checked="true"]) .checkbox-indicator, + :host([aria-checked="true"]) ::slotted([slot="checkbox-indicator"]), + :host([aria-checked="true"]) ::slotted([slot="radio-indicator"]) { + fill: ${SystemColors.Highlight}; + } - :host([aria-checked='true']) .radio-indicator { - background: ${SystemColors.Highlight}; - } + :host([aria-checked="true"]) .radio-indicator { + background: ${SystemColors.Highlight}; + } - ::slotted([slot='end']:not(svg)) { - color: ${SystemColors.ButtonText}; - } + ::slotted([slot="end"]:not(svg)) { + color: ${SystemColors.ButtonText}; + } - :host(:hover) ::slotted([slot="end"]:not(svg)), + :host(:hover) ::slotted([slot="end"]:not(svg)), :host(:${focusVisible}) ::slotted([slot="end"]:not(svg)) { - color: ${SystemColors.HighlightText}; - } - `), - - new DirectionalStyleSheetBehavior( - css` - .expand-collapse-glyph { - transform: rotate(0deg); - } - `, - css` - .expand-collapse-glyph { - transform: rotate(180deg); - } - ` - ) - ); + color: ${SystemColors.HighlightText}; + } + ` + ), + + new DirectionalStyleSheetBehavior( + css` + .expand-collapse-glyph { + transform: rotate(0deg); + } + `, + css` + .expand-collapse-glyph { + transform: rotate(180deg); + } + ` + ) + ); diff --git a/packages/components/src/menu/index.ts b/packages/components/src/menu/index.ts index f2105df1..aa7c1640 100644 --- a/packages/components/src/menu/index.ts +++ b/packages/components/src/menu/index.ts @@ -1,8 +1,23 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. +import { + Menu as FoundationMenu, + menuTemplate as template, +} from "@microsoft/fast-foundation"; +import { fillColor, neutralLayerFloating } from "../design-tokens.js"; +import { menuStyles as styles } from "./menu.styles.js"; -import { Menu, menuTemplate as template } from '@microsoft/fast-foundation'; -import { menuStyles as styles } from '@microsoft/fast-components'; +/** + * @public + */ +export class Menu extends FoundationMenu { + /** + * @internal + */ + public connectedCallback(): void { + super.connectedCallback(); + + fillColor.setValueFor(this, neutralLayerFloating); + } +} /** * A function that returns a {@link @microsoft/fast-foundation#Menu} registration for configuring the component with a DesignSystem. @@ -14,15 +29,9 @@ import { menuStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpMenu = Menu.compose({ - baseName: 'menu', - template, - styles + baseName: 'menu', + template, + styles, }); -/** - * Base class for Menu - * @public - */ -export { Menu }; - export { styles as menuStyles }; diff --git a/packages/components/src/menu/menu.styles.ts b/packages/components/src/menu/menu.styles.ts new file mode 100644 index 00000000..9736b9fa --- /dev/null +++ b/packages/components/src/menu/menu.styles.ts @@ -0,0 +1,59 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + controlCornerRadius, + designUnit, + fillColor, + neutralStrokeDividerRest, + strokeWidth, +} from "../design-tokens.js"; +import { elevation } from "../styles/index.js"; + +/** + * Styles for Menu + * @public + */ +export const menuStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("block")} :host { + --elevation: 11; + background: ${fillColor}; + border: calc(${strokeWidth} * 1px) solid transparent; + ${elevation} + margin: 0; + border-radius: calc(${controlCornerRadius} * 1px); + padding: calc(${designUnit} * 1px) 0; + max-width: 368px; + min-width: 64px; + } + + :host([slot="submenu"]) { + width: max-content; + margin: 0 calc(${designUnit} * 1px); + } + + ::slotted(hr) { + box-sizing: content-box; + height: 0; + margin: 0; + border: none; + border-top: calc(${strokeWidth} * 1px) solid ${neutralStrokeDividerRest}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + background: ${SystemColors.Canvas}; + border-color: ${SystemColors.CanvasText}; + } + ` + ) + ); diff --git a/packages/components/src/number-field/index.ts b/packages/components/src/number-field/index.ts index c733205d..66f67ff0 100644 --- a/packages/components/src/number-field/index.ts +++ b/packages/components/src/number-field/index.ts @@ -1,16 +1,31 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { attr } from "@microsoft/fast-element"; import { - NumberField as FoundationNumberField, - NumberFieldOptions, - numberFieldTemplate as template -} from '@microsoft/fast-foundation'; -import { NumberField } from '@microsoft/fast-components'; -import { numberFieldStyles as styles } from './number-field.styles'; + NumberField as FoundationNumberField, + NumberFieldOptions, + numberFieldTemplate as template, +} from "@microsoft/fast-foundation"; +import { numberFieldStyles as styles } from "./number-field.styles.js"; + +/** + * Number field appearances + * @public + */ +export type NumberFieldAppearance = "filled" | "outline"; -// TODO -// we need to add error/invalid +/** + * @internal + */ +export class NumberField extends FoundationNumberField { + /** + * The appearance of the element. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance: NumberFieldAppearance = "outline"; +} /** * A function that returns a {@link @microsoft/fast-foundation#NumberField} registration for configuring the component with a DesignSystem. @@ -24,25 +39,19 @@ import { numberFieldStyles as styles } from './number-field.styles'; * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} */ export const jpNumberField = NumberField.compose({ - baseName: 'number-field', - baseClass: FoundationNumberField, - styles, - template, - shadowOptions: { - delegatesFocus: true - }, - stepDownGlyph: /* html */ ` + baseName: 'number-field', + baseClass: FoundationNumberField, + styles, + template, + shadowOptions: { + delegatesFocus: true, + }, + stepDownGlyph: /* html */ ` `, - stepUpGlyph: /* html */ ` + stepUpGlyph: /* html */ ` - ` + `, }); -export { NumberField, NumberFieldAppearance } from '@microsoft/fast-components'; - -/** - * Styles for NumberField - * @public - */ export { styles as numberFieldStyles }; diff --git a/packages/components/src/option/index.ts b/packages/components/src/option/index.ts index dfebd945..6c5a62cf 100644 --- a/packages/components/src/option/index.ts +++ b/packages/components/src/option/index.ts @@ -1,32 +1,29 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - import { ListboxOption, - listboxOptionTemplate as template -} from '@microsoft/fast-foundation'; -import { optionStyles as styles } from './option.styles'; + listboxOptionTemplate as template, +} from "@microsoft/fast-foundation"; +import { optionStyles as styles } from "./option.styles.js"; /** - * A function that returns a Option registration for configuring the component with a DesignSystem. - * - * - * @public - * @remarks - * Generates HTML Element: `` - * - */ +* A function that returns a {@link @microsoft/fast-foundation#ListboxOption} registration for configuring the component with a DesignSystem. +* Implements {@link @microsoft/fast-foundation#listboxOptionTemplate} +* +* +* @public +* @remarks +* Generates HTML Element: `` +* +*/ export const jpOption = ListboxOption.compose({ baseName: 'option', template, - styles + styles, }); /** - * Base class for Option - * @public - */ -export { ListboxOption as Option }; +* Base class for Option +* @public +*/ +export { ListboxOption as Option}; export { styles as optionStyles }; diff --git a/packages/components/src/option/option.styles.ts b/packages/components/src/option/option.styles.ts index f4cfc249..4322edfe 100644 --- a/packages/components/src/option/option.styles.ts +++ b/packages/components/src/option/option.styles.ts @@ -1,154 +1,174 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - +import { css } from "@microsoft/fast-element"; +import type { ElementStyles } from "@microsoft/fast-element"; import { - accentFillActive, - accentFillFocus, - accentFillHover, - accentFillRest, - bodyFont, - controlCornerRadius, - designUnit, - disabledOpacity, - focusStrokeWidth, - foregroundOnAccentActive, - foregroundOnAccentFocus, - foregroundOnAccentHover, - foregroundOnAccentRest, - neutralFillHover, - neutralForegroundRest, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '@microsoft/fast-components'; -import type { ElementStyles } from '@microsoft/fast-element'; -import { css } from '@microsoft/fast-element'; + disabledCursor, + display, + forcedColorsStylesheetBehavior, +} from "@microsoft/fast-foundation"; import type { - FoundationElementTemplate, - ListboxOptionOptions -} from '@microsoft/fast-foundation'; + FoundationElementTemplate, + ListboxOptionOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; -import { heightNumber } from '../styles'; + accentFillActive, + accentFillHover, + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + focusStrokeInner, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + neutralFillStealthActive, + neutralFillStealthHover, + neutralFillStealthRest, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/size.js"; /** - * Styles for Option + * Styles for the {@link @microsoft/fast-components#fastOption | Listbox Option} component. + * + * @param context - the element definition context + * @param definition - the foundation element definition + * @returns The element styles for the listbox option component + * * @public */ export const optionStyles: FoundationElementTemplate< - ElementStyles, - ListboxOptionOptions + ElementStyles, + ListboxOptionOptions > = (context, definition) => - css` - ${display('inline-flex')} :host { - align-items: center; - font-family: ${bodyFont}; - border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${focusStrokeWidth} * 1px) solid transparent; - box-sizing: border-box; - color: ${neutralForegroundRest}; - cursor: pointer; - flex: 0 0 auto; - fill: currentcolor; - font-size: ${typeRampBaseFontSize}; - height: calc(${heightNumber} * 1px); - line-height: ${typeRampBaseLineHeight}; - margin: 0 calc(${designUnit} * 1px); - outline: none; - overflow: hidden; - padding: 0 calc(${designUnit} * 2.25px); - user-select: none; - white-space: nowrap; - } - - /* TODO should we use outline instead of background for focus to support multi-selection */ - :host(:${focusVisible}) { - background: ${accentFillFocus}; - color: ${foregroundOnAccentFocus}; - } - - :host([aria-selected='true']) { - background: ${accentFillRest}; - color: ${foregroundOnAccentRest}; - } - - :host(:hover) { - background: ${accentFillHover}; - color: ${foregroundOnAccentHover}; - } - - :host(:active) { - background: ${accentFillActive}; - color: ${foregroundOnAccentActive}; - } - - :host(:not([aria-selected='true']):hover), - :host(:not([aria-selected='true']):active) { - background: ${neutralFillHover}; - color: ${neutralForegroundRest}; - } - - :host([disabled]) { - cursor: ${disabledCursor}; - opacity: ${disabledOpacity}; - } - - :host([disabled]:hover) { - background-color: inherit; - } - - .content { - grid-column-start: 2; - justify-self: start; - overflow: hidden; - text-overflow: ellipsis; - } - - .start, - .end, - ::slotted(svg) { - display: flex; - } - - ::slotted(svg) { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - height: calc(${designUnit} * 4px); - width: calc(${designUnit} * 4px); - } - - ::slotted([slot='end']) { - margin-inline-start: 1ch; - } - - ::slotted([slot='start']) { - margin-inline-end: 1ch; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host { - border-color: transparent; - forced-color-adjust: none; - color: ${SystemColors.ButtonText}; - fill: currentcolor; - } - - :host(:not([aria-selected='true']):hover), - :host([aria-selected='true']) { - background: ${SystemColors.Highlight}; - color: ${SystemColors.HighlightText}; - } - - :host([disabled]), - :host([disabled]:not([aria-selected='true']):hover) { - background: ${SystemColors.Canvas}; - color: ${SystemColors.GrayText}; - fill: currentcolor; - opacity: 1; - } - `) - ); + css` + ${display("inline-flex")} :host { + align-items: center; + font-family: ${bodyFont}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${focusStrokeWidth} * 1px) solid transparent; + box-sizing: border-box; + background: ${neutralFillStealthRest}; + color: ${neutralForegroundRest}; + cursor: pointer; + flex: 0 0 auto; + fill: currentcolor; + font-size: ${typeRampBaseFontSize}; + height: calc(${heightNumber} * 1px); + line-height: ${typeRampBaseLineHeight}; + margin: 0 calc((${designUnit} - ${focusStrokeWidth}) * 1px); + outline: none; + overflow: hidden; + padding: 0 1ch; + user-select: none; + white-space: nowrap; + } + + :host(:not([disabled]):not([aria-selected="true"]):hover) { + background: ${neutralFillStealthHover}; + } + + :host(:not([disabled]):not([aria-selected="true"]):active) { + background: ${neutralFillStealthActive}; + } + + :host([aria-selected="true"]) { + background: ${accentFillRest}; + color: ${foregroundOnAccentRest}; + } + + :host(:not([disabled])[aria-selected="true"]:hover) { + background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; + } + + :host(:not([disabled])[aria-selected="true"]:active) { + background: ${accentFillActive}; + color: ${foregroundOnAccentActive}; + } + + :host([disabled]) { + cursor: ${disabledCursor}; + opacity: ${disabledOpacity}; + } + + .content { + grid-column-start: 2; + justify-self: start; + overflow: hidden; + text-overflow: ellipsis; + } + + .start, + .end, + ::slotted(svg) { + display: flex; + } + + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + height: calc(${designUnit} * 4px); + width: calc(${designUnit} * 4px); + } + + ::slotted([slot="end"]) { + margin-inline-start: 1ch; + } + + ::slotted([slot="start"]) { + margin-inline-end: 1ch; + } + + :host([aria-checked="true"][aria-selected="false"]) { + border-color: ${focusStrokeOuter}; + } + + :host([aria-checked="true"][aria-selected="true"]) { + border-color: ${focusStrokeOuter}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 2 * 1px) inset + ${focusStrokeInner}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + border-color: transparent; + forced-color-adjust: none; + color: ${SystemColors.ButtonText}; + fill: currentcolor; + } + + :host(:not([aria-selected="true"]):hover), + :host([aria-selected="true"]) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host([disabled]), + :host([disabled][aria-selected="false"]:hover) { + background: ${SystemColors.Canvas}; + color: ${SystemColors.GrayText}; + fill: currentcolor; + opacity: 1; + } + + :host([aria-checked="true"][aria-selected="false"]) { + background: ${SystemColors.ButtonFace}; + color: ${SystemColors.ButtonText}; + border-color: ${SystemColors.ButtonText}; + } + + :host([aria-checked="true"][aria-selected="true"]), + :host([aria-checked="true"][aria-selected="true"]:hover) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + border-color: ${SystemColors.ButtonText}; + } + ` + ) + ); diff --git a/packages/components/src/picker/README.md b/packages/components/src/picker/README.md new file mode 100644 index 00000000..9e908d5f --- /dev/null +++ b/packages/components/src/picker/README.md @@ -0,0 +1 @@ +# fast-picker \ No newline at end of file diff --git a/packages/components/src/picker/fixtures/picker.html b/packages/components/src/picker/fixtures/picker.html new file mode 100644 index 00000000..f289faf4 --- /dev/null +++ b/packages/components/src/picker/fixtures/picker.html @@ -0,0 +1,162 @@ + + +

Picker

+ +

Default

+ + +

Filter query and filter selection off

+ + +

Preselection

+ + +

Custom menu

+ + + + pre-option + +
Group 1
+ + +
Group 2
+ + + + post-option + +
+
+ +

Custom menu no options

+ + + + pre-option + +
Group 1
+ + +
Group 2
+ + + + post-option + +
+
+ +

Single item

+ + +

Multiple items, limit to 3

+ + +

Custom content templates

+ + +

Menu above

+ + +

Menu above or below

+ + +

Menu above or below, not scaling

+ + +
diff --git a/packages/components/src/picker/index.ts b/packages/components/src/picker/index.ts new file mode 100644 index 00000000..34e1d33f --- /dev/null +++ b/packages/components/src/picker/index.ts @@ -0,0 +1,114 @@ +import { + FoundationElementDefinition, + PickerMenu as FoundationPickerMenu, + Picker, + PickerList, + PickerListItem, + pickerListItemTemplate, + pickerListTemplate, + PickerMenuOption, + pickerMenuOptionTemplate, + pickerMenuTemplate, + pickerTemplate, +} from "@microsoft/fast-foundation"; +import { fillColor, neutralLayerFloating } from "../design-tokens.js"; +import { pickerStyles } from "./picker.styles.js"; +import { pickerMenuStyles } from "./picker-menu.styles.js"; +import { pickerMenuOptionStyles } from "./picker-menu-option.styles.js"; +import { pickerListStyles } from "./picker-list.styles.js"; +import { pickerListItemStyles } from "./picker-list-item.styles.js"; + +/** + * The FAST Picker Custom Element. Implements {@link @microsoft/fast-foundation#Picker}, + * {@link @microsoft/fast-foundation#PickerTemplate} + * + * + * @alpha + * @remarks + * * Generates HTML Element: `` + */ +export const fastPicker = Picker.compose({ + baseName: "picker", + template: pickerTemplate, + styles: pickerStyles, + shadowOptions: {}, +}); + +/** + * Base class for Picker + * @alpha + */ +export { Picker }; + +/** + * @public + */ +export class PickerMenu extends FoundationPickerMenu { + /** + * @public + */ + public connectedCallback(): void { + fillColor.setValueFor(this, neutralLayerFloating); + + super.connectedCallback(); + } +} + +/** + * Component that displays the list of available picker options + * + * + * @alpha + * @remarks + * HTML Element: \ + */ +export const fastPickerMenu = PickerMenu.compose({ + baseName: "picker-menu", + baseClass: FoundationPickerMenu, + template: pickerMenuTemplate, + styles: pickerMenuStyles, +}); + +/** + * Component that displays available picker menu options + * + * + * @alpha + * @remarks + * HTML Element: \ + */ +export const fastPickerMenuOption = PickerMenuOption.compose({ + baseName: "picker-menu-option", + template: pickerMenuOptionTemplate, + styles: pickerMenuOptionStyles, +}); + +/** + * Component that displays the list of selected picker items along + * with the input combobox + * + * @alpha + * @remarks + * HTML Element: \ + * + */ +export const fastPickerList = PickerList.compose({ + baseName: "picker-list", + template: pickerListTemplate, + styles: pickerListStyles, +}); + +/** + * Component that displays selected items + * + * @alpha + * @remarks + * HTML Element: \ + */ +export const fastPickerListItem = PickerListItem.compose({ + baseName: "picker-list-item", + template: pickerListItemTemplate, + styles: pickerListItemStyles, +}); + +export { pickerStyles, pickerListItemStyles, pickerMenuOptionStyles, pickerMenuStyles }; diff --git a/packages/components/src/picker/picker-list-item.open-ui.definition.ts b/packages/components/src/picker/picker-list-item.open-ui.definition.ts new file mode 100644 index 00000000..be3bdfa1 --- /dev/null +++ b/packages/components/src/picker/picker-list-item.open-ui.definition.ts @@ -0,0 +1,4 @@ +export default { + name: "Picker list item", + url: "https://fast.design/docs/components/picker-list-item", +}; diff --git a/packages/components/src/picker/picker-list-item.styles.ts b/packages/components/src/picker/picker-list-item.styles.ts new file mode 100644 index 00000000..e2b82c37 --- /dev/null +++ b/packages/components/src/picker/picker-list-item.styles.ts @@ -0,0 +1,99 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentActive, + neutralFillStealthActive, + neutralFillStealthFocus, + neutralFillStealthHover, + neutralFillStealthRest, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; + +/** + * Styles for Picker list item + * @public + */ +export const pickerListItemStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host { + display: flex; + align-items: center; + justify-items: center; + font-family: ${bodyFont}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${focusStrokeWidth} * 1px) solid transparent; + box-sizing: border-box; + background: ${neutralFillStealthRest}; + color: ${neutralForegroundRest}; + cursor: pointer; + fill: currentcolor; + font-size: ${typeRampBaseFontSize}; + height: calc(${heightNumber} * 1px); + line-height: ${typeRampBaseLineHeight}; + outline: none; + overflow: hidden; + padding: 0 calc(${designUnit} * 2.25px); + user-select: none; + white-space: nowrap; + } + + :host(:hover) { + background: ${neutralFillStealthHover}; + } + + :host(:active) { + background: ${neutralFillStealthActive}; + } + + :host(:${focusVisible}) { + background: ${neutralFillStealthFocus}; + border-color: ${focusStrokeOuter}; + } + + :host([aria-selected="true"]) { + background: ${accentFillRest}; + color: ${foregroundOnAccentActive}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + border-color: transparent; + forced-color-adjust: none; + color: ${SystemColors.ButtonText}; + fill: currentcolor; + } + + :host(:not([aria-selected="true"]):hover), + :host([aria-selected="true"]) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host([disabled]), + :host([disabled]:not([aria-selected="true"]):hover) { + background: ${SystemColors.Canvas}; + color: ${SystemColors.GrayText}; + fill: currentcolor; + opacity: 1; + } + ` + ) + ); diff --git a/packages/components/src/picker/picker-list-item.vscode.definition.ts b/packages/components/src/picker/picker-list-item.vscode.definition.ts new file mode 100644 index 00000000..a978e14e --- /dev/null +++ b/packages/components/src/picker/picker-list-item.vscode.definition.ts @@ -0,0 +1,25 @@ +export default { + version: 1.1, + tags: [ + { + name: "fast-picker-list-item", + title: "Picker list item", + description: "The FAST picker-list-item element", + attributes: [ + { + name: "value", + description: "The value attribute", + default: "", + required: true, + type: "string", + }, + ], + slots: [ + { + name: "", + description: "The default slot", + }, + ], + }, + ], +}; diff --git a/packages/components/src/picker/picker-list.open-ui.definition.ts b/packages/components/src/picker/picker-list.open-ui.definition.ts new file mode 100644 index 00000000..a77b76a9 --- /dev/null +++ b/packages/components/src/picker/picker-list.open-ui.definition.ts @@ -0,0 +1,4 @@ +export default { + name: "Picker list", + url: "https://fast.design/docs/components/picker-list", +}; diff --git a/packages/components/src/picker/picker-list.styles.ts b/packages/components/src/picker/picker-list.styles.ts new file mode 100644 index 00000000..d4854abb --- /dev/null +++ b/packages/components/src/picker/picker-list.styles.ts @@ -0,0 +1,82 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentFillActive, + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + focusStrokeOuter, + neutralFillInputHover, + neutralFillInputRest, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; + +/** + * Styles for Picker list + * @public + */ +export const pickerListStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host { + display: flex; + flex-direction: row; + column-gap: calc(${designUnit} * 1px); + row-gap: calc(${designUnit} * 1px); + flex-wrap: wrap; + } + + ::slotted([role="combobox"]) { + min-width: 260px; + width: auto; + box-sizing: border-box; + color: ${neutralForegroundRest}; + background: ${neutralFillInputRest}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${accentFillRest}; + height: calc(${heightNumber} * 1px); + font-family: ${bodyFont}; + outline: none; + user-select: none; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + padding: 0 calc(${designUnit} * 2px + 1px); + } + + ::slotted([role="combobox"]:active) { { + background: ${neutralFillInputHover}; + border-color: ${accentFillActive}; + } + + ::slotted([role="combobox"]:focus-within) { + border-color: ${focusStrokeOuter}; + box-shadow: 0 0 0 1px ${focusStrokeOuter} inset; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + ::slotted([role="combobox"]:active) { + background: ${SystemColors.Field}; + border-color: ${SystemColors.Highlight}; + } + ::slotted([role="combobox"]:focus-within) { + border-color: ${SystemColors.Highlight}; + box-shadow: 0 0 0 1px ${SystemColors.Highlight} inset; + } + ::slotted(input:placeholder) { + color: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/picker/picker-list.vscode.definition.ts b/packages/components/src/picker/picker-list.vscode.definition.ts new file mode 100644 index 00000000..26bca121 --- /dev/null +++ b/packages/components/src/picker/picker-list.vscode.definition.ts @@ -0,0 +1,32 @@ +export default { + version: 1.1, + tags: [ + { + name: "fast-picker-list", + title: "Picker list", + description: "The FAST picker-list element", + attributes: [ + { + name: "label", + description: "The label attribute", + type: "string", + default: "", + required: false, + }, + { + name: "labelledby", + description: "The labelledby attribute", + type: "string", + default: "", + required: false, + }, + ], + slots: [ + { + name: "", + description: "The default slot", + }, + ], + }, + ], +}; diff --git a/packages/components/src/picker/picker-menu-option.open-ui.definition.ts b/packages/components/src/picker/picker-menu-option.open-ui.definition.ts new file mode 100644 index 00000000..6d754586 --- /dev/null +++ b/packages/components/src/picker/picker-menu-option.open-ui.definition.ts @@ -0,0 +1,4 @@ +export default { + name: "Picker menu item", + url: "https://fast.design/docs/components/picker-menu-option", +}; diff --git a/packages/components/src/picker/picker-menu-option.styles.ts b/packages/components/src/picker/picker-menu-option.styles.ts new file mode 100644 index 00000000..6916cc8c --- /dev/null +++ b/packages/components/src/picker/picker-menu-option.styles.ts @@ -0,0 +1,114 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentFillActive, + accentFillHover, + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + neutralFillStealthActive, + neutralFillStealthFocus, + neutralFillStealthHover, + neutralFillStealthRest, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; + +/** + * Styles for Picker menu option + * @public + */ +export const pickerMenuOptionStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host { + display: flex; + align-items: center; + justify-items: center; + font-family: ${bodyFont}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${focusStrokeWidth} * 1px) solid transparent; + box-sizing: border-box; + background: ${neutralFillStealthRest}; + color: ${neutralForegroundRest}; + cursor: pointer; + fill: currentcolor; + font-size: ${typeRampBaseFontSize}; + min-height: calc(${heightNumber} * 1px); + line-height: ${typeRampBaseLineHeight}; + margin: 0 calc(${designUnit} * 1px); + outline: none; + overflow: hidden; + padding: 0 calc(${designUnit} * 2.25px); + user-select: none; + white-space: nowrap; + } + + :host(:${focusVisible}[role="listitem"]) { + border-color: ${focusStrokeOuter}; + background: ${neutralFillStealthFocus}; + } + + :host(:hover) { + background: ${neutralFillStealthHover}; + } + + :host(:active) { + background: ${neutralFillStealthActive}; + } + + :host([aria-selected="true"]) { + background: ${accentFillRest}; + color: ${foregroundOnAccentRest}; + } + + :host([aria-selected="true"]:hover) { + background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; + } + + :host([aria-selected="true"]:active) { + background: ${accentFillActive}; + color: ${foregroundOnAccentActive}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + border-color: transparent; + forced-color-adjust: none; + color: ${SystemColors.ButtonText}; + fill: currentcolor; + } + + :host(:not([aria-selected="true"]):hover), + :host([aria-selected="true"]) { + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host([disabled]), + :host([disabled]:not([aria-selected="true"]):hover) { + background: ${SystemColors.Canvas}; + color: ${SystemColors.GrayText}; + fill: currentcolor; + opacity: 1; + } + ` + ) + ); diff --git a/packages/components/src/picker/picker-menu-option.vscode.definition.ts b/packages/components/src/picker/picker-menu-option.vscode.definition.ts new file mode 100644 index 00000000..b42beefd --- /dev/null +++ b/packages/components/src/picker/picker-menu-option.vscode.definition.ts @@ -0,0 +1,25 @@ +export default { + version: 1.1, + tags: [ + { + name: "fast-picker-menu-option", + title: "Picker menu item", + description: "The FAST picker-menu-option element", + attributes: [ + { + name: "value", + description: "The value attribute", + default: "", + required: true, + type: "string", + }, + ], + slots: [ + { + name: "", + description: "The default slot", + }, + ], + }, + ], +}; diff --git a/packages/components/src/picker/picker-menu.open-ui.definition.ts b/packages/components/src/picker/picker-menu.open-ui.definition.ts new file mode 100644 index 00000000..e8fc5d08 --- /dev/null +++ b/packages/components/src/picker/picker-menu.open-ui.definition.ts @@ -0,0 +1,4 @@ +export default { + name: "Picker menu", + url: "https://fast.design/docs/components/picker-menu", +}; diff --git a/packages/components/src/picker/picker-menu.styles.ts b/packages/components/src/picker/picker-menu.styles.ts new file mode 100644 index 00000000..f0a3bb27 --- /dev/null +++ b/packages/components/src/picker/picker-menu.styles.ts @@ -0,0 +1,59 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + controlCornerRadius, + designUnit, + fillColor, + strokeWidth, +} from "../design-tokens.js"; +import { elevation } from "../styles/index.js"; + +/** + * Styles for Picker menu + * @public + */ +export const pickerMenuStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host { + background: ${fillColor}; + --elevation: 11; + /* TODO: a mechanism to manage z-index across components + https://github.com/microsoft/fast/issues/3813 */ + z-index: 1000; + display: flex; + width: 100%; + max-height: 100%; + min-height: 58px; + box-sizing: border-box; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; + pointer-events: auto; + border-radius: calc(${controlCornerRadius} * 1px); + padding: calc(${designUnit} * 1px) 0; + border: calc(${strokeWidth} * 1px) solid transparent; + ${elevation} + } + + .suggestions-available-alert { + height: 0; + opacity: 0; + overflow: hidden; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + background: ${SystemColors.Canvas}; + border-color: ${SystemColors.CanvasText}; + } + ` + ) + ); diff --git a/packages/components/src/picker/picker-menu.vscode.definition.ts b/packages/components/src/picker/picker-menu.vscode.definition.ts new file mode 100644 index 00000000..2345fa21 --- /dev/null +++ b/packages/components/src/picker/picker-menu.vscode.definition.ts @@ -0,0 +1,25 @@ +export default { + version: 1.1, + tags: [ + { + name: "fast-picker-menu", + title: "Picker menu", + description: "The FAST picker-menu element", + attributes: [], + slots: [ + { + name: "", + description: "The default slot", + }, + { + name: "header-region", + description: "The header-region slot", + }, + { + name: "footer-region", + description: "The footer-region slot", + }, + ], + }, + ], +}; diff --git a/packages/components/src/picker/picker.open-ui.definition.ts b/packages/components/src/picker/picker.open-ui.definition.ts new file mode 100644 index 00000000..1fef29ce --- /dev/null +++ b/packages/components/src/picker/picker.open-ui.definition.ts @@ -0,0 +1,4 @@ +export default { + name: "Picker", + url: "https://fast.design/docs/components/picker", +}; diff --git a/packages/components/src/picker/picker.stories.ts b/packages/components/src/picker/picker.stories.ts new file mode 100644 index 00000000..bef91a15 --- /dev/null +++ b/packages/components/src/picker/picker.stories.ts @@ -0,0 +1,33 @@ +import { html, ViewTemplate } from "@microsoft/fast-element"; +import addons from "@storybook/addons"; +import { STORY_RENDERED } from "@storybook/core-events"; +import { Picker } from "@microsoft/fast-foundation"; +import PickerTemplate from "./fixtures/picker.html"; + +const optionContentsTemplate: ViewTemplate = html` +
+ ${x => x.value} +
+`; + +const itemContentsTemplate: ViewTemplate = html` +
+ ${x => x.value} +
+`; + +addons.getChannel().addListener(STORY_RENDERED, (name: string) => { + if (name.toLowerCase().startsWith("picker")) { + const customTemplatePicker = document.getElementById( + "customtemplatepicker" + ) as Picker; + customTemplatePicker.menuOptionContentsTemplate = optionContentsTemplate; + customTemplatePicker.listItemContentsTemplate = itemContentsTemplate; + } +}); + +export default { + title: "Picker", +}; + +export const picker = () => PickerTemplate; diff --git a/packages/components/src/picker/picker.styles.ts b/packages/components/src/picker/picker.styles.ts new file mode 100644 index 00000000..438eed23 --- /dev/null +++ b/packages/components/src/picker/picker.styles.ts @@ -0,0 +1,57 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + bodyFont, + designUnit, + fillColor, + typeRampBaseFontSize, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; + +/** + * Styles for Picker + * @public + */ +export const pickerStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + .region { + z-index: 1000; + overflow: hidden; + display: flex; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + } + + .loaded { + opacity: 1; + pointer-events: none; + } + + .loading-display, + .no-options-display { + background: ${fillColor}; + width: 100%; + min-height: calc(${heightNumber} * 1px); + display: flex; + flex-direction: column; + align-items: center; + justify-items: center; + padding: calc(${designUnit} * 1px); + } + + .loading-progress { + width: 42px; + height: 42px; + } + + .bottom { + flex-direction: column; + } + + .top { + flex-direction: column-reverse; + } + `; diff --git a/packages/components/src/picker/picker.vscode.definition.ts b/packages/components/src/picker/picker.vscode.definition.ts new file mode 100644 index 00000000..6ab60359 --- /dev/null +++ b/packages/components/src/picker/picker.vscode.definition.ts @@ -0,0 +1,114 @@ +export default { + version: 1.1, + tags: [ + { + name: "fast-picker", + title: "Picker", + description: "The FAST picker element", + attributes: [ + { + name: "selection", + description: "The selection attribute", + default: "", + required: false, + type: "string", + }, + { + name: "options", + description: "The options attribute", + required: false, + type: "string", + default: "", + }, + { + name: "filter-selected", + description: "The filter-selected attribute", + type: "boolean", + default: true, + required: false, + }, + { + name: "filter-query", + description: "The filter-query attribute", + type: "boolean", + default: true, + required: false, + }, + { + name: "max-selected", + description: "The max-selected attribute", + type: "number", + default: "", + required: false, + }, + { + name: "no-suggestions-text", + description: "The no-suggestions-text attribute", + type: "string", + default: "", + required: false, + }, + { + name: "suggestions-available-text", + description: "The suggestions-available-text attribute", + type: "string", + default: "", + required: false, + }, + { + name: "loading-text", + description: "The loading-text attribute", + type: "string", + default: "", + required: false, + }, + { + name: "label", + description: "The label attribute", + type: "string", + default: "", + required: false, + }, + { + name: "labelledby", + description: "The labelledby attribute", + type: "string", + default: "", + required: false, + }, + { + name: "menu-placement", + description: "The menu-placement attribute", + type: "string", + default: "", + required: false, + }, + { + name: "placeholder", + description: "The placeholder attribute", + type: "string", + default: "", + required: false, + }, + ], + slots: [ + { + name: "list-region", + description: "The list-region slot", + }, + { + name: "menu-region", + description: "The menu-region slot", + }, + { + name: "no-options-region", + description: "The no-options-region slot", + }, + { + name: "loading-region", + description: "The loading-region slot", + }, + ], + }, + ], +}; diff --git a/packages/components/src/picker/scenarios/index.html b/packages/components/src/picker/scenarios/index.html new file mode 100644 index 00000000..f39050b9 --- /dev/null +++ b/packages/components/src/picker/scenarios/index.html @@ -0,0 +1,11 @@ + diff --git a/packages/components/src/progress-ring/index.ts b/packages/components/src/progress-ring/index.ts index a5df470d..6e21c9a4 100644 --- a/packages/components/src/progress-ring/index.ts +++ b/packages/components/src/progress-ring/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - BaseProgress as ProgressRing, - ProgressRingOptions, - progressRingTemplate as template -} from '@microsoft/fast-foundation'; -import { progressRingStyles as styles } from '@microsoft/fast-components'; + BaseProgress as ProgressRing, + ProgressRingOptions, + progressRingTemplate as template, +} from "@microsoft/fast-foundation"; +import { progressRingStyles as styles } from "./progress-ring.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#BaseProgress} registration for configuring the component with a DesignSystem. @@ -18,10 +15,10 @@ import { progressRingStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpProgressRing = ProgressRing.compose({ - baseName: 'progress-ring', - template, - styles, - indeterminateIndicator: /* html */ ` + baseName: 'progress-ring', + template, + styles, + indeterminateIndicator: /* html */ ` ({ r="7px" > - ` + `, }); /** diff --git a/packages/components/src/progress-ring/progress-ring.styles.ts b/packages/components/src/progress-ring/progress-ring.styles.ts new file mode 100644 index 00000000..bf9cae30 --- /dev/null +++ b/packages/components/src/progress-ring/progress-ring.styles.ts @@ -0,0 +1,106 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + ProgressRingOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentForegroundRest, + neutralFillRest, + neutralForegroundHint, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/size.js"; + +/** + * Styles for Progress Ring + * @public + */ +export const progressRingStyles: FoundationElementTemplate< + ElementStyles, + ProgressRingOptions +> = (context, definition) => + css` + ${display("flex")} :host { + align-items: center; + outline: none; + height: calc(${heightNumber} * 1px); + width: calc(${heightNumber} * 1px); + margin: calc(${heightNumber} * 1px) 0; + } + + .progress { + height: 100%; + width: 100%; + } + + .background { + stroke: ${neutralFillRest}; + fill: none; + stroke-width: 2px; + } + + .determinate { + stroke: ${accentForegroundRest}; + fill: none; + stroke-width: 2px; + stroke-linecap: round; + transform-origin: 50% 50%; + transform: rotate(-90deg); + transition: all 0.2s ease-in-out; + } + + .indeterminate-indicator-1 { + stroke: ${accentForegroundRest}; + fill: none; + stroke-width: 2px; + stroke-linecap: round; + transform-origin: 50% 50%; + transform: rotate(-90deg); + transition: all 0.2s ease-in-out; + animation: spin-infinite 2s linear infinite; + } + + :host([paused]) .indeterminate-indicator-1 { + animation-play-state: paused; + stroke: ${neutralFillRest}; + } + + :host([paused]) .determinate { + stroke: ${neutralForegroundHint}; + } + + @keyframes spin-infinite { + 0% { + stroke-dasharray: 0.01px 43.97px; + transform: rotate(0deg); + } + 50% { + stroke-dasharray: 21.99px 21.99px; + transform: rotate(450deg); + } + 100% { + stroke-dasharray: 0.01px 43.97px; + transform: rotate(1080deg); + } + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .indeterminate-indicator-1, + .determinate { + stroke: ${SystemColors.FieldText}; + } + .background { + stroke: ${SystemColors.Field}; + } + :host([paused]) .indeterminate-indicator-1 { + stroke: ${SystemColors.Field}; + } + :host([paused]) .determinate { + stroke: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/progress/index.ts b/packages/components/src/progress/index.ts index e3bbfb98..31c14d52 100644 --- a/packages/components/src/progress/index.ts +++ b/packages/components/src/progress/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - BaseProgress as Progress, - ProgressOptions, - progressTemplate as template -} from '@microsoft/fast-foundation'; -import { progressStyles as styles } from '@microsoft/fast-components'; + BaseProgress as Progress, + ProgressOptions, + progressTemplate as template, +} from "@microsoft/fast-foundation"; +import { progressStyles as styles } from "./progress.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#BaseProgress} registration for configuring the component with a DesignSystem. @@ -18,13 +15,13 @@ import { progressStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpProgress = Progress.compose({ - baseName: 'progress', - template, - styles, - indeterminateIndicator1: /* html */ ` + baseName: 'progress', + template, + styles, + indeterminateIndicator1: /* html */ ` `, - indeterminateIndicator2: /* html */ ` + indeterminateIndicator2: /* html */ ` ` }); diff --git a/packages/components/src/progress/progress.styles.ts b/packages/components/src/progress/progress.styles.ts new file mode 100644 index 00000000..191f65ce --- /dev/null +++ b/packages/components/src/progress/progress.styles.ts @@ -0,0 +1,147 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + ProgressOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentForegroundRest, + designUnit, + neutralFillRest, + neutralForegroundHint, +} from "../design-tokens.js"; + +/** + * Styles for Progress + * @public + */ +export const progressStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("flex")} :host { + align-items: center; + outline: none; + height: calc(${designUnit} * 1px); + margin: calc(${designUnit} * 1px) 0; + } + + .progress { + background-color: ${neutralFillRest}; + border-radius: calc(${designUnit} * 1px); + width: 100%; + height: 100%; + display: flex; + align-items: center; + position: relative; + } + + .determinate { + background-color: ${accentForegroundRest}; + border-radius: calc(${designUnit} * 1px); + height: 100%; + transition: all 0.2s ease-in-out; + display: flex; + } + + .indeterminate { + height: 100%; + border-radius: calc(${designUnit} * 1px); + display: flex; + width: 100%; + position: relative; + overflow: hidden; + } + + .indeterminate-indicator-1 { + position: absolute; + opacity: 0; + height: 100%; + background-color: ${accentForegroundRest}; + border-radius: calc(${designUnit} * 1px); + animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); + width: 40%; + animation: indeterminate-1 2s infinite; + } + + .indeterminate-indicator-2 { + position: absolute; + opacity: 0; + height: 100%; + background-color: ${accentForegroundRest}; + border-radius: calc(${designUnit} * 1px); + animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); + width: 60%; + animation: indeterminate-2 2s infinite; + } + + :host([paused]) .indeterminate-indicator-1, + :host([paused]) .indeterminate-indicator-2 { + animation-play-state: paused; + background-color: ${neutralFillRest}; + } + + :host([paused]) .determinate { + background-color: ${neutralForegroundHint}; + } + + @keyframes indeterminate-1 { + 0% { + opacity: 1; + transform: translateX(-100%); + } + 70% { + opacity: 1; + transform: translateX(300%); + } + 70.01% { + opacity: 0; + } + 100% { + opacity: 0; + transform: translateX(300%); + } + } + + @keyframes indeterminate-2 { + 0% { + opacity: 0; + transform: translateX(-150%); + } + 29.99% { + opacity: 0; + } + 30% { + opacity: 1; + transform: translateX(-150%); + } + 100% { + transform: translateX(166.66%); + opacity: 1; + } + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .progress { + forced-color-adjust: none; + background-color: ${SystemColors.Field}; + box-shadow: 0 0 0 1px inset ${SystemColors.FieldText}; + } + .determinate, + .indeterminate-indicator-1, + .indeterminate-indicator-2 { + forced-color-adjust: none; + background-color: ${SystemColors.FieldText}; + } + :host([paused]) .determinate, + :host([paused]) .indeterminate-indicator-1, + :host([paused]) .indeterminate-indicator-2 { + background-color: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/radio-group/index.ts b/packages/components/src/radio-group/index.ts index 63dd8360..fa261552 100644 --- a/packages/components/src/radio-group/index.ts +++ b/packages/components/src/radio-group/index.ts @@ -1,11 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { - RadioGroup, - radioGroupTemplate as template -} from '@microsoft/fast-foundation'; -import { radioGroupStyles as styles } from '@microsoft/fast-components'; +import { RadioGroup, radioGroupTemplate as template } from "@microsoft/fast-foundation"; +import { radioGroupStyles as styles } from "./radio-group.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#RadioGroup} registration for configuring the component with a DesignSystem. @@ -17,9 +11,9 @@ import { radioGroupStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpRadioGroup = RadioGroup.compose({ - baseName: 'radio-group', - template, - styles + baseName: 'radio-group', + template, + styles, }); /** diff --git a/packages/components/src/radio-group/radio-group.styles.ts b/packages/components/src/radio-group/radio-group.styles.ts new file mode 100644 index 00000000..d0f72a49 --- /dev/null +++ b/packages/components/src/radio-group/radio-group.styles.ts @@ -0,0 +1,28 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { designUnit } from "../design-tokens.js"; + +/** + * Styles for Radio Group + * @public + */ +export const radioGroupStyles: FoundationElementTemplate = ( + context, + definition +) => css` + ${display("flex")} :host { + align-items: flex-start; + margin: calc(${designUnit} * 1px) 0; + flex-direction: column; + } + .positioning-region { + display: flex; + flex-wrap: wrap; + } + :host([orientation="vertical"]) .positioning-region { + flex-direction: column; + } + :host([orientation="horizontal"]) .positioning-region { + flex-direction: row; + } +`; diff --git a/packages/components/src/radio/index.ts b/packages/components/src/radio/index.ts index 09885a16..c26b1396 100644 --- a/packages/components/src/radio/index.ts +++ b/packages/components/src/radio/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - Radio, - RadioOptions, - radioTemplate as template -} from '@microsoft/fast-foundation'; -import { radioStyles as styles } from './radio.styles'; + Radio, + RadioOptions, + radioTemplate as template, +} from "@microsoft/fast-foundation"; +import { radioStyles as styles } from "./radio.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Radio} registration for configuring the component with a DesignSystem. @@ -18,12 +15,12 @@ import { radioStyles as styles } from './radio.styles'; * Generates HTML Element: `` */ export const jpRadio = Radio.compose({ - baseName: 'radio', - template, - styles, - checkedIndicator: /* html */ ` -
- ` + baseName: 'radio', + template, + styles, + checkedIndicator: /* html */ ` +
+ `, }); /** diff --git a/packages/components/src/radio/radio.styles.ts b/packages/components/src/radio/radio.styles.ts index 2a153cb1..3ea4e530 100644 --- a/packages/components/src/radio/radio.styles.ts +++ b/packages/components/src/radio/radio.styles.ts @@ -1,230 +1,216 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - RadioOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + RadioOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - accentFillActive, - accentFillFocus, - accentFillHover, - accentFillRest, - bodyFont, - designUnit, - disabledOpacity, - focusStrokeWidth, - foregroundOnAccentActive, - foregroundOnAccentHover, - foregroundOnAccentRest, - neutralFillInputActive, - neutralFillInputHover, - neutralFillInputRest, - neutralForegroundRest, - neutralStrokeActive, - neutralStrokeHover, - neutralStrokeRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; - + accentFillActive, + accentFillFocus, + accentFillHover, + accentFillRest, + bodyFont, + designUnit, + disabledOpacity, + fillColor, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + neutralFillInputActive, + neutralFillInputHover, + neutralFillInputRest, + neutralForegroundRest, + neutralStrokeActive, + neutralStrokeHover, + neutralStrokeRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; /** * Styles for Radio * @public */ -export const radioStyles: FoundationElementTemplate< - ElementStyles, - RadioOptions -> = (context, definition) => - css` - ${display('inline-flex')} :host { - --input-size: calc((${heightNumber} / 2) + ${designUnit}); - align-items: center; - outline: none; - margin: calc(${designUnit} * 1px) 0; - /* Chromium likes to select label text or the default slot when - the radio is clicked. Maybe there is a better solution here? */ - user-select: none; - position: relative; - flex-direction: row; - transition: all 0.2s ease-in-out; - } - - .control { - position: relative; - width: calc((${heightNumber} / 2 + ${designUnit}) * 1px); - height: calc((${heightNumber} / 2 + ${designUnit}) * 1px); - box-sizing: border-box; - border-radius: 999px; - border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; - background: ${neutralFillInputRest}; - outline: none; - cursor: pointer; - } - - .label { - font-family: ${bodyFont}; - color: ${neutralForegroundRest}; - /* Need to discuss with Brian how HorizontalSpacingNumber can work. - https://github.com/microsoft/fast/issues/2766 */ - padding-inline-start: calc(${designUnit} * 2px + 2px); - margin-inline-end: calc(${designUnit} * 2px + 2px); - cursor: pointer; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - } - - .label__hidden { - display: none; - visibility: hidden; - } - - .control, - .checked-indicator { - flex-shrink: 0; - } - - .checked-indicator { - position: absolute; - top: 5px; - left: 5px; - right: 5px; - bottom: 5px; - border-radius: 999px; - display: inline-block; - background: ${foregroundOnAccentRest}; - fill: ${foregroundOnAccentRest}; - opacity: 0; - pointer-events: none; - } - - :host(:not([disabled])) .control:hover { - background: ${neutralFillInputHover}; - border-color: ${neutralStrokeHover}; - } - - :host(:not([disabled])) .control:active { - background: ${neutralFillInputActive}; - border-color: ${neutralStrokeActive}; - } - - :host(:${focusVisible}) .control { - outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; - } - - :host([aria-checked='true']) .control { - background: ${accentFillRest}; - border: calc(${strokeWidth} * 1px) solid ${accentFillRest}; - } - - :host([aria-checked='true']:not([disabled])) .control:hover { - background: ${accentFillHover}; - border: calc(${strokeWidth} * 1px) solid ${accentFillHover}; - } - - :host([aria-checked='true']:not([disabled])) - .control:hover - .checked-indicator { - background: ${foregroundOnAccentHover}; - fill: ${foregroundOnAccentHover}; - } - - :host([aria-checked='true']:not([disabled])) .control:active { - background: ${accentFillActive}; - border: calc(${strokeWidth} * 1px) solid ${accentFillActive}; - } - - :host([aria-checked='true']:not([disabled])) - .control:active - .checked-indicator { - background: ${foregroundOnAccentActive}; - fill: ${foregroundOnAccentActive}; - } - - :host([aria-checked="true"]:${focusVisible}:not([disabled])) .control { - outline-offset: 2px; - outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; - } - - :host([disabled]) .label, - :host([readonly]) .label, - :host([readonly]) .control, - :host([disabled]) .control { - cursor: ${disabledCursor}; - } - - :host([aria-checked='true']) .checked-indicator { - opacity: 1; - } - - :host([disabled]) { - opacity: ${disabledOpacity}; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - .control, - :host([aria-checked='true']:not([disabled])) .control { - forced-color-adjust: none; - border-color: ${SystemColors.FieldText}; - background: ${SystemColors.Field}; - } - :host(:not([disabled])) .control:hover { - border-color: ${SystemColors.Highlight}; - background: ${SystemColors.Field}; - } - :host([aria-checked='true']:not([disabled])) .control:hover, - :host([aria-checked='true']:not([disabled])) .control:active { - border-color: ${SystemColors.Highlight}; - background: ${SystemColors.Highlight}; - } - :host([aria-checked='true']) .checked-indicator { - background: ${SystemColors.Highlight}; - fill: ${SystemColors.Highlight}; - } - :host([aria-checked='true']:not([disabled])) - .control:hover - .checked-indicator, - :host([aria-checked='true']:not([disabled])) - .control:active +export const radioStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("inline-flex")} :host { + --input-size: calc((${heightNumber} / 2) + ${designUnit}); + align-items: center; + outline: none; + margin: calc(${designUnit} * 1px) 0; + /* Chromium likes to select label text or the default slot when + the radio is clicked. Maybe there is a better solution here? */ + user-select: none; + position: relative; + flex-direction: row; + transition: all 0.2s ease-in-out; + } + + .control { + position: relative; + width: calc((${heightNumber} / 2 + ${designUnit}) * 1px); + height: calc((${heightNumber} / 2 + ${designUnit}) * 1px); + box-sizing: border-box; + border-radius: 999px; + border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; + background: ${neutralFillInputRest}; + outline: none; + cursor: pointer; + } + + .label { + font-family: ${bodyFont}; + color: ${neutralForegroundRest}; + padding-inline-start: calc(${designUnit} * 2px + 2px); + margin-inline-end: calc(${designUnit} * 2px + 2px); + cursor: pointer; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + } + + .label__hidden { + display: none; + visibility: hidden; + } + + .control, .checked-indicator { + flex-shrink: 0; + } + .checked-indicator { - background: ${SystemColors.HighlightText}; - fill: ${SystemColors.HighlightText}; - } - :host(:${focusVisible}) .control { - border-color: ${SystemColors.Highlight}; - outline-offset: 2px; - outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; - } - :host([aria-checked="true"]:${focusVisible}:not([disabled])) .control { - border-color: ${SystemColors.Highlight}; + position: absolute; + top: 5px; + left: 5px; + right: 5px; + bottom: 5px; + border-radius: 999px; + display: inline-block; + background: ${foregroundOnAccentRest}; + fill: ${foregroundOnAccentRest}; + opacity: 0; + pointer-events: none; + } + + :host(:not([disabled])) .control:hover{ + background: ${neutralFillInputHover}; + border-color: ${neutralStrokeHover}; + } + + :host(:not([disabled])) .control:active { + background: ${neutralFillInputActive}; + border-color: ${neutralStrokeActive}; + } + + :host(:${focusVisible}) .control { + outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; + } + + :host([aria-checked="true"]) .control { + background: ${accentFillRest}; + border: calc(${strokeWidth} * 1px) solid ${accentFillRest}; + } + + :host([aria-checked="true"]:not([disabled])) .control:hover { + background: ${accentFillHover}; + border: calc(${strokeWidth} * 1px) solid ${accentFillHover}; + } + + :host([aria-checked="true"]:not([disabled])) .control:hover .checked-indicator { + background: ${foregroundOnAccentHover}; + fill: ${foregroundOnAccentHover}; + } + + :host([aria-checked="true"]:not([disabled])) .control:active { + background: ${accentFillActive}; + border: calc(${strokeWidth} * 1px) solid ${accentFillActive}; + } + + :host([aria-checked="true"]:not([disabled])) .control:active .checked-indicator { + background: ${foregroundOnAccentActive}; + fill: ${foregroundOnAccentActive}; + } + + :host([aria-checked="true"]:${focusVisible}:not([disabled])) .control { + box-shadow: 0 0 0 2px ${fillColor}, 0 0 0 4px ${focusStrokeOuter}; + } + + :host([disabled]) .label, + :host([readonly]) .label, + :host([readonly]) .control, + :host([disabled]) .control { + cursor: ${disabledCursor}; + } + + :host([aria-checked="true"]) .checked-indicator { + opacity: 1; + } + + :host([disabled]) { + opacity: ${disabledOpacity}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .control, + :host([aria-checked="true"]:not([disabled])) .control { + forced-color-adjust: none; + border-color: ${SystemColors.FieldText}; + background: ${SystemColors.Field}; + } + :host(:not([disabled])) .control:hover { + border-color: ${SystemColors.Highlight}; + background: ${SystemColors.Field}; + } + :host([aria-checked="true"]:not([disabled])) .control:hover, + :host([aria-checked="true"]:not([disabled])) .control:active { + border-color: ${SystemColors.Highlight}; + background: ${SystemColors.Highlight}; + } + :host([aria-checked="true"]) .checked-indicator { + background: ${SystemColors.Highlight}; + fill: ${SystemColors.Highlight}; + } + :host([aria-checked="true"]:not([disabled])) .control:hover .checked-indicator, + :host([aria-checked="true"]:not([disabled])) .control:active .checked-indicator { + background: ${SystemColors.HighlightText}; + fill: ${SystemColors.HighlightText}; + } + :host(:${focusVisible}) .control { + border-color: ${SystemColors.Highlight}; + outline-offset: 2px; outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; - } - :host([disabled]) { - forced-color-adjust: none; - opacity: 1; - } - :host([disabled]) .label { - color: ${SystemColors.GrayText}; - } - :host([disabled]) .control, - :host([aria-checked='true'][disabled]) .control:hover, - .control:active { - background: ${SystemColors.Field}; - border-color: ${SystemColors.GrayText}; - } - :host([disabled]) .checked-indicator, - :host([aria-checked='true'][disabled]) .control:hover .checked-indicator { - fill: ${SystemColors.GrayText}; - background: ${SystemColors.GrayText}; - } - `) - ); + } + :host([aria-checked="true"]:${focusVisible}:not([disabled])) .control { + border-color: ${SystemColors.Highlight}; + outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; + } + :host([disabled]) { + forced-color-adjust: none; + opacity: 1; + } + :host([disabled]) .label { + color: ${SystemColors.GrayText}; + } + :host([disabled]) .control, + :host([aria-checked="true"][disabled]) .control:hover, .control:active { + background: ${SystemColors.Field}; + border-color: ${SystemColors.GrayText}; + } + :host([disabled]) .checked-indicator, + :host([aria-checked="true"][disabled]) .control:hover .checked-indicator { + fill: ${SystemColors.GrayText}; + background: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/search/index.ts b/packages/components/src/search/index.ts index 40b0f97e..77395800 100644 --- a/packages/components/src/search/index.ts +++ b/packages/components/src/search/index.ts @@ -1,15 +1,30 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { attr } from "@microsoft/fast-element"; import { - Search as FoundationSearch, - searchTemplate as template -} from '@microsoft/fast-foundation'; -import { Search } from '@microsoft/fast-components'; -import { searchStyles as styles } from './search.styles'; + Search as FoundationSearch, + searchTemplate as template, +} from "@microsoft/fast-foundation"; +import { searchStyles as styles } from "./search.styles.js"; + +/** + * Search appearances + * @public + */ +export type SearchAppearance = "filled" | "outline"; -// TODO -// we need to add error/invalid +/** + * @internal + */ +export class Search extends FoundationSearch { + /** + * The appearance of the element. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance: SearchAppearance = "outline"; +} /** * A function that returns a {@link @microsoft/fast-foundation#Search} registration for configuring the component with a DesignSystem. @@ -23,19 +38,17 @@ import { searchStyles as styles } from './search.styles'; * {@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 - } + baseName: 'search', + baseClass: FoundationSearch, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); -export { Search, SearchAppearance } from '@microsoft/fast-components'; - /** * Styles for Search * @public */ -export { styles as searchStyles }; +export const searchStyles = styles; diff --git a/packages/components/src/search/search.styles.ts b/packages/components/src/search/search.styles.ts index 4328188c..617888b9 100644 --- a/packages/components/src/search/search.styles.ts +++ b/packages/components/src/search/search.styles.ts @@ -1,147 +1,141 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - DesignToken, - focusVisible, - FoundationElementTemplate, - TextFieldOptions -} from '@microsoft/fast-foundation'; -import { Swatch } from '../color'; + DesignToken, focusVisible, FoundationElementTemplate, + TextFieldOptions +} from "@microsoft/fast-foundation"; +import { Swatch } from "../color/swatch.js"; import { - bodyFont, - controlCornerRadius, - density, - neutralFillRecipe, - neutralFillStealthActive, - neutralFillStealthHover, - neutralFillStealthRecipe, - neutralForegroundRest, - typeRampBaseFontSize, - typeRampBaseLineHeight, - designUnit -} from '../design-tokens'; -import { BaseFieldStyles, heightNumber } from '../styles/index'; - -const clearButtonHover = DesignToken.create( - 'clear-button-hover' -).withDefault((target: HTMLElement) => { - const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); - const inputRecipe = neutralFillRecipe.getValueFor(target); - return buttonRecipe.evaluate(target, inputRecipe.evaluate(target).hover) - .hover; -}); - -const clearButtonActive = DesignToken.create( - 'clear-button-active' -).withDefault((target: HTMLElement) => { - const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); - const inputRecipe = neutralFillRecipe.getValueFor(target); - return buttonRecipe.evaluate(target, inputRecipe.evaluate(target).hover) - .active; -}); + bodyFont, + controlCornerRadius, + density, + designUnit, neutralFillRecipe, + neutralFillStealthActive, + neutralFillStealthHover, + neutralFillStealthRecipe, + neutralForegroundRest, typeRampBaseFontSize, + typeRampBaseLineHeight +} from '../design-tokens.js'; +import { BaseFieldStyles, heightNumber } from '../styles/index.js'; + +const clearButtonHover = DesignToken.create("clear-button-hover").withDefault( + (target: HTMLElement) => { + const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); + const inputRecipe = neutralFillRecipe.getValueFor(target); + return buttonRecipe.evaluate(target, inputRecipe.evaluate(target).hover).hover; + } +); + +const clearButtonActive = DesignToken.create("clear-button-active").withDefault( + (target: HTMLElement) => { + const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); + const inputRecipe = neutralFillRecipe.getValueFor(target); + return buttonRecipe.evaluate(target, inputRecipe.evaluate(target).hover).active; + } +); export const searchStyles: FoundationElementTemplate< ElementStyles, TextFieldOptions > = (context, definition) => css` - ${BaseFieldStyles} + ${BaseFieldStyles} - .control { - padding: 0; + .control { + padding: 0; padding-inline-start: calc(${designUnit} * 2px + 1px); - padding-inline-end: calc( + padding-inline-end: calc( (${designUnit} * 2px) + (${heightNumber} * 1px) + 1px ); - } + } - .control::-webkit-search-cancel-button { - -webkit-appearance: none; - } + .control::-webkit-search-cancel-button { + -webkit-appearance: none; + } - .control:hover, + .control:hover, .control:${focusVisible}, .control:disabled, .control:active { - outline: none; - } - - .clear-button { - height: calc(100% - 2px); - opacity: 0; - margin: 1px; - background: transparent; - color: ${neutralForegroundRest}; - fill: currentcolor; - border: none; - border-radius: calc(${controlCornerRadius} * 1px); - min-width: calc(${heightNumber} * 1px); - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - outline: none; - font-family: ${bodyFont}; - padding: 0 calc((10 + (${designUnit} * 2 * ${density})) * 1px); - } - - .clear-button:hover { - background: ${neutralFillStealthHover}; - } - - .clear-button:active { - background: ${neutralFillStealthActive}; - } - - :host([appearance='filled']) .clear-button:hover { - background: ${clearButtonHover}; - } - - :host([appearance='filled']) .clear-button:active { - background: ${clearButtonActive}; - } - - .input-wrapper { - display: flex; - position: relative; - width: 100%; - } - - .start, - .end { - display: flex; - margin: 1px; - } - - ::slotted([slot='end']) { - height: 100%; - } - - .end { - margin-inline-end: 1px; - } - - ::slotted(svg) { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - margin-inline-end: 11px; - margin-inline-start: 11px; - margin-top: auto; - margin-bottom: auto; - } - - .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; - } + outline: none; + } + + .clear-button { + height: calc(100% - 2px); + opacity: 0; + margin: 1px; + background: transparent; + color: ${neutralForegroundRest}; + fill: currentcolor; + border: none; + border-radius: calc(${controlCornerRadius} * 1px); + min-width: calc(${heightNumber} * 1px); + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + outline: none; + font-family: ${bodyFont}; + padding: 0 calc((10 + (${designUnit} * 2 * ${density})) * 1px); + } + + .clear-button:hover { + background: ${neutralFillStealthHover}; + } + + .clear-button:active { + background: ${neutralFillStealthActive}; + } + + :host([appearance="filled"]) .clear-button:hover { + background: ${clearButtonHover}; + } + + :host([appearance="filled"]) .clear-button:active { + background: ${clearButtonActive}; + } + + .input-wrapper { + display: flex; + position: relative; + width: 100%; + } + + .start, + .end { + display: flex; + margin: 1px; + fill: currentcolor; + } + + ::slotted([slot="end"]) { + height: 100% + } + + .end { + margin-inline-end: 1px; + height: calc(100% - 2px); + } + + ::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; + } + + .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; + } `; diff --git a/packages/components/src/select/index.ts b/packages/components/src/select/index.ts index 5f90000c..ba81270a 100644 --- a/packages/components/src/select/index.ts +++ b/packages/components/src/select/index.ts @@ -1,104 +1,246 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { attr } from '@microsoft/fast-element'; +import { attr, css, ElementStyles, observable } from "@microsoft/fast-element"; +import { + Select as FoundationSelect, + SelectOptions, + selectTemplate as template, +} from "@microsoft/fast-foundation"; import { - Select as FoundationSelect, - SelectOptions, - selectTemplate as template -} from '@microsoft/fast-foundation'; -import { selectStyles as styles } from './select.styles'; + fillColor, + heightNumberAsToken, + neutralLayerFloating, +} from "../design-tokens.js"; +import { selectStyles as styles } from "./select.styles.js"; /** - * Base class for Select + * Base class for Select. * @public */ export class Select extends FoundationSelect { - /** - * Whether the select has a compact layout or not. - * - * @public - * @remarks - * HTML Attribute: minimal - */ - @attr({ attribute: 'autowidth', mode: 'boolean' }) - public autoWidth: boolean; - - /** - * Whether the select has a compact layout or not. - * - * @public - * @remarks - * HTML Attribute: minimal - */ - @attr({ attribute: 'minimal', mode: 'boolean' }) - public minimal: boolean; - - /** - * The connected callback for this FASTElement. - * - * @override - * - * @internal - */ - connectedCallback(): void { - super.connectedCallback(); - this.setAutoWidth(); - } - - /** - * Synchronize the form-associated proxy and updates the value property of the element. - * - * @param prev - the previous collection of slotted option elements - * @param next - the next collection of slotted option elements - * - * @internal - */ - slottedOptionsChanged(prev: Element[] | undefined, next: Element[]): void { - super.slottedOptionsChanged(prev, next); - this.setAutoWidth(); - } - - /** - * (Un-)set the width when the autoWidth property changes. - * - * @param prev - the previous autoWidth value - * @param next - the current autoWidth value - */ - protected autoWidthChanged(prev: boolean | undefined, next: boolean): void { - if (next) { + /** + * Whether the select has a compact layout or not. + * + * @public + * @remarks + * HTML Attribute: minimal + */ + @attr({ attribute: 'autowidth', mode: 'boolean' }) + public autoWidth: boolean; + + /** + * (Un-)set the width when the autoWidth property changes. + * + * @param prev - the previous autoWidth value + * @param next - the current autoWidth value + */ + protected autoWidthChanged(prev: boolean | undefined, next: boolean): void { + if (next) { + this.setAutoWidth(); + } else { + this.style.removeProperty('width'); + } + } + + /** + * Compute the listbox width to set the one of the input. + */ + protected setAutoWidth(): void { + if (!this.autoWidth || !this.isConnected) { + return; + } + + let listWidth = this.listbox.getBoundingClientRect().width; + // If the list has not been displayed yet trick to get its size + if (listWidth === 0 && this.listbox.hidden) { + Object.assign(this.listbox.style, { visibility: 'hidden' }); + this.listbox.removeAttribute('hidden'); + listWidth = this.listbox.getBoundingClientRect().width; + this.listbox.setAttribute('hidden', ''); + this.listbox.style.removeProperty('visibility'); + } + + if (listWidth > 0) { + Object.assign(this.style, { width: `${listWidth}px` }); + } + } + + /** + * Whether the select has a compact layout or not. + * + * @public + * @remarks + * HTML Attribute: minimal + */ + @attr({ attribute: 'minimal', mode: 'boolean' }) + public minimal: boolean; + + /** + * An internal stylesheet to hold calculated CSS custom properties. + * + * @internal + */ + private computedStylesheet?: ElementStyles; + + /** + * @internal + */ + public connectedCallback(): void { + super.connectedCallback(); + this.setAutoWidth(); + + if (this.listbox) { + fillColor.setValueFor(this.listbox, neutralLayerFloating); + } + } + + /** + * Synchronize the form-associated proxy and updates the value property of the element. + * + * @param prev - the previous collection of slotted option elements + * @param next - the next collection of slotted option elements + * + * @internal + */ + slottedOptionsChanged(prev: Element[] | undefined, next: Element[]): void { + super.slottedOptionsChanged(prev, next); this.setAutoWidth(); - } else { - this.style.removeProperty('width'); } - } - - /** - * Compute the listbox width to set the one of the input. - */ - protected setAutoWidth(): void { - if (!this.autoWidth || !this.isConnected) { - return; + + /** + * Returns the calculated max height for the listbox. + * + * @internal + * @remarks + * Used to generate the `--listbox-max-height` CSS custom property. + * + */ + private get listboxMaxHeight(): string { + return Math.floor( + this.maxHeight / heightNumberAsToken.getValueFor(this) + ).toString(); + } + + /** + * The cached scroll width of the listbox when visible. + * + * @internal + */ + @observable + private listboxScrollWidth: string = ""; + + /** + * @internal + */ + protected listboxScrollWidthChanged(): void { + this.updateComputedStylesheet(); + } + + /** + * Returns the size value, if any. Otherwise, returns 4 if in + * multi-selection mode, or 0 if in single-selection mode. + * + * @internal + * @remarks + * Used to generate the `--size` CSS custom property. + * + */ + private get selectSize(): string { + return `${this.size ?? (this.multiple ? 4 : 0)}`; + } + + /** + * Updates the computed stylesheet when the multiple property changes. + * + * @param prev - the previous multiple value + * @param next - the current multiple value + * + * @override + * @internal + */ + public multipleChanged(prev: boolean | undefined, next: boolean): void { + super.multipleChanged(prev, next); + this.updateComputedStylesheet(); + } + + /** + * Sets the selectMaxSize design token when the maxHeight property changes. + * + * @param prev - the previous maxHeight value + * @param next - the current maxHeight value + * + * @internal + */ + protected maxHeightChanged(prev: number | undefined, next: number): void { + if (this.collapsible) { + this.updateComputedStylesheet(); + } } - let listWidth = this.listbox.getBoundingClientRect().width; - // If the list has not been displayed yet trick to get its size - if (listWidth === 0 && this.listbox.hidden) { - Object.assign(this.listbox.style, { visibility: 'hidden' }); - this.listbox.removeAttribute('hidden'); - listWidth = this.listbox.getBoundingClientRect().width; - this.listbox.setAttribute('hidden', ''); - this.listbox.style.removeProperty('visibility'); + public setPositioning(): void { + super.setPositioning(); + this.updateComputedStylesheet(); } - if (listWidth > 0) { - Object.assign(this.style, { width: `${listWidth}px` }); + /** + * Updates the component dimensions when the size property is changed. + * + * @param prev - the previous size value + * @param next - the current size value + * + * @override + * @internal + */ + protected sizeChanged(prev: number | undefined, next: number): void { + super.sizeChanged(prev, next); + this.updateComputedStylesheet(); + + if (this.collapsible) { + requestAnimationFrame(() => { + this.listbox.style.setProperty("display", "flex"); + this.listbox.style.setProperty("overflow", "visible"); + this.listbox.style.setProperty("visibility", "hidden"); + this.listbox.style.setProperty("width", "auto"); + this.listbox.hidden = false; + + this.listboxScrollWidth = `${this.listbox.scrollWidth}`; + + this.listbox.hidden = true; + this.listbox.style.removeProperty("display"); + this.listbox.style.removeProperty("overflow"); + this.listbox.style.removeProperty("visibility"); + this.listbox.style.removeProperty("width"); + }); + + return; + } + + this.listboxScrollWidth = ""; + } + + /** + * Updates an internal stylesheet with calculated CSS custom properties. + * + * @internal + */ + protected updateComputedStylesheet(): void { + if (this.computedStylesheet) { + this.$fastController.removeStyles(this.computedStylesheet); + } + + this.computedStylesheet = css` + :host { + --listbox-max-height: ${this.listboxMaxHeight}; + --listbox-scroll-width: ${this.listboxScrollWidth}; + --size: ${this.selectSize}; + } + `; + + this.$fastController.addStyles(this.computedStylesheet); } - } } /** - * A function that returns a Select registration for configuring the component with a DesignSystem. + * A function that returns a {@link @microsoft/fast-foundation#Select} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#selectTemplate} * * * @public @@ -107,11 +249,11 @@ export class Select extends FoundationSelect { * */ export const jpSelect = Select.compose({ - baseName: 'select', - baseClass: FoundationSelect, - template, - styles, - indicator: /* html */ ` + baseName: 'select', + baseClass: FoundationSelect, + template, + styles, + indicator: /* html */ ` ({ d="M11.85.65c.2.2.2.5 0 .7L6.4 6.84a.55.55 0 01-.78 0L.14 1.35a.5.5 0 11.71-.7L6 5.8 11.15.65c.2-.2.5-.2.7 0z" /> - ` + `, }); export { styles as selectStyles }; diff --git a/packages/components/src/select/select.pw.spec.ts b/packages/components/src/select/select.pw.spec.ts new file mode 100644 index 00000000..5f8bce95 --- /dev/null +++ b/packages/components/src/select/select.pw.spec.ts @@ -0,0 +1,322 @@ +import type { + ListboxOption as JpOption, + Select as JpSelectType +} from "@microsoft/fast-foundation"; +import { ArrowKeys } from "@microsoft/fast-web-utilities"; +import chai from "chai"; +import type { ElementHandle } from "playwright"; + +const { expect } = chai; + +type JpSelect = HTMLElement & JpSelectType; + +describe("JpSelect", function () { + beforeEach(async function () { + if (!this.page && !this.browser) { + this.skip(); + } + + this.documentHandle = await this.page.evaluateHandle(() => document); + + this.setupHandle = await this.page.evaluateHandle( + (document) => { + const element = document.createElement("jp-select") as JpSelect; + + for (let i = 1; i <= 3; i++) { + const option = document.createElement("jp-option") as JpOption; + option.value = `${i}`; + option.textContent = `option ${i}`; + element.appendChild(option); + } + + document.body.appendChild(element) + }, + this.documentHandle + ); + }); + + afterEach(async function () { + if (this.setupHandle) { + await this.setupHandle.dispose(); + } + }); + + // FASTSelect should render on the page + it("should render on the page", async function () { + const element = await this.page.waitForSelector("jp-select"); + + expect(element).to.exist; + }); + + // FASTSelect should have a value of 'one' + it("should have a value of 'one'", async function () { + const element = await this.page.waitForSelector("jp-select"); + expect(await element?.evaluate(node => (node as JpSelect).value)).to.equal("1"); + }); + + // FASTSelect should have a text content of 'option 1' + it("should have a text content of 'option 1'", async function () { + const element = await this.page.waitForSelector("jp-select .selected-value"); + expect(await element?.evaluate((node: HTMLElement) => node.innerText)).to.equal( + "option 1" + ); + }); + + // FASTSelect should open when focused and receives keyboard interaction + describe("should open when focused and receives keyboard interaction", function () { + // FASTSelect should open when focused and receives keyboard interaction via space key + it("via Space key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + expect(await element.evaluate(node => node.open)).to.be.false; + + await element.focus(); + + await this.page.keyboard.press(" "); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await this.page.keyboard.press(" "); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + + // FASTSelect should open when focused and receives keyboard interaction via enter key + it("via Enter key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + expect(await element.evaluate(node => node.open)).to.be.false; + + await element.focus(); + + await element.press("Enter"); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await element.press("Enter"); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + }); + + // FASTSelect should close + describe("should close", function () { + // FASTSelect should close when focused and keyboard interaction is received + describe("when focused and keyboard interaction is received", function () { + // FASTSelect should close when focused and keyboard interaction is received via space key + it("via Space key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.press(" "); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await element.press(" "); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + + // FASTSelect should close when focused and keyboard interaction is received via enter key + it("via Enter key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.press("Enter"); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await element.press("Enter"); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + + // FASTSelect should close when focused and keyboard interaction is received via escape key + it("via Escape key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.click(); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await this.page.keyboard.press("Escape"); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + + // FASTSelect should close when focused and keyboard interaction is received via tab key + it("via Tab key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.click(); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await element.press("Tab"); + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + }); + + describe("when focus is lost", function () { + it("via click", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.click(); + + expect(await element.evaluate(node => node.open)).to.be.true; + + await this.page.click("body"); + + expect( + await this.page.evaluate( + element => element.isSameNode(document.activeElement), + element + ) + ).to.be.false; + + expect(await element.evaluate(node => node.open)).to.be.false; + }); + }); + }); + + describe("should emit an event when focused and receives keyboard interaction", function () { + describe("while closed", function () { + for (const direction of Object.values(ArrowKeys)) { + describe(`via ${direction} key`, function () { + for (const eventName of ["change", "input"]) { + it(`of type '${eventName}'`, async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await this.page.exposeFunction("sendEvent", type => + expect(type).to.equal(eventName) + ); + + await element.evaluate((node, eventName) => { + node.addEventListener( + eventName, + async (e: CustomEvent) => + await window["sendEvent"](e.type) + ); + }, eventName); + + await element.press(direction); + }); + } + }); + } + }); + }); + + describe("should change the value when focused and receives keyboard interaction", function () { + it("via arrow down key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + expect(await element.evaluate(node => node.value)).to.equal("1"); + + await element.press("ArrowDown"); + + expect(await element.evaluate(node => node.value)).to.equal("2"); + }); + + it("via arrow up key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.evaluate(node => (node.value = "2")); + + expect(await element.evaluate(node => node.value)).to.equal("2"); + + await element.press("ArrowUp"); + + expect(await element.evaluate(node => node.value)).to.equal("1"); + }); + + it("via home key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.evaluate(node => (node.value = "3")); + + expect(await element.evaluate(node => node.value)).to.equal("3"); + + await element.press("Home"); + + expect(await element.evaluate(node => node.value)).to.equal("1"); + }); + + it("via end key", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + expect(await element.evaluate(node => node.value)).to.equal("1"); + + await element.press("End"); + + expect(await element.evaluate(node => node.value)).to.equal("3"); + }); + }); + + describe("when opened", function () { + it("should scroll the selected option into view", async function () { + const element = (await this.page.waitForSelector( + "jp-select" + )) as ElementHandle; + + await element.evaluate(element => { + element.innerHTML = ""; + for (let i = 0; i < 50; i++) { + const option = document.createElement("jp-option") as JpOption; + option.value = `${i}`; + option.textContent = `option ${i}`; + element.appendChild(option); + } + }); + + const selectedOption = (await element.$(".listbox")) as ElementHandle< + JpOption + >; + + await element.evaluate(node => (node.selectedIndex = 35)); + + expect( + await element.evaluate(node => node.firstSelectedOption.value) + ).to.equal("35"); + + await element.click(); + + await selectedOption.waitForElementState("visible"); + + expect( + await selectedOption.evaluate(node => node.scrollTop) + ).to.be.closeTo(451, 16); + + await element.evaluate(node => (node.selectedIndex = 0)); + + await element.waitForElementState("stable"); + + expect( + await selectedOption.evaluate(node => node.scrollTop) + ).to.be.closeTo(6, 16); + }); + }); +}); diff --git a/packages/components/src/select/select.styles.ts b/packages/components/src/select/select.styles.ts index b58678cd..712937dc 100644 --- a/packages/components/src/select/select.styles.ts +++ b/packages/components/src/select/select.styles.ts @@ -1,276 +1,316 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - -import { - accentFillActive, - accentFillFocus, - bodyFont, - controlCornerRadius, - designUnit, - disabledOpacity, - focusStrokeWidth, - neutralFillInputActive, - neutralFillInputHover, - neutralFillInputRest, - neutralFillStealthRest, - neutralFillStrongHover, - neutralFillStrongRest, - neutralForegroundRest, - neutralLayerFloating, - neutralStrokeRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '@microsoft/fast-components'; -import type { ElementStyles } from '@microsoft/fast-element'; -import { css } from '@microsoft/fast-element'; +import type { ElementStyles } from "@microsoft/fast-element"; +import { css } from "@microsoft/fast-element"; import type { - FoundationElementTemplate, - SelectOptions -} from '@microsoft/fast-foundation'; + FoundationElementTemplate, + SelectOptions, +} from "@microsoft/fast-foundation"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; -import { elevation, heightNumber } from '../styles'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + ListboxOption, + Select, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + accentFillActive, + accentFillFocus, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + focusStrokeWidth, + foregroundOnAccentFocus, + neutralFillInputActive, + neutralFillInputHover, + neutralFillInputRest, + neutralFillStealthRest, + neutralFillStrongHover, + neutralFillStrongRest, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { listboxStyles } from "../listbox/listbox.styles.js"; +import { elevation } from "../styles/elevation.js"; +import { heightNumber } from "../styles/size.js"; /** - * Styles for Select + * Styles for Select. + * * @public */ -export const selectStyles: FoundationElementTemplate< - ElementStyles, - SelectOptions -> = (context, definition) => - css` - ${display('inline-flex')} :host { - --elevation: 14; - background: ${neutralFillInputRest}; - border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; - box-sizing: border-box; - color: ${neutralForegroundRest}; - font-family: ${bodyFont}; - height: calc(${heightNumber} * 1px); - position: relative; - user-select: none; - outline: none; - vertical-align: top; - } - - :host(:not([autowidth])) { - min-width: 250px; - } - - .listbox { - ${elevation} - background: ${neutralLayerFloating}; - border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; - border-radius: calc(${controlCornerRadius} * 1px); - box-sizing: border-box; - display: inline-flex; - flex-direction: column; - left: 0; - max-height: calc(var(--max-height) - (${heightNumber} * 1px)); - padding: calc(${designUnit} * 1px) 0; - overflow-y: auto; - position: absolute; - z-index: 1; - } - - :host(:not([autowidth])) .listbox { - width: 100%; - } - - :host([autowidth]) ::slotted([role='option']), - :host([autowidth]) ::slotted(option) { - padding: 0 calc(1em + ${designUnit} * 1.25px + 1px); - } - - .listbox[hidden] { - display: none; - } - - .control { - align-items: center; - box-sizing: border-box; - cursor: pointer; - display: flex; - font-size: ${typeRampBaseFontSize}; - font-family: inherit; - line-height: ${typeRampBaseLineHeight}; - min-height: 100%; - padding: 0 calc(${designUnit} * 2.25px); - width: 100%; - } +export const selectStyles: FoundationElementTemplate = ( + context, + definition +) => { + const selectContext = context.name === context.tagFor(Select); + + // The expression interpolations present in this block cause Prettier to generate + // various formatting bugs. + // prettier-ignore + return css` + ${display("inline-flex")} + + :host { + --elevation: 14; + background: ${neutralFillInputRest}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; + box-sizing: border-box; + color: ${neutralForegroundRest}; + font-family: ${bodyFont}; + height: calc(${heightNumber} * 1px); + position: relative; + user-select: none; + min-width: 250px; + outline: none; + vertical-align: top; + } + + :host(:not([autowidth])) { + min-width: 250px; + } + + ${selectContext ? css` + :host(:not([aria-haspopup])) { + --elevation: 0; + border: 0; + height: auto; + min-width: 0; + } + ` : ""} + + ${listboxStyles(context, definition)} + + :host .listbox { + ${elevation} + border: none; + display: flex; + left: 0; + position: absolute; + width: 100%; + z-index: 1; + } + + .control + .listbox { + --stroke-size: calc(${designUnit} * ${strokeWidth} * 2); + max-height: calc( + (var(--listbox-max-height) * ${heightNumber} + var(--stroke-size)) * 1px + ); + } + + ${selectContext ? css` + :host(:not([aria-haspopup])) .listbox { + left: auto; + position: static; + z-index: auto; + } + ` : ""} + + :host(:not([autowidth])) .listbox { + width: 100%; + } + + :host([autowidth]) ::slotted([role='option']), + :host([autowidth]) ::slotted(option) { + padding: 0 calc(1em + ${designUnit} * 1.25px + 1px); + } + + .listbox[hidden] { + display: none; + } + + .control { + align-items: center; + box-sizing: border-box; + cursor: pointer; + display: flex; + font-size: ${typeRampBaseFontSize}; + font-family: inherit; + line-height: ${typeRampBaseLineHeight}; + min-height: 100%; + padding: 0 calc(${designUnit} * 2.25px); + width: 100%; + } :host([minimal]) { --density: -4; } - :host(:not([disabled]):hover) { - background: ${neutralFillInputHover}; - border-color: ${neutralFillStrongHover}; - } - - :host(:${focusVisible}) { - border-color: ${accentFillFocus}; - box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) - ${accentFillFocus}; - } - - :host([disabled]) { - cursor: ${disabledCursor}; - opacity: ${disabledOpacity}; - } - - :host([disabled]) .control { - cursor: ${disabledCursor}; - user-select: none; - } - - :host([disabled]:hover) { - background: ${neutralFillStealthRest}; - color: ${neutralForegroundRest}; - fill: currentcolor; - } - - :host(:not([disabled])) .control:active { - background: ${neutralFillInputActive}; - border-color: ${accentFillActive}; - border-radius: calc(${controlCornerRadius} * 1px); - } - - :host([open][position='above']) .listbox { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - :host([open][position='below']) .listbox { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - :host([open][position='above']) .listbox { - border-bottom: 0; - bottom: calc(${heightNumber} * 1px); - } - - :host([open][position='below']) .listbox { - border-top: 0; - top: calc(${heightNumber} * 1px); - } - - .selected-value { - flex: 1 1 auto; - font-family: inherit; - text-align: start; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - - .indicator { - flex: 0 0 auto; - margin-inline-start: 1em; - } - - slot[name='listbox'] { - display: none; - width: 100%; - } - - :host([open]) slot[name='listbox'] { - display: flex; - position: absolute; - ${elevation} - } - - .end { - margin-inline-start: auto; - } - - .start, - .end, - .indicator, - .select-indicator, - ::slotted(svg) { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - fill: currentcolor; - height: 1em; - min-height: calc(${designUnit} * 4px); - min-width: calc(${designUnit} * 4px); - width: 1em; - } - - ::slotted([role='option']), - ::slotted(option) { - flex: 0 0 auto; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host(:not([disabled]):hover), - :host(:not([disabled]):active) { - border-color: ${SystemColors.Highlight}; - } - - :host(:not([disabled]):${focusVisible}) { - background-color: ${SystemColors.ButtonFace}; - box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) - ${SystemColors.Highlight}; - color: ${SystemColors.ButtonText}; - fill: currentcolor; - forced-color-adjust: none; - } - - :host(:not([disabled]):${focusVisible}) .listbox { - background: ${SystemColors.ButtonFace}; - } - - :host([disabled]) { - border-color: ${SystemColors.GrayText}; - background-color: ${SystemColors.ButtonFace}; - color: ${SystemColors.GrayText}; - fill: currentcolor; - opacity: 1; - forced-color-adjust: none; - } - - :host([disabled]:hover) { - background: ${SystemColors.ButtonFace}; - } - - :host([disabled]) .control { - color: ${SystemColors.GrayText}; - border-color: ${SystemColors.GrayText}; - } - - :host([disabled]) .control .select-indicator { - fill: ${SystemColors.GrayText}; - } - - :host(:${focusVisible}) ::slotted([aria-selected="true"][role="option"]), - :host(:${focusVisible}) ::slotted(option[aria-selected="true"]), - :host(:${focusVisible}) ::slotted([aria-selected="true"][role="option"]:not([disabled])) { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.ButtonText}; - box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) + :host(:not([disabled]):hover) { + background: ${neutralFillInputHover}; + border-color: ${neutralFillStrongHover}; + } + + :host(:${focusVisible}) { + border-color: ${accentFillFocus}; + box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) + ${accentFillFocus}; + } + + :host(:not([size]):not([multiple]):not([open]):${focusVisible}), + :host([multiple]:${focusVisible}), + :host([size]:${focusVisible}) { + box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) + ${accentFillFocus}; + } + + :host(:not([multiple]):not([size]):${focusVisible}) ::slotted(${context.tagFor( + ListboxOption + )}[aria-selected="true"]:not([disabled])) { + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) inset ${accentFillFocus}; + border-color: ${accentFillFocus}; + background: ${accentFillFocus}; + color: ${foregroundOnAccentFocus}; + } + + :host([disabled]) { + cursor: ${disabledCursor}; + opacity: ${disabledOpacity}; + } + + :host([disabled]) .control { + cursor: ${disabledCursor}; + user-select: none; + } + + :host([disabled]:hover) { + background: ${neutralFillStealthRest}; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + + :host(:not([disabled])) .control:active { + background: ${neutralFillInputActive}; + border-color: ${accentFillActive}; + border-radius: calc(${controlCornerRadius} * 1px); + } + + :host([open][position="above"]) .listbox { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: 0; + bottom: calc(${heightNumber} * 1px); + } + + :host([open][position="below"]) .listbox { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: 0; + top: calc(${heightNumber} * 1px); + } + + .selected-value { + flex: 1 1 auto; + font-family: inherit; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + } + + .indicator { + flex: 0 0 auto; + margin-inline-start: 1em; + } + + slot[name="listbox"] { + display: none; + width: 100%; + } + + :host([open]) slot[name="listbox"] { + display: flex; + position: absolute; + ${elevation} + } + + .end { + margin-inline-start: auto; + } + + .start, + .end, + .indicator, + .select-indicator, + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + fill: currentcolor; + height: 1em; + min-height: calc(${designUnit} * 4px); + min-width: calc(${designUnit} * 4px); + width: 1em; + } + + ::slotted([role="option"]), + ::slotted(option) { + flex: 0 0 auto; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host(:not([disabled]):hover), + :host(:not([disabled]):active) { + border-color: ${SystemColors.Highlight}; + } + + :host(:not([disabled]):${focusVisible}) { + background-color: ${SystemColors.ButtonFace}; + box-shadow: 0 0 0 calc(${focusStrokeWidth} * 1px) ${SystemColors.Highlight}; + color: ${SystemColors.ButtonText}; + fill: currentcolor; + forced-color-adjust: none; + } + + :host(:not([disabled]):${focusVisible}) .listbox { + background: ${SystemColors.ButtonFace}; + } + + :host([disabled]) { + border-color: ${SystemColors.GrayText}; + background-color: ${SystemColors.ButtonFace}; + color: ${SystemColors.GrayText}; + fill: currentcolor; + opacity: 1; + forced-color-adjust: none; + } + + :host([disabled]:hover) { + background: ${SystemColors.ButtonFace}; + } + + :host([disabled]) .control { + color: ${SystemColors.GrayText}; + border-color: ${SystemColors.GrayText}; + } + + :host([disabled]) .control .select-indicator { + fill: ${SystemColors.GrayText}; + } + + :host(:${focusVisible}) ::slotted([aria-selected="true"][role="option"]), + :host(:${focusVisible}) ::slotted(option[aria-selected="true"]), + :host(:${focusVisible}) ::slotted([aria-selected="true"][role="option"]:not([disabled])) { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.ButtonText}; + box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) ${SystemColors.HighlightText}; - color: ${SystemColors.HighlightText}; - fill: currentcolor; - } - - .start, - .end, - .indicator, - .select-indicator, - ::slotted(svg) { - color: ${SystemColors.ButtonText}; - fill: currentcolor; - } - `) - ); + color: ${SystemColors.HighlightText}; + fill: currentcolor; + } + + .start, + .end, + .indicator, + .select-indicator, + ::slotted(svg) { + color: ${SystemColors.ButtonText}; + fill: currentcolor; + } + ` + ) + ); +}; diff --git a/packages/components/src/skeleton/index.ts b/packages/components/src/skeleton/index.ts new file mode 100644 index 00000000..477b1890 --- /dev/null +++ b/packages/components/src/skeleton/index.ts @@ -0,0 +1,25 @@ +import { Skeleton, skeletonTemplate as template } from "@microsoft/fast-foundation"; +import { skeletonStyles as styles } from "./skeleton.styles.js"; + +/** + * A function that returns a {@link @microsoft/fast-foundation#Skeleton} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#skeletonTemplate} + * + * + * @public + * @remarks + * Generates HTML Element: `` + */ +export const fastSkeleton = Skeleton.compose({ + baseName: "skeleton", + template, + styles, +}); + +/** + * Base class for Skeleton + * @public + */ +export { Skeleton }; + +export { styles as skeletonStyles }; diff --git a/packages/components/src/skeleton/skeleton.stories.ts b/packages/components/src/skeleton/skeleton.stories.ts new file mode 100644 index 00000000..f5d9a79d --- /dev/null +++ b/packages/components/src/skeleton/skeleton.stories.ts @@ -0,0 +1,8 @@ +import SkeletonTemplate from "./fixtures/base.html"; +import "./index.js"; + +export default { + title: "Skeleton", +}; + +export const Skeleton = () => SkeletonTemplate; diff --git a/packages/components/src/skeleton/skeleton.styles.ts b/packages/components/src/skeleton/skeleton.styles.ts new file mode 100644 index 00000000..5902f17d --- /dev/null +++ b/packages/components/src/skeleton/skeleton.styles.ts @@ -0,0 +1,106 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { display } from "@microsoft/fast-foundation"; +import { controlCornerRadius, neutralFillRest } from "../design-tokens.js"; + +/** + * Styles for Skeleton + * @public + */ +export const skeletonStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("block")} :host { + --skeleton-fill-default: #e1dfdd; + overflow: hidden; + width: 100%; + position: relative; + background-color: var(--skeleton-fill, var(--skeleton-fill-default)); + --skeleton-animation-gradient-default: linear-gradient( + 270deg, + var(--skeleton-fill, var(--skeleton-fill-default)) 0%, + #f3f2f1 51.13%, + var(--skeleton-fill, var(--skeleton-fill-default)) 100% + ); + --skeleton-animation-timing-default: ease-in-out; + } + + :host([shape="rect"]) { + border-radius: calc(${controlCornerRadius} * 1px); + } + + :host([shape="circle"]) { + border-radius: 100%; + overflow: hidden; + } + + object { + position: absolute; + width: 100%; + height: auto; + z-index: 2; + } + + object img { + width: 100%; + height: auto; + } + + ${display("block")} span.shimmer { + position: absolute; + width: 100%; + height: 100%; + background-image: var( + --skeleton-animation-gradient, + var(--skeleton-animation-gradient-default) + ); + background-size: 0px 0px / 90% 100%; + background-repeat: no-repeat; + background-color: var(--skeleton-animation-fill, ${neutralFillRest}); + animation: shimmer 2s infinite; + animation-timing-function: var( + --skeleton-animation-timing, + var(--skeleton-timing-default) + ); + animation-direction: normal; + z-index: 1; + } + + ::slotted(svg) { + z-index: 2; + } + + ::slotted(.pattern) { + width: 100%; + height: 100%; + } + + @keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host { + forced-color-adjust: none; + background-color: ${SystemColors.ButtonFace}; + box-shadow: 0 0 0 1px ${SystemColors.ButtonText}; + } + + ${display("block")} span.shimmer { + display: none; + } + ` + ) + ); diff --git a/packages/components/src/slider-label/index.ts b/packages/components/src/slider-label/index.ts index 424650a0..834b8868 100644 --- a/packages/components/src/slider-label/index.ts +++ b/packages/components/src/slider-label/index.ts @@ -1,16 +1,28 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - horizontalSliderLabelStyles, - SliderLabel, - sliderLabelStyles as styles, - verticalSliderLabelStyles -} from '@microsoft/fast-components'; + SliderLabel as FoundationSliderLabel, + sliderLabelTemplate as template, +} from "@microsoft/fast-foundation"; +import { Orientation } from "@microsoft/fast-web-utilities"; import { - SliderLabel as FoundationSliderLabel, - sliderLabelTemplate as template -} from '@microsoft/fast-foundation'; + horizontalSliderLabelStyles, + sliderLabelStyles as styles, + verticalSliderLabelStyles, +} from "./slider-label.styles.js"; + +/** + * @internal + */ +export class SliderLabel extends FoundationSliderLabel { + protected sliderOrientationChanged(): void { + if (this.sliderOrientation === Orientation.horizontal) { + this.$fastController.addStyles(horizontalSliderLabelStyles); + this.$fastController.removeStyles(verticalSliderLabelStyles); + } else { + this.$fastController.addStyles(verticalSliderLabelStyles); + this.$fastController.removeStyles(horizontalSliderLabelStyles); + } + } +} /** * A function that returns a {@link @microsoft/fast-foundation#SliderLabel} registration for configuring the component with a DesignSystem. @@ -22,15 +34,14 @@ import { * Generates HTML Element: `` */ export const jpSliderLabel = SliderLabel.compose({ - baseName: 'slider-label', - baseClass: FoundationSliderLabel, - template, - styles + baseName: 'slider-label', + baseClass: FoundationSliderLabel, + template, + styles, }); export { - SliderLabel, - horizontalSliderLabelStyles, - styles as sliderLabelStyles, - verticalSliderLabelStyles + horizontalSliderLabelStyles, + styles as sliderLabelStyles, + verticalSliderLabelStyles, }; diff --git a/packages/components/src/slider-label/slider-label.styles.ts b/packages/components/src/slider-label/slider-label.styles.ts new file mode 100644 index 00000000..86bec4e5 --- /dev/null +++ b/packages/components/src/slider-label/slider-label.styles.ts @@ -0,0 +1,122 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { + bodyFont, + designUnit, + disabledOpacity, + neutralForegroundRest, + neutralStrokeRest, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; + +/** + * Styles for Horizontal Slider label + * @public + */ +export const horizontalSliderLabelStyles = css` + :host { + align-self: start; + grid-row: 2; + margin-top: -2px; + height: calc((${heightNumber} / 2 + ${designUnit}) * 1px); + width: auto; + } + .container { + grid-template-rows: auto auto; + grid-template-columns: 0; + } + .label { + margin: 2px 0; + } +`; + +/** + * Styles for Vertical slider label + * @public + */ +export const verticalSliderLabelStyles = css` + :host { + justify-self: start; + grid-column: 2; + margin-left: 2px; + height: auto; + width: calc((${heightNumber} / 2 + ${designUnit}) * 1px); + } + .container { + grid-template-columns: auto auto; + grid-template-rows: 0; + min-width: calc(var(--thumb-size) * 1px); + height: calc(var(--thumb-size) * 1px); + } + .mark { + transform: rotate(90deg); + align-self: center; + } + .label { + margin-left: calc((${designUnit} / 2) * 3px); + align-self: center; + } +`; + +/** + * Styles for Slider Label + * @public + */ +export const sliderLabelStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + ${display("block")} :host { + font-family: ${bodyFont}; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + .root { + position: absolute; + display: grid; + } + .container { + display: grid; + justify-self: center; + } + .label { + justify-self: center; + align-self: center; + white-space: nowrap; + max-width: 30px; + } + .mark { + width: calc((${designUnit} / 4) * 1px); + height: calc(${heightNumber} * 0.25 * 1px); + background: ${neutralStrokeRest}; + justify-self: center; + } + :host(.disabled) { + opacity: ${disabledOpacity}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .mark { + forced-color-adjust: none; + background: ${SystemColors.FieldText}; + } + :host(.disabled) { + forced-color-adjust: none; + opacity: 1; + } + :host(.disabled) .label { + color: ${SystemColors.GrayText}; + } + :host(.disabled) .mark { + background: ${SystemColors.GrayText}; + } + ` + ) + ); diff --git a/packages/components/src/slider/index.ts b/packages/components/src/slider/index.ts index e970d89c..9ecc756a 100644 --- a/packages/components/src/slider/index.ts +++ b/packages/components/src/slider/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - Slider, - SliderOptions, - sliderTemplate as template -} from '@microsoft/fast-foundation'; -import { sliderStyles as styles } from './slider.styles'; + Slider, + SliderOptions, + sliderTemplate as template, +} from "@microsoft/fast-foundation"; +import { sliderStyles as styles } from "./slider.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Slider} registration for configuring the component with a DesignSystem. @@ -18,12 +15,12 @@ import { sliderStyles as styles } from './slider.styles'; * Generates HTML Element: `` */ export const jpSlider = Slider.compose({ - baseName: 'slider', - template, - styles, - thumb: /* html */ ` + baseName: 'slider', + template, + styles, + thumb: /* html */ `
- ` + `, }); /** diff --git a/packages/components/src/slider/slider.styles.ts b/packages/components/src/slider/slider.styles.ts index 4f62bfd6..27cf7dcf 100644 --- a/packages/components/src/slider/slider.styles.ts +++ b/packages/components/src/slider/slider.styles.ts @@ -1,175 +1,193 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - SliderOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + SliderOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { accentFillFocus, - controlCornerRadius, - designUnit, - disabledOpacity, - fillColor, - focusStrokeWidth, - neutralForegroundRest, - neutralStrokeHover, - neutralStrokeRest -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; + accentForegroundRest, + controlCornerRadius, + designUnit, + disabledOpacity, + fillColor, + focusStrokeOuter, + focusStrokeWidth, + neutralForegroundRest, + neutralStrokeHover, + neutralStrokeRest, +} from "../design-tokens.js"; +import { DirectionalStyleSheetBehavior, heightNumber } from "../styles/index.js"; + +const ltr = css` + .track-start { + left: 0; + } +`; + +const rtl = css` + .track-start { + right: 0; + } +`; /** * Styles for Slider * @public */ -export const sliderStyles: FoundationElementTemplate< - ElementStyles, - SliderOptions -> = (context, definition) => - css` - :host([hidden]) { - display: none; - } +export const sliderStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host([hidden]) { + display: none; + } - ${display('inline-grid')} :host { - --thumb-size: calc(${heightNumber} * 0.5 - ${designUnit}); - --thumb-translate: calc( - var(--thumb-size) * -0.5 + var(--track-width) / 2 - ); - --track-overhang: calc((${designUnit} / 2) * -1); - --track-width: ${designUnit}; - --jp-slider-height: calc(var(--thumb-size) * 10); - align-items: center; - width: 100%; - margin: calc(${designUnit} * 1px) 0; - user-select: none; - box-sizing: border-box; - border-radius: calc(${controlCornerRadius} * 1px); - outline: none; - cursor: pointer; - } - :host([orientation='horizontal']) .positioning-region { - position: relative; - margin: 0 8px; - display: grid; - grid-template-rows: calc(var(--thumb-size) * 1px) 1fr; - } - :host([orientation='vertical']) .positioning-region { - position: relative; - margin: 0 8px; - display: grid; - height: 100%; - grid-template-columns: calc(var(--thumb-size) * 1px) 1fr; - } + ${display("inline-grid")} :host { + --thumb-size: calc(${heightNumber} * 0.5 - ${designUnit}); + --thumb-translate: calc(var(--thumb-size) * -0.5 + var(--track-width) / 2); + --track-overhang: calc((${designUnit} / 2) * -1); + --track-width: ${designUnit}; + --jp-slider-height: calc(var(--thumb-size) * 10); + align-items: center; + width: 100%; + margin: calc(${designUnit} * 1px) 0; + user-select: none; + box-sizing: border-box; + border-radius: calc(${controlCornerRadius} * 1px); + outline: none; + cursor: pointer; + } + :host([orientation="horizontal"]) .positioning-region { + position: relative; + margin: 0 8px; + display: grid; + grid-template-rows: calc(var(--thumb-size) * 1px) 1fr; + } + :host([orientation="vertical"]) .positioning-region { + position: relative; + margin: 0 8px; + display: grid; + height: 100%; + grid-template-columns: calc(var(--thumb-size) * 1px) 1fr; + } - :host(:${focusVisible}) .thumb-cursor { - box-shadow: + :host(:${focusVisible}) .thumb-cursor { + box-shadow: 0 0 0 2px ${fillColor}, 0 0 0 calc((2 + ${focusStrokeWidth}) * 1px) ${accentFillFocus}; - } + } - .thumb-container { - position: absolute; - height: calc(var(--thumb-size) * 1px); - width: calc(var(--thumb-size) * 1px); - transition: all 0.2s ease; - color: ${neutralForegroundRest}; - fill: currentcolor; - } - .thumb-cursor { - border: none; - width: calc(var(--thumb-size) * 1px); - height: calc(var(--thumb-size) * 1px); - background: ${neutralForegroundRest}; - border-radius: calc(${controlCornerRadius} * 1px); - } - .thumb-cursor:hover { - background: ${neutralForegroundRest}; - border-color: ${neutralStrokeHover}; - } - .thumb-cursor:active { - background: ${neutralForegroundRest}; - } - :host([orientation='horizontal']) .thumb-container { - transform: translateX(calc(var(--thumb-size) * 0.5px)) - translateY(calc(var(--thumb-translate) * 1px)); - } - :host([orientation='vertical']) .thumb-container { - transform: translateX(calc(var(--thumb-translate) * 1px)) - translateY(calc(var(--thumb-size) * 0.5px)); - } - :host([orientation='horizontal']) { - min-width: calc(var(--thumb-size) * 1px); - } - :host([orientation='horizontal']) .track { - right: calc(var(--track-overhang) * 1px); - left: calc(var(--track-overhang) * 1px); - align-self: start; - height: calc(var(--track-width) * 1px); - } - :host([orientation='vertical']) .track { - top: calc(var(--track-overhang) * 1px); - bottom: calc(var(--track-overhang) * 1px); - width: calc(var(--track-width) * 1px); - height: 100%; - } - .track { - background: ${neutralStrokeRest}; - position: absolute; - border-radius: calc(${controlCornerRadius} * 1px); - } - :host([orientation='vertical']) { - height: calc(var(--jp-slider-height) * 1px); - min-height: calc(var(--thumb-size) * 1px); - min-width: calc(${designUnit} * 20px); - } - :host([disabled]), - :host([readonly]) { - cursor: ${disabledCursor}; - } - :host([disabled]) { - opacity: ${disabledOpacity}; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - .thumb-cursor { - forced-color-adjust: none; - border-color: ${SystemColors.FieldText}; - background: ${SystemColors.FieldText}; - } - .thumb-cursor:hover, - .thumb-cursor:active { - background: ${SystemColors.Highlight}; - } - .track { - forced-color-adjust: none; - background: ${SystemColors.FieldText}; - } - :host(:${focusVisible}) .thumb-cursor { - border-color: ${SystemColors.Highlight}; - } - :host([disabled]) { - opacity: 1; - } - :host([disabled]) .track, - :host([disabled]) .thumb-cursor { - forced-color-adjust: none; - background: ${SystemColors.GrayText}; - } + .thumb-container { + position: absolute; + height: calc(var(--thumb-size) * 1px); + width: calc(var(--thumb-size) * 1px); + transition: all 0.2s ease; + color: ${neutralForegroundRest}; + fill: currentcolor; + } + .thumb-cursor { + border: none; + width: calc(var(--thumb-size) * 1px); + height: calc(var(--thumb-size) * 1px); + background: ${neutralForegroundRest}; + border-radius: calc(${controlCornerRadius} * 1px); + } + .thumb-cursor:hover { + background: ${neutralForegroundRest}; + border-color: ${neutralStrokeHover}; + } + .thumb-cursor:active { + background: ${neutralForegroundRest}; + } + .track-start { + background: ${accentForegroundRest}; + position: absolute; + height: 100%; + left: 0; + border-radius: calc(${controlCornerRadius} * 1px); + } + :host([orientation="horizontal"]) .thumb-container { + transform: translateX(calc(var(--thumb-size) * 0.5px)) translateY(calc(var(--thumb-translate) * 1px)); + } + :host([orientation="vertical"]) .thumb-container { + transform: translateX(calc(var(--thumb-translate) * 1px)) translateY(calc(var(--thumb-size) * 0.5px)); + } + :host([orientation="horizontal"]) { + min-width: calc(var(--thumb-size) * 1px); + } + :host([orientation="horizontal"]) .track { + right: calc(var(--track-overhang) * 1px); + left: calc(var(--track-overhang) * 1px); + align-self: start; + height: calc(var(--track-width) * 1px); + } + :host([orientation="vertical"]) .track { + top: calc(var(--track-overhang) * 1px); + bottom: calc(var(--track-overhang) * 1px); + width: calc(var(--track-width) * 1px); + height: 100%; + } + .track { + background: ${neutralStrokeRest}; + position: absolute; + border-radius: calc(${controlCornerRadius} * 1px); + } + :host([orientation="vertical"]) { + height: calc(var(--fast-slider-height) * 1px); + min-height: calc(var(--thumb-size) * 1px); + min-width: calc(${designUnit} * 20px); + } + :host([orientation="vertical"]) .track-start { + height: auto; + width: 100%; + top: 0; + } + :host([disabled]), :host([readonly]) { + cursor: ${disabledCursor}; + } + :host([disabled]) { + opacity: ${disabledOpacity}; + } + `.withBehaviors( + new DirectionalStyleSheetBehavior(ltr, rtl), + forcedColorsStylesheetBehavior( + css` + .thumb-cursor { + forced-color-adjust: none; + border-color: ${SystemColors.FieldText}; + background: ${SystemColors.FieldText}; + } + .thumb-cursor:hover, + .thumb-cursor:active { + background: ${SystemColors.Highlight}; + } + .track { + forced-color-adjust: none; + background: ${SystemColors.FieldText}; + } + :host(:${focusVisible}) .thumb-cursor { + border-color: ${SystemColors.Highlight}; + } + :host([disabled]) { + opacity: 1; + } + :host([disabled]) .track, + :host([disabled]) .thumb-cursor { + forced-color-adjust: none; + background: ${SystemColors.GrayText}; + } - :host(:${focusVisible}) .thumb-cursor { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.Highlight}; - box-shadow: - 0 0 0 2px ${SystemColors.Field}, - 0 0 0 4px ${SystemColors.FieldText}; - } - `) - ); + :host(:${focusVisible}) .thumb-cursor { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; + box-shadow: 0 0 0 2px ${SystemColors.Field}, 0 0 0 4px ${SystemColors.FieldText}; + } + ` + ) + ); diff --git a/packages/components/src/styles/direction.ts b/packages/components/src/styles/direction.ts new file mode 100644 index 00000000..f8db6ea7 --- /dev/null +++ b/packages/components/src/styles/direction.ts @@ -0,0 +1,102 @@ +import { + Behavior, + ElementStyles, + FASTElement, + Subscriber, +} from "@microsoft/fast-element"; +import { DesignTokenChangeRecord } from "@microsoft/fast-foundation"; +import { Direction } from "@microsoft/fast-web-utilities"; +import { direction as directionDesignToken } from "../design-tokens.js"; +/** + * Behavior to conditionally apply LTR and RTL stylesheets. To determine which to apply, + * the behavior will use the nearest DesignSystemProvider's 'direction' design system value. + * + * @public + * @example + * ```ts + * import { css } from "@microsoft/fast-element"; + * import { DirectionalStyleSheetBehavior } from "@microsoft/fast-foundation"; + * + * css` + * // ... + * `.withBehaviors(new DirectionalStyleSheetBehavior( + * css`:host { content: "ltr"}`), + * css`:host { content: "rtl"}`), + * ) + * ``` + */ +export class DirectionalStyleSheetBehavior implements Behavior { + private ltr: ElementStyles | null; + private rtl: ElementStyles | null; + private cache: WeakMap< + HTMLElement, + DirectionalStyleSheetBehaviorSubscription + > = new WeakMap(); + + constructor(ltr: ElementStyles | null, rtl: ElementStyles | null) { + this.ltr = ltr; + this.rtl = rtl; + } + + /** + * @internal + */ + public bind(source: FASTElement & HTMLElement) { + this.attach(source); + } + + /** + * @internal + */ + public unbind(source: FASTElement & HTMLElement) { + const cache = this.cache.get(source); + + if (cache) { + directionDesignToken.unsubscribe(cache); + } + } + + private attach(source: FASTElement & HTMLElement) { + const subscriber = + this.cache.get(source) || + new DirectionalStyleSheetBehaviorSubscription(this.ltr, this.rtl, source); + + const value = directionDesignToken.getValueFor(source); + directionDesignToken.subscribe(subscriber); + subscriber.attach(value); + + this.cache.set(source, subscriber); + } +} + +/** + * Subscription for {@link DirectionalStyleSheetBehavior} + */ +class DirectionalStyleSheetBehaviorSubscription implements Subscriber { + private attached: ElementStyles | null = null; + + constructor( + private ltr: ElementStyles | null, + private rtl: ElementStyles | null, + private source: HTMLElement & FASTElement + ) {} + + public handleChange({ + target, + token, + }: DesignTokenChangeRecord) { + this.attach(token.getValueFor(target)); + } + + public attach(direction: Direction) { + if (this.attached !== this[direction]) { + if (this.attached !== null) { + this.source.$fastController.removeStyles(this.attached); + } + this.attached = this[direction]; + if (this.attached !== null) { + this.source.$fastController.addStyles(this.attached); + } + } + } +} diff --git a/packages/components/src/styles/index.ts b/packages/components/src/styles/index.ts index 516debab..7cd82aa3 100644 --- a/packages/components/src/styles/index.ts +++ b/packages/components/src/styles/index.ts @@ -1,6 +1,8 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -export * from './elevation'; -export * from './patterns/index'; -export * from './size'; +export * from "./elevation.js"; +export * from "./patterns/index.js"; +export * from "./size.js"; +export * from "./direction.js"; + diff --git a/packages/components/src/styles/patterns/button.ts b/packages/components/src/styles/patterns/button.ts new file mode 100644 index 00000000..6ce5c881 --- /dev/null +++ b/packages/components/src/styles/patterns/button.ts @@ -0,0 +1,575 @@ + +import { css } from '@microsoft/fast-element'; +import { + display, + focusVisible, + forcedColorsStylesheetBehavior +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; +import { + accentFillActive, + accentFillFocus, + accentFillHover, + accentFillRest, + accentForegroundActive, + accentForegroundHover, + accentForegroundRest, + bodyFont, + controlCornerRadius, + density, + designUnit, + errorFillActive, + errorFillFocus, + errorFillHover, + errorFillRest, + errorForegroundActive, + focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + neutralFillActive, + neutralFillHover, + neutralFillRest, + neutralFillStealthActive, + neutralFillStealthHover, + neutralFillStrongActive, + neutralFillStrongFocus, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight +} from '../../design-tokens.js'; +import { heightNumber } from '../size.js'; + + // 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 + */ + export const BaseButtonStyles = css` + ${display('inline-flex')} :host { + font-family: ${bodyFont}; + outline: none; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + height: calc(${heightNumber} * 1px); + min-width: calc(${heightNumber} * 1px); + background-color: ${neutralFillRest}; + color: ${neutralForegroundRest}; + border-radius: calc(${controlCornerRadius} * 1px); + fill: currentcolor; + cursor: pointer; + margin: calc((${focusStrokeWidth} + 2) * 1px); + } + + .control { + background: transparent; + height: inherit; + flex-grow: 1; + box-sizing: border-box; + display: inline-flex; + justify-content: center; + align-items: center; + padding: 0 calc((10 + (${designUnit} * 2 * ${density})) * 1px); + white-space: nowrap; + outline: none; + text-decoration: none; + border: calc(${strokeWidth} * 1px) solid transparent; + color: inherit; + border-radius: inherit; + fill: inherit; + cursor: inherit; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } + + :host(:hover) { + background-color: ${neutralFillHover}; + } + + :host(:active) { + background-color: ${neutralFillActive}; + } + + :host([aria-pressed='true']) { + box-shadow: inset 0px 0px 2px 2px ${neutralFillStrongActive}; + } + + :host([minimal]) { + --density: -4; + } + + :host([minimal]) .control { + padding: 1px; + } + + /* prettier-ignore */ + .control:${focusVisible} { + outline: calc(${focusStrokeWidth} * 1px) solid ${neutralFillStrongFocus}; + outline-offset: 2px; + -moz-outline-radius: 0px; + } + + .control::-moz-focus-inner { + border: 0; + } + + .start, + .end { + display: flex; + } + + .control.icon-only { + padding: 0; + line-height: 0; + } + + ::slotted(svg) { + ${ + /* Glyph size and margin-left is temporary - + replace when adaptive typography is figured out */ '' + } width: 16px; + height: 16px; + pointer-events: none; + } + + .start { + margin-inline-end: 11px; + } + + .end { + margin-inline-start: 11px; + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host .control { + background-color: ${SystemColors.ButtonFace}; + border-color: ${SystemColors.ButtonText}; + color: ${SystemColors.ButtonText}; + fill: currentColor; + } + + :host(:hover) .control { + forced-color-adjust: none; + background-color: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + /* prettier-ignore */ + .control:${focusVisible} { + forced-color-adjust: none; + background-color: ${SystemColors.Highlight}; + outline-color: ${SystemColors.ButtonText}; + color: ${SystemColors.HighlightText}; + } + + .control:hover, + :host([appearance='outline']) .control:hover { + border-color: ${SystemColors.ButtonText}; + } + + :host([href]) .control { + border-color: ${SystemColors.LinkText}; + color: ${SystemColors.LinkText}; + } + + :host([href]) .control:hover, + :host([href]) .control:${focusVisible} { + forced-color-adjust: none; + background: ${SystemColors.ButtonFace}; + outline-color: ${SystemColors.LinkText}; + color: ${SystemColors.LinkText}; + fill: currentColor; + } + `) + ); + + /** + * @internal + */ + export const AccentButtonStyles = css` + :host([appearance='accent']) { + background: ${accentFillRest}; + color: ${foregroundOnAccentRest}; + } + + :host([appearance='accent']:hover) { + background: ${accentFillHover}; + color: ${foregroundOnAccentHover}; + } + + :host([appearance='accent'][aria-pressed='true']) { + box-shadow: inset 0px 0px 2px 2px ${accentForegroundActive}; + } + + :host([appearance='accent']:active) .control:active { + background: ${accentFillActive}; + color: ${foregroundOnAccentActive}; + } + + :host([appearance="accent"]) .control:${focusVisible} { + outline-color: ${accentFillFocus}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host([appearance='accent']) .control { + forced-color-adjust: none; + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host([appearance='accent']) .control:hover, + :host([appearance='accent']:active) .control:active { + background: ${SystemColors.HighlightText}; + border-color: ${SystemColors.Highlight}; + color: ${SystemColors.Highlight}; + } + + :host([appearance="accent"]) .control:${focusVisible} { + outline-color: ${SystemColors.Highlight}; + } + + :host([appearance='accent'][href]) .control { + background: ${SystemColors.LinkText}; + color: ${SystemColors.HighlightText}; + } + + :host([appearance='accent'][href]) .control:hover { + background: ${SystemColors.ButtonFace}; + border-color: ${SystemColors.LinkText}; + box-shadow: none; + color: ${SystemColors.LinkText}; + fill: currentColor; + } + + :host([appearance="accent"][href]) .control:${focusVisible} { + outline-color: ${SystemColors.HighlightText}; + } + `) + ); + + /** + * @internal + */ + export const ErrorButtonStyles = css` + :host([appearance='error']) { + background: ${errorFillRest}; + color: ${foregroundOnAccentRest}; + } + + :host([appearance='error']:hover) { + background: ${errorFillHover}; + color: ${foregroundOnAccentHover}; + } + + :host([appearance='error'][aria-pressed='true']) { + box-shadow: inset 0px 0px 2px 2px ${errorForegroundActive}; + } + + :host([appearance='error']:active) .control:active { + background: ${errorFillActive}; + color: ${foregroundOnAccentActive}; + } + + :host([appearance="error"]) .control:${focusVisible} { + outline-color: ${errorFillFocus}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host([appearance='error']) .control { + forced-color-adjust: none; + background: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + } + + :host([appearance='error']) .control:hover, + :host([appearance='error']:active) .control:active { + background: ${SystemColors.HighlightText}; + border-color: ${SystemColors.Highlight}; + color: ${SystemColors.Highlight}; + } + + :host([appearance="error"]) .control:${focusVisible} { + outline-color: ${SystemColors.Highlight}; + } + + :host([appearance='error'][href]) .control { + background: ${SystemColors.LinkText}; + color: ${SystemColors.HighlightText}; + } + + :host([appearance='error'][href]) .control:hover { + background: ${SystemColors.ButtonFace}; + border-color: ${SystemColors.LinkText}; + box-shadow: none; + color: ${SystemColors.LinkText}; + fill: currentColor; + } + + :host([appearance="error"][href]) .control:${focusVisible} { + outline-color: ${SystemColors.HighlightText}; + } + `) + ); + /** + * @internal + */ +export const HypertextStyles = css` +:host([appearance="hypertext"]) { + font-size: inherit; + line-height: inherit; + height: auto; + min-width: 0; + background: transparent; +} + +:host([appearance="hypertext"]) .control { + display: inline; + padding: 0; + border: none; + box-shadow: none; + border-radius: 0; + line-height: 1; +} + +:host a.control:not(:link) { + background-color: transparent; + cursor: default; +} +:host([appearance="hypertext"]) .control:link, +:host([appearance="hypertext"]) .control:visited { + background: transparent; + color: ${accentForegroundRest}; + border-bottom: calc(${strokeWidth} * 1px) solid ${accentForegroundRest}; +} + +:host([appearance="hypertext"]:hover), +:host([appearance="hypertext"]) .control:hover { + background: transparent; + border-bottom-color: ${accentForegroundHover}; +} + +:host([appearance="hypertext"]:active), +:host([appearance="hypertext"]) .control:active { + background: transparent; + border-bottom-color: ${accentForegroundActive}; +} + +:host([appearance="hypertext"]) .control:${focusVisible} { + border-bottom: calc(${focusStrokeWidth} * 1px) solid ${accentFillFocus}; + margin-bottom: calc(calc(${strokeWidth} - ${focusStrokeWidth}) * 1px); +} +`.withBehaviors( +forcedColorsStylesheetBehavior( + css` + :host([appearance="hypertext"]:hover) { + background-color: ${SystemColors.ButtonFace}; + color: ${SystemColors.ButtonText}; + } + :host([appearance="hypertext"][href]) .control:hover, + :host([appearance="hypertext"][href]) .control:active, + :host([appearance="hypertext"][href]) .control:${focusVisible} { + color: ${SystemColors.LinkText}; + border-bottom-color: ${SystemColors.LinkText}; + box-shadow: none; + } + ` +) +); + + /** + * @internal + */ + export const LightweightButtonStyles = css` + :host([appearance='lightweight']) { + background: transparent; + color: ${accentForegroundRest}; + } + + :host([appearance='lightweight']) .control { + padding: 0; + height: initial; + border: none; + box-shadow: none; + border-radius: 0; + } + + :host([appearance='lightweight']:hover) { + background: transparent; + color: ${accentForegroundHover}; + } + + :host([appearance='lightweight']:active) { + background: transparent; + color: ${accentForegroundActive}; + } + + :host([appearance='lightweight']) .content { + position: relative; + } + + :host([appearance='lightweight']) .content::before { + content: ''; + display: block; + height: calc(${strokeWidth} * 1px); + position: absolute; + top: calc(1em + 4px); + width: 100%; + } + + :host([appearance='lightweight']:hover) .content::before { + background: ${accentForegroundHover}; + } + + :host([appearance='lightweight']:active) .content::before { + background: ${accentForegroundActive}; + } + + :host([appearance="lightweight"]) .control:${focusVisible} { + outline-color: transparent; + } + + :host([appearance="lightweight"]) .control:${focusVisible} .content::before { + background: ${neutralForegroundRest}; + height: calc(${focusStrokeWidth} * 1px); + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host([appearance="lightweight"]) .control:hover, + :host([appearance="lightweight"]) .control:${focusVisible} { + forced-color-adjust: none; + background: ${SystemColors.ButtonFace}; + color: ${SystemColors.Highlight}; + } + :host([appearance="lightweight"]) .control:hover .content::before, + :host([appearance="lightweight"]) .control:${focusVisible} .content::before { + background: ${SystemColors.Highlight}; + } + + :host([appearance="lightweight"][href]) .control:hover, + :host([appearance="lightweight"][href]) .control:${focusVisible} { + background: ${SystemColors.ButtonFace}; + box-shadow: none; + color: ${SystemColors.LinkText}; + } + + :host([appearance="lightweight"][href]) .control:hover .content::before, + :host([appearance="lightweight"][href]) .control:${focusVisible} .content::before { + background: ${SystemColors.LinkText}; + } + `) + ); + + /** + * @internal + */ + export const OutlineButtonStyles = css` + :host([appearance='outline']) { + background: transparent; + border-color: ${accentFillRest}; + } + + :host([appearance='outline']:hover) { + border-color: ${accentFillHover}; + } + + :host([appearance='outline']:active) { + border-color: ${accentFillActive}; + } + + :host([appearance='outline']) .control { + border-color: inherit; + } + + :host([appearance="outline"]) .control:${focusVisible} { + outline-color: ${accentFillFocus}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host([appearance='outline']) .control { + border-color: ${SystemColors.ButtonText}; + } + :host([appearance="outline"]) .control:${focusVisible} { + forced-color-adjust: none; + background-color: ${SystemColors.Highlight}; + outline-color: ${SystemColors.ButtonText}; + color: ${SystemColors.HighlightText}; + fill: currentColor; + } + :host([appearance='outline'][href]) .control { + background: ${SystemColors.ButtonFace}; + border-color: ${SystemColors.LinkText}; + color: ${SystemColors.LinkText}; + fill: currentColor; + } + :host([appearance="outline"][href]) .control:hover, + :host([appearance="outline"][href]) .control:${focusVisible} { + forced-color-adjust: none; + outline-color: ${SystemColors.LinkText}; + } + `) + ); + + /** + * @internal + */ + export const StealthButtonStyles = css` + :host([appearance='stealth']) { + background: transparent; + } + + :host([appearance='stealth']:hover) { + background: ${neutralFillStealthHover}; + } + + :host([appearance='stealth']:active) { + background: ${neutralFillStealthActive}; + } + + :host([appearance='stealth']) .control:${focusVisible} { + outline-color: ${accentFillFocus}; + } + `.withBehaviors( + forcedColorsStylesheetBehavior(css` + :host([appearance='stealth']), + :host([appearance='stealth']) .control { + forced-color-adjust: none; + background: ${SystemColors.ButtonFace}; + border-color: transparent; + color: ${SystemColors.ButtonText}; + fill: currentColor; + } + + :host([appearance='stealth']:hover) .control { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + fill: currentColor; + } + + :host([appearance="stealth"]:${focusVisible}) .control { + outline-color: ${SystemColors.Highlight}; + color: ${SystemColors.HighlightText}; + fill: currentColor; + } + + :host([appearance='stealth'][href]) .control { + color: ${SystemColors.LinkText}; + } + + :host([appearance="stealth"][href]:hover) .control, + :host([appearance="stealth"][href]:${focusVisible}) .control { + background: ${SystemColors.LinkText}; + border-color: ${SystemColors.LinkText}; + color: ${SystemColors.HighlightText}; + fill: currentColor; + } + + :host([appearance="stealth"][href]:${focusVisible}) .control { + forced-color-adjust: none; + box-shadow: 0 0 0 1px ${SystemColors.LinkText}; + } + `) + ); \ No newline at end of file diff --git a/packages/components/src/styles/patterns/field.ts b/packages/components/src/styles/patterns/field.ts index b3070f49..3cc6969d 100644 --- a/packages/components/src/styles/patterns/field.ts +++ b/packages/components/src/styles/patterns/field.ts @@ -2,6 +2,14 @@ // Copyright (c) Microsoft Corporation. // Distributed under the terms of the Modified BSD License. +import { css } from '@microsoft/fast-element'; +import { + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior +} from '@microsoft/fast-foundation'; +import { SystemColors } from '@microsoft/fast-web-utilities'; import { accentFillFocus, bodyFont, @@ -21,15 +29,7 @@ import { strokeWidth, typeRampBaseFontSize, typeRampBaseLineHeight -} from '@microsoft/fast-components'; -import { css } from '@microsoft/fast-element'; -import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; +} from '../../design-tokens.js'; import { heightNumber } from '../size'; export const BaseFieldStyles = css` diff --git a/packages/components/src/styles/patterns/index.ts b/packages/components/src/styles/patterns/index.ts index edeafbef..1eeddb49 100644 --- a/packages/components/src/styles/patterns/index.ts +++ b/packages/components/src/styles/patterns/index.ts @@ -1,4 +1,5 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -export * from './field'; +export * from './button.js'; +export * from './field.js'; diff --git a/packages/components/src/switch/index.ts b/packages/components/src/switch/index.ts index a108283d..456b55a5 100644 --- a/packages/components/src/switch/index.ts +++ b/packages/components/src/switch/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - Switch, - SwitchOptions, - switchTemplate as template -} from '@microsoft/fast-foundation'; -import { switchStyles as styles } from './switch.styles'; + Switch, + SwitchOptions, + switchTemplate as template, +} from "@microsoft/fast-foundation"; +import { switchStyles as styles } from "./switch.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Switch} registration for configuring the component with a DesignSystem. @@ -18,12 +15,12 @@ import { switchStyles as styles } from './switch.styles'; * Generates HTML Element: `` */ export const jpSwitch = Switch.compose({ - baseName: 'switch', - template, - styles, - switch: /* html */ ` - - ` + baseName: 'switch', + template, + styles, + switch: /* html */ ` + + `, }); /** diff --git a/packages/components/src/switch/switch.styles.ts b/packages/components/src/switch/switch.styles.ts index 00a1660c..568c80b0 100644 --- a/packages/components/src/switch/switch.styles.ts +++ b/packages/components/src/switch/switch.styles.ts @@ -1,281 +1,271 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - SwitchOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + SwitchOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - accentFillActive, - accentFillFocus, - accentFillHover, - accentFillRest, - bodyFont, - controlCornerRadius, - designUnit, - DirectionalStyleSheetBehavior, - disabledOpacity, - focusStrokeWidth, - foregroundOnAccentActive, - foregroundOnAccentHover, - foregroundOnAccentRest, - neutralFillInputActive, - neutralFillInputHover, - neutralFillInputRest, - neutralForegroundRest, - neutralStrokeActive, - neutralStrokeHover, - neutralStrokeRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; + accentFillActive, + accentFillFocus, + accentFillHover, + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + fillColor, + focusStrokeOuter, + focusStrokeWidth, + foregroundOnAccentActive, + foregroundOnAccentHover, + foregroundOnAccentRest, + neutralFillInputActive, + neutralFillInputHover, + neutralFillInputRest, + neutralForegroundRest, + neutralStrokeActive, + neutralStrokeHover, + neutralStrokeRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { DirectionalStyleSheetBehavior, heightNumber } from "../styles/index.js"; /** * Styles for Switch * @public */ -export const switchStyles: FoundationElementTemplate< - ElementStyles, - SwitchOptions -> = (context, definition) => - css` - :host([hidden]) { - display: none; - } +export const switchStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + :host([hidden]) { + display: none; + } - ${display('inline-flex')} :host { - align-items: center; - outline: none; - font-family: ${bodyFont}; - margin: calc(${designUnit} * 1px) 0; - ${ - /* - * Chromium likes to select label text or the default slot when - * the checkbox is clicked. Maybe there is a better solution here? - */ '' - } user-select: none; - } + ${display("inline-flex")} :host { + align-items: center; + outline: none; + font-family: ${bodyFont}; + margin: calc(${designUnit} * 1px) 0; + ${ + /* + * Chromium likes to select label text or the default slot when + * the checkbox is clicked. Maybe there is a better solution here? + */ "" + } user-select: none; + } - :host([disabled]) { - opacity: ${disabledOpacity}; - } + :host([disabled]) { + opacity: ${disabledOpacity}; + } - :host([disabled]) .label, - :host([readonly]) .label, - :host([readonly]) .switch, - :host([disabled]) .switch { - cursor: ${disabledCursor}; - } + :host([disabled]) .label, + :host([readonly]) .label, + :host([readonly]) .switch, + :host([disabled]) .switch { + cursor: ${disabledCursor}; + } - .switch { - position: relative; - outline: none; - box-sizing: border-box; - width: calc(${heightNumber} * 1px); - height: calc((${heightNumber} / 2 + ${designUnit}) * 1px); - background: ${neutralFillInputRest}; - border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; - } + .switch { + position: relative; + outline: none; + box-sizing: border-box; + width: calc(${heightNumber} * 1px); + height: calc((${heightNumber} / 2 + ${designUnit}) * 1px); + background: ${neutralFillInputRest}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${neutralStrokeRest}; + } - .switch:hover { - background: ${neutralFillInputHover}; - border-color: ${neutralStrokeHover}; - cursor: pointer; - } + .switch:hover { + background: ${neutralFillInputHover}; + border-color: ${neutralStrokeHover}; + cursor: pointer; + } - host([disabled]) .switch:hover, - host([readonly]) .switch:hover { - background: ${neutralFillInputHover}; - border-color: ${neutralStrokeHover}; - cursor: ${disabledCursor}; - } + host([disabled]) .switch:hover, + host([readonly]) .switch:hover { + background: ${neutralFillInputHover}; + border-color: ${neutralStrokeHover}; + cursor: ${disabledCursor}; + } - :host(:not([disabled])) .switch:active { - background: ${neutralFillInputActive}; - border-color: ${neutralStrokeActive}; - } + :host(:not([disabled])) .switch:active { + background: ${neutralFillInputActive}; + border-color: ${neutralStrokeActive}; + } - :host(:${focusVisible}) .switch { - outline-offset: 2px; + :host(:${focusVisible}) .switch { + outline-offset: 2px; outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; - } - - .checked-indicator { - position: absolute; - top: 5px; - bottom: 5px; - background: ${neutralForegroundRest}; - border-radius: calc(${controlCornerRadius} * 1px); - transition: all 0.2s ease-in-out; - } - - .status-message { - color: ${neutralForegroundRest}; - cursor: pointer; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - } - - :host([disabled]) .status-message, - :host([readonly]) .status-message { - cursor: ${disabledCursor}; - } - - .label { - color: ${neutralForegroundRest}; + } - ${ - /* Need to discuss with Brian how HorizontalSpacingNumber can work. https://github.com/microsoft/fast/issues/2766 */ '' - } margin-inline-end: calc(${designUnit} * 2px + 2px); - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - cursor: pointer; - } + .checked-indicator { + position: absolute; + top: 5px; + bottom: 5px; + background: ${neutralForegroundRest}; + border-radius: calc(${controlCornerRadius} * 1px); + transition: all 0.2s ease-in-out; + } - .label__hidden { - display: none; - visibility: hidden; - } + .status-message { + color: ${neutralForegroundRest}; + cursor: pointer; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + } - ::slotted([slot='checked-message']), - ::slotted([slot='unchecked-message']) { - margin-inline-start: calc(${designUnit} * 2px + 2px); - } + :host([disabled]) .status-message, + :host([readonly]) .status-message { + cursor: ${disabledCursor}; + } - :host([aria-checked='true']) .checked-indicator { - background: ${foregroundOnAccentRest}; - } + .label { + color: ${neutralForegroundRest}; + margin-inline-end: calc(${designUnit} * 2px + 2px); + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + cursor: pointer; + } - :host([aria-checked='true']) .switch { - background: ${accentFillRest}; - border-color: ${accentFillRest}; - } + .label__hidden { + display: none; + visibility: hidden; + } - :host([aria-checked='true']:not([disabled])) .switch:hover { - background: ${accentFillHover}; - border-color: ${accentFillHover}; - } + ::slotted([slot="checked-message"]), + ::slotted([slot="unchecked-message"]) { + margin-inline-start: calc(${designUnit} * 2px + 2px); + } - :host([aria-checked='true']:not([disabled])) - .switch:hover - .checked-indicator { - background: ${foregroundOnAccentHover}; - } + :host([aria-checked="true"]) .checked-indicator { + background: ${foregroundOnAccentRest}; + } - :host([aria-checked='true']:not([disabled])) .switch:active { - background: ${accentFillActive}; - border-color: ${accentFillActive}; - } + :host([aria-checked="true"]) .switch { + background: ${accentFillRest}; + border-color: ${accentFillRest}; + } - :host([aria-checked='true']:not([disabled])) - .switch:active - .checked-indicator { - background: ${foregroundOnAccentActive}; - } + :host([aria-checked="true"]:not([disabled])) .switch:hover { + background: ${accentFillHover}; + border-color: ${accentFillHover}; + } - :host([aria-checked="true"]:${focusVisible}:not([disabled])) .switch { - outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; - } + :host([aria-checked="true"]:not([disabled])) .switch:hover .checked-indicator { + background: ${foregroundOnAccentHover}; + } - .unchecked-message { - display: block; - } + :host([aria-checked="true"]:not([disabled])) .switch:active { + background: ${accentFillActive}; + border-color: ${accentFillActive}; + } - .checked-message { - display: none; - } + :host([aria-checked="true"]:not([disabled])) .switch:active .checked-indicator { + background: ${foregroundOnAccentActive}; + } - :host([aria-checked='true']) .unchecked-message { - display: none; - } + :host([aria-checked="true"]:${focusVisible}:not([disabled])) .switch { + outline: solid calc(${focusStrokeWidth} * 1px) ${accentFillFocus}; + } - :host([aria-checked='true']) .checked-message { - display: block; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - .checked-indicator, - :host(:not([disabled])) .switch:active .checked-indicator { - forced-color-adjust: none; - background: ${SystemColors.FieldText}; - } - .switch { - forced-color-adjust: none; - background: ${SystemColors.Field}; - border-color: ${SystemColors.FieldText}; - } - :host(:not([disabled])) .switch:hover { - background: ${SystemColors.HighlightText}; - border-color: ${SystemColors.Highlight}; - } - :host([aria-checked='true']) .switch { - background: ${SystemColors.Highlight}; - border-color: ${SystemColors.Highlight}; - } - :host([aria-checked='true']:not([disabled])) .switch:hover, - :host(:not([disabled])) .switch:active { - background: ${SystemColors.HighlightText}; - border-color: ${SystemColors.Highlight}; - } - :host([aria-checked='true']) .checked-indicator { - background: ${SystemColors.HighlightText}; - } - :host([aria-checked='true']:not([disabled])) - .switch:hover - .checked-indicator { - background: ${SystemColors.Highlight}; - } - :host([disabled]) { - opacity: 1; - } - :host(:${focusVisible}) .switch { - border-color: ${SystemColors.Highlight}; - outline-offset: 2px; - outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; - } - :host([aria-checked="true"]:${focusVisible}:not([disabled])) .switch { - outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; - } - :host([disabled]) .checked-indicator { - background: ${SystemColors.GrayText}; - } - :host([disabled]) .switch { - background: ${SystemColors.Field}; - border-color: ${SystemColors.GrayText}; - } - `), - new DirectionalStyleSheetBehavior( - css` - .checked-indicator { - left: 5px; - right: calc(((${heightNumber} / 2) + 1) * 1px); + .unchecked-message { + display: block; } - :host([aria-checked='true']) .checked-indicator { - left: calc(((${heightNumber} / 2) + 1) * 1px); - right: 5px; + .checked-message { + display: none; } - `, - css` - .checked-indicator { - right: 5px; - left: calc(((${heightNumber} / 2) + 1) * 1px); + + :host([aria-checked="true"]) .unchecked-message { + display: none; } - :host([aria-checked='true']) .checked-indicator { - right: calc(((${heightNumber} / 2) + 1) * 1px); - left: 5px; + :host([aria-checked="true"]) .checked-message { + display: block; } - ` - ) - ); + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + .checked-indicator, + :host(:not([disabled])) .switch:active .checked-indicator { + forced-color-adjust: none; + background: ${SystemColors.FieldText}; + } + .switch { + forced-color-adjust: none; + background: ${SystemColors.Field}; + border-color: ${SystemColors.FieldText}; + } + :host(:not([disabled])) .switch:hover { + background: ${SystemColors.HighlightText}; + border-color: ${SystemColors.Highlight}; + } + :host([aria-checked="true"]) .switch { + background: ${SystemColors.Highlight}; + border-color: ${SystemColors.Highlight}; + } + :host([aria-checked="true"]:not([disabled])) .switch:hover, + :host(:not([disabled])) .switch:active { + background: ${SystemColors.HighlightText}; + border-color: ${SystemColors.Highlight}; + } + :host([aria-checked="true"]) .checked-indicator { + background: ${SystemColors.HighlightText}; + } + :host([aria-checked="true"]:not([disabled])) .switch:hover .checked-indicator { + background: ${SystemColors.Highlight}; + } + :host([disabled]) { + opacity: 1; + } + :host(:${focusVisible}) .switch { + border-color: ${SystemColors.Highlight}; + outline-offset: 2px; + outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; + } + :host([aria-checked="true"]:${focusVisible}:not([disabled])) .switch { + outline: solid calc(${focusStrokeWidth} * 1px) ${SystemColors.FieldText}; + } + :host([disabled]) .checked-indicator { + background: ${SystemColors.GrayText}; + } + :host([disabled]) .switch { + background: ${SystemColors.Field}; + border-color: ${SystemColors.GrayText}; + } + ` + ), + new DirectionalStyleSheetBehavior( + css` + .checked-indicator { + left: 5px; + right: calc(((${heightNumber} / 2) + 1) * 1px); + } + + :host([aria-checked="true"]) .checked-indicator { + left: calc(((${heightNumber} / 2) + 1) * 1px); + right: 5px; + } + `, + css` + .checked-indicator { + right: 5px; + left: calc(((${heightNumber} / 2) + 1) * 1px); + } + + :host([aria-checked="true"]) .checked-indicator { + right: calc(((${heightNumber} / 2) + 1) * 1px); + left: 5px; + } + ` + ) + ); diff --git a/packages/components/src/tab-panel/index.ts b/packages/components/src/tab-panel/index.ts index a476ba34..95003c94 100644 --- a/packages/components/src/tab-panel/index.ts +++ b/packages/components/src/tab-panel/index.ts @@ -1,11 +1,5 @@ -// 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'; +import { TabPanel, tabPanelTemplate as template } from "@microsoft/fast-foundation"; +import { tabPanelStyles as styles } from "./tab-panel.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#TabPanel} registration for configuring the component with a DesignSystem. @@ -17,9 +11,9 @@ import { tabPanelStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpTabPanel = TabPanel.compose({ - baseName: 'tab-panel', - template, - styles + baseName: 'tab-panel', + template, + styles, }); /** diff --git a/packages/components/src/tab-panel/tab-panel.styles.ts b/packages/components/src/tab-panel/tab-panel.styles.ts new file mode 100644 index 00000000..0152c9ee --- /dev/null +++ b/packages/components/src/tab-panel/tab-panel.styles.ts @@ -0,0 +1,24 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; +import { + density, + designUnit, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; + +/** + * Styles for Tab Panel + * @public + */ +export const tabPanelStyles: FoundationElementTemplate = ( + context, + definition +) => css` + ${display("block")} :host { + box-sizing: border-box; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + padding: 0 calc((6 + (${designUnit} * 2 * ${density})) * 1px); + } +`; diff --git a/packages/components/src/tab/index.ts b/packages/components/src/tab/index.ts index c946ab05..297c59c5 100644 --- a/packages/components/src/tab/index.ts +++ b/packages/components/src/tab/index.ts @@ -14,9 +14,9 @@ import { tabStyles as styles } from './tab.styles'; * Generates HTML Element: `` */ export const jpTab = Tab.compose({ - baseName: 'tab', - template, - styles + baseName: 'tab', + template, + styles, }); /** diff --git a/packages/components/src/tab/tab.styles.ts b/packages/components/src/tab/tab.styles.ts index 318a3984..a45186bb 100644 --- a/packages/components/src/tab/tab.styles.ts +++ b/packages/components/src/tab/tab.styles.ts @@ -1,161 +1,162 @@ -// 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 { - accentFillFocus, - bodyFont, - controlCornerRadius, - designUnit, - disabledOpacity, - focusStrokeWidth, - neutralFillActive, - neutralFillHover, - neutralFillRest, - neutralFillStealthRest, - neutralForegroundHint, - neutralForegroundRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '@microsoft/fast-components'; -import { css, ElementStyles } from '@microsoft/fast-element'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; -import { heightNumber } from '../styles'; + accentFillFocus, + accentForegroundActive, + accentForegroundHover, + accentForegroundRest, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + focusStrokeOuter, + focusStrokeWidth, + neutralFillActive, + neutralFillHover, + neutralFillRest, + neutralFillStealthRest, + neutralForegroundHint, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/size.js"; /** * Styles for Tab * @public */ export const tabStyles: FoundationElementTemplate = ( - context, - definition + 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; + 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; + justify-content: center; + grid-row: 2; + cursor: pointer; } :host(:hover) { - color: ${neutralForegroundRest}; - fill: currentcolor; + color: ${neutralForegroundRest}; + fill: currentcolor; } :host(:active) { - color: ${neutralForegroundRest}; - fill: currentcolor; + color: ${neutralForegroundRest}; + fill: currentcolor; } :host([disabled]) { - cursor: ${disabledCursor}; - opacity: ${disabledOpacity}; + cursor: ${disabledCursor}; + opacity: ${disabledOpacity}; } :host([disabled]:hover) { - color: ${neutralForegroundHint}; - background: ${neutralFillStealthRest}; + color: ${neutralForegroundHint}; + background: ${neutralFillStealthRest}; } - :host([aria-selected='true']) { - background: ${neutralFillRest}; - color: ${neutralForegroundRest}; - fill: currentcolor; + :host([aria-selected="true"]) { + background: ${neutralFillRest}; + color: ${accentForegroundRest}; + fill: currentcolor; } - :host([aria-selected='true']:hover) { - background: ${neutralFillHover}; - color: ${neutralForegroundRest}; - fill: currentcolor; + :host([aria-selected="true"]:hover) { + background: ${neutralFillHover}; + color: ${accentForegroundHover}; + fill: currentcolor; } - :host([aria-selected='true']:active) { - background: ${neutralFillActive}; - color: ${neutralForegroundRest}; - fill: currentcolor; + :host([aria-selected="true"]:active) { + background: ${neutralFillActive}; + color: ${accentForegroundActive}; + fill: currentcolor; } :host(:${focusVisible}) { - outline: none; - border-color: ${accentFillFocus}; - box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) - ${accentFillFocus}; + outline: none; + border-color: ${accentFillFocus}; + box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) + ${accentFillFocus}; } :host(:focus) { - outline: none; + outline: none; } :host(.vertical) { - justify-content: end; - grid-column: 2; - border-bottom-left-radius: 0; + 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[aria-selected="true"]) { + z-index: 2; } :host(.vertical:hover) { - color: ${neutralForegroundRest}; + color: ${neutralForegroundRest}; } :host(.vertical:active) { - color: ${neutralForegroundRest}; + color: ${neutralForegroundRest}; } - :host(.vertical:hover[aria-selected='true']) { + :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}; - } - `) - ); +`.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 index 50322c2b..dcbfc362 100644 --- a/packages/components/src/tabs/index.ts +++ b/packages/components/src/tabs/index.ts @@ -14,13 +14,13 @@ import { tabsStyles as styles } from './tabs.styles'; * Generates HTML Element: `` */ export const jpTabs = Tabs.compose({ - baseName: 'tabs', - template, - styles + baseName: 'tabs', + template, + styles, }); -export * from '../tab'; -export * from '../tab-panel'; +export * from "../tab/index.js"; +export * from "../tab-panel/index.js"; /** * Base class for Tabs diff --git a/packages/components/src/tabs/tabs.styles.ts b/packages/components/src/tabs/tabs.styles.ts index c4fb2ec8..2fe3ae82 100644 --- a/packages/components/src/tabs/tabs.styles.ts +++ b/packages/components/src/tabs/tabs.styles.ts @@ -1,134 +1,134 @@ -// 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 { css, ElementStyles } from "@microsoft/fast-element"; import { - display, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - TabsOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; + display, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + TabsOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; import { - accentFillRest, - bodyFont, - controlCornerRadius, - designUnit, - neutralForegroundRest, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + neutralForegroundRest, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; /** * 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; - } +export const tabsStyles: FoundationElementTemplate = ( + 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; - } + .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; - } + .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; - } + .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; - } + .activeIndicatorTransition { + transition: transform 0.01s ease-in-out; + } - .tabpanel { - grid-row: 2; - grid-column-start: 1; - grid-column-end: 4; - position: relative; - } + .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"]) { + 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"]) .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"]) .tabpanel { + grid-column: 2; + grid-row-start: 1; + grid-row-end: 4; + } - :host([orientation='vertical']) .end { - grid-row: 3; - } + :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"]) .activeIndicator { + grid-column: 1; + grid-row: 1; + width: 4px; + height: 100%; + margin-inline-end: 10px; + align-self: center; + background: ${accentFillRest}; + margin-top: 0; + 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}; - } - `) - ); + :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/text-area/index.ts b/packages/components/src/text-area/index.ts index f97ee894..e2644f1d 100644 --- a/packages/components/src/text-area/index.ts +++ b/packages/components/src/text-area/index.ts @@ -1,12 +1,30 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { attr } from "@microsoft/fast-element"; import { - TextArea as FoundationTextArea, - textAreaTemplate as template -} from '@microsoft/fast-foundation'; -import { TextArea, TextAreaAppearance } from '@microsoft/fast-components'; -import { textAreaStyles as styles } from './text-area.styles'; + TextArea as FoundationTextArea, + textAreaTemplate as template, +} from "@microsoft/fast-foundation"; +import { textAreaStyles as styles } from "./text-area.styles.js"; + +/** + * Text area appearances + * @public + */ +export type TextAreaAppearance = "filled" | "outline"; + +/** + * @internal + */ +export class TextArea extends FoundationTextArea { + /** + * The appearance of the element. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance: TextAreaAppearance = "outline"; +} /** * A function that returns a {@link @microsoft/fast-foundation#TextArea} registration for configuring the component with a DesignSystem. @@ -20,13 +38,13 @@ import { textAreaStyles as styles } from './text-area.styles'; * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} */ export const jpTextArea = TextArea.compose({ - baseName: 'text-area', - baseClass: FoundationTextArea, - template, - styles, - shadowOptions: { - delegatesFocus: true - } + baseName: 'text-area', + baseClass: FoundationTextArea, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); -export { TextArea, TextAreaAppearance, styles as textAreaStyles }; +export { styles as textAreaStyles }; diff --git a/packages/components/src/text-area/text-area.styles.ts b/packages/components/src/text-area/text-area.styles.ts index 6df39686..9c2f95d4 100644 --- a/packages/components/src/text-area/text-area.styles.ts +++ b/packages/components/src/text-area/text-area.styles.ts @@ -1,142 +1,152 @@ -// 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 { - accentFillFocus, - bodyFont, - controlCornerRadius, - designUnit, - disabledOpacity, - focusStrokeWidth, - neutralFillHover, - neutralFillInputActive, - neutralFillInputHover, - neutralFillInputRest, - neutralFillRest, - neutralFillStrongActive, - neutralFillStrongHover, - neutralFillStrongRest, - neutralForegroundRest, - neutralStrokeRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { css, ElementStyles } from '@microsoft/fast-element'; + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, +} from "@microsoft/fast-foundation"; import { - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate -} from '@microsoft/fast-foundation'; -import { heightNumber } from '../styles/index'; + accentFillActive, + accentFillFocus, + accentFillHover, + accentFillRest, + bodyFont, + controlCornerRadius, + designUnit, + disabledOpacity, + focusStrokeOuter, + focusStrokeWidth, + neutralFillHover, + neutralFillInputActive, + neutralFillInputHover, + neutralFillInputRest, + neutralFillRest, + neutralFillStrongActive, + neutralFillStrongHover, + neutralFillStrongRest, + neutralForegroundRest, + neutralStrokeRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { heightNumber } from "../styles/index.js"; /** * Styles for Text Area * @public */ export const textAreaStyles: FoundationElementTemplate = ( - context, - definition + context, + definition ) => - css` - ${display('inline-block')} :host { - font-family: ${bodyFont}; - outline: none; - user-select: none; + css` + ${display("inline-block")} :host { + font-family: ${bodyFont}; + outline: none; + user-select: none; } .control { - box-sizing: border-box; - position: relative; - color: ${neutralForegroundRest}; - background: ${neutralFillInputRest}; - border-radius: calc(${controlCornerRadius} * 1px); - border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; - height: calc(${heightNumber} * 2px); - font: inherit; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - padding: calc(${designUnit} * 2px + 1px); - width: 100%; - resize: none; + box-sizing: border-box; + position: relative; + color: ${neutralForegroundRest}; + background: ${neutralFillInputRest}; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${neutralFillStrongRest}; + height: calc(${heightNumber} * 2px); + font: inherit; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + padding: calc(${designUnit} * 2px + 1px); + width: 100%; + resize: none; } .control:hover:enabled { - background: ${neutralFillInputHover}; - border-color: ${neutralFillStrongHover}; + background: ${neutralFillInputHover}; + border-color: ${neutralFillStrongHover}; } .control:active:enabled { - background: ${neutralFillInputActive}; - border-color: ${neutralFillStrongActive}; + background: ${neutralFillInputActive}; + border-color: ${neutralFillStrongActive}; } .control:hover, .control:${focusVisible}, .control:disabled, .control:active { - outline: none; + outline: none; } :host(:focus-within) .control { - border-color: ${accentFillFocus}; - box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) + border-color: ${accentFillFocus}; + box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) ${accentFillFocus}; } - :host([appearance='filled']) .control { - background: ${neutralFillRest}; + :host([appearance="filled"]) .control { + background: ${neutralFillRest}; } - :host([appearance='filled']:hover:not([disabled])) .control { - background: ${neutralFillHover}; + :host([appearance="filled"]:hover:not([disabled])) .control { + background: ${neutralFillHover}; } - :host([resize='both']) .control { - resize: both; + :host([resize="both"]) .control { + resize: both; } - :host([resize='horizontal']) .control { - resize: horizontal; + :host([resize="horizontal"]) .control { + resize: horizontal; } - :host([resize='vertical']) .control { - resize: vertical; + :host([resize="vertical"]) .control { + resize: vertical; } .label { - display: block; - color: ${neutralForegroundRest}; - cursor: pointer; - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - margin-bottom: 4px; + display: block; + color: ${neutralForegroundRest}; + cursor: pointer; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + margin-bottom: 4px; } .label__hidden { - display: none; - visibility: hidden; + display: none; + visibility: hidden; } :host([disabled]) .label, :host([readonly]) .label, :host([readonly]) .control, :host([disabled]) .control { - cursor: ${disabledCursor}; + cursor: ${disabledCursor}; } :host([disabled]) { - opacity: ${disabledOpacity}; + opacity: ${disabledOpacity}; } :host([disabled]) .control { - border-color: ${neutralStrokeRest}; - } - `.withBehaviors( - forcedColorsStylesheetBehavior(css` - :host([disabled]) { - opacity: 1; - } - `) - ); + border-color: ${neutralStrokeRest}; + } + + :host([cols]){ + width: initial; + } + + :host([rows]) .control { + height: initial; + } + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host([disabled]) { + opacity: 1; + } + ` + ) + ); diff --git a/packages/components/src/text-field/index.ts b/packages/components/src/text-field/index.ts index 2856f0d7..fe9bfada 100644 --- a/packages/components/src/text-field/index.ts +++ b/packages/components/src/text-field/index.ts @@ -1,18 +1,34 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - +import { attr } from "@microsoft/fast-element"; import { - TextField as FoundationTextField, - textFieldTemplate as template -} from '@microsoft/fast-foundation'; -import { TextField } from '@microsoft/fast-components'; -import { textFieldStyles as styles } from './text-field.styles'; + TextField as FoundationTextField, + textFieldTemplate as template, +} from "@microsoft/fast-foundation"; +import { textFieldStyles as styles } from "./text-field.styles.js"; -// TODO -// we need to add error/invalid +/** + * Text field appearances + * @public + */ +export type TextFieldAppearance = "filled" | "outline"; /** - * A function that returns a TextField registration for configuring the component with a DesignSystem. + * @internal + */ +export class TextField extends FoundationTextField { + /** + * The appearance of the element. + * + * @public + * @remarks + * HTML Attribute: appearance + */ + @attr + public appearance: TextFieldAppearance = "outline"; +} + +/** + * A function that returns a {@link @microsoft/fast-foundation#TextField} registration for configuring the component with a DesignSystem. + * Implements {@link @microsoft/fast-foundation#textFieldTemplate} * * * @public @@ -22,19 +38,13 @@ import { textFieldStyles as styles } from './text-field.styles'; * {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus | delegatesFocus} */ export const jpTextField = TextField.compose({ - baseName: 'text-field', - baseClass: FoundationTextField, - template, - styles, - shadowOptions: { - delegatesFocus: true - } + baseName: 'text-field', + baseClass: FoundationTextField, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); -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.styles.ts b/packages/components/src/text-field/text-field.styles.ts index 255e35f1..5720beee 100644 --- a/packages/components/src/text-field/text-field.styles.ts +++ b/packages/components/src/text-field/text-field.styles.ts @@ -4,8 +4,8 @@ import { css, ElementStyles } from '@microsoft/fast-element'; import { - FoundationElementTemplate, - TextFieldOptions + FoundationElementTemplate, + TextFieldOptions } from '@microsoft/fast-foundation'; import { BaseFieldStyles } from '../styles/index'; @@ -14,13 +14,13 @@ import { BaseFieldStyles } from '../styles/index'; * @public */ export const textFieldStyles: FoundationElementTemplate< - ElementStyles, - TextFieldOptions + ElementStyles, + TextFieldOptions > = (context, definition) => css` - ${BaseFieldStyles} + ${BaseFieldStyles} - .start, + .start, .end { - display: flex; - } + display: flex; + } `; diff --git a/packages/components/src/toolbar/index.ts b/packages/components/src/toolbar/index.ts index ff1a0226..7bd253f5 100644 --- a/packages/components/src/toolbar/index.ts +++ b/packages/components/src/toolbar/index.ts @@ -1,12 +1,32 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { Toolbar } from '@microsoft/fast-components'; import { - Toolbar as FoundationToolbar, - toolbarTemplate as template -} from '@microsoft/fast-foundation'; -import { toolbarStyles as styles } from './toolbar.styles'; + composedParent, + Toolbar as FoundationToolbar, + toolbarTemplate as template, +} from "@microsoft/fast-foundation"; +import { Swatch } from "../color/swatch.js"; +import { fillColor, neutralFillLayerRecipe } from "../design-tokens.js"; +import { toolbarStyles as styles } from "./toolbar.styles.js"; + +/** + * @internal + */ +export class Toolbar extends FoundationToolbar { + connectedCallback() { + super.connectedCallback(); + + const parent = composedParent(this); + + if (parent) { + fillColor.setValueFor( + this, + (target: HTMLElement): Swatch => + neutralFillLayerRecipe + .getValueFor(target) + .evaluate(target, fillColor.getValueFor(parent)) + ); + } + } +} /** * A function that returns a {@link @microsoft/fast-foundation#Toolbar} registration for configuring the component with a DesignSystem. @@ -19,13 +39,13 @@ import { toolbarStyles as styles } from './toolbar.styles'; * */ export const jpToolbar = Toolbar.compose({ - baseName: 'toolbar', - baseClass: FoundationToolbar, - template, - styles, - shadowOptions: { - delegatesFocus: true - } + baseName: 'toolbar', + baseClass: FoundationToolbar, + template, + styles, + shadowOptions: { + delegatesFocus: true, + }, }); -export { Toolbar, styles as toolbarStyles }; +export { styles as toolbarStyles }; diff --git a/packages/components/src/tooltip/index.ts b/packages/components/src/tooltip/index.ts index f64264c3..2d4f27a8 100644 --- a/packages/components/src/tooltip/index.ts +++ b/packages/components/src/tooltip/index.ts @@ -1,11 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { - tooltipTemplate as template, - Tooltip -} from '@microsoft/fast-foundation'; -import { tooltipStyles as styles } from '@microsoft/fast-components'; +import { tooltipTemplate as template, Tooltip } from "@microsoft/fast-foundation"; +import { tooltipStyles as styles } from "./tooltip.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#Tooltip} registration for configuring the component with a DesignSystem. @@ -17,9 +11,9 @@ import { tooltipStyles as styles } from '@microsoft/fast-components'; * Generates HTML Element: `` */ export const jpTooltip = Tooltip.compose({ - baseName: 'tooltip', - template, - styles + baseName: 'tooltip', + template, + styles, }); /** diff --git a/packages/components/src/tooltip/tooltip.styles.ts b/packages/components/src/tooltip/tooltip.styles.ts new file mode 100644 index 00000000..5bd0ccd6 --- /dev/null +++ b/packages/components/src/tooltip/tooltip.styles.ts @@ -0,0 +1,116 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { + AnchoredRegion, + ElementDefinitionContext, + forcedColorsStylesheetBehavior, + FoundationElementDefinition, +} from "@microsoft/fast-foundation"; +import { + bodyFont, + controlCornerRadius, + focusStrokeOuter, + neutralFillRest, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; + +/** + * Styles for Tooltip + * @public + */ +export const tooltipStyles: ( + context: ElementDefinitionContext, + definition: FoundationElementDefinition +) => ElementStyles = ( + context: ElementDefinitionContext, + definition: FoundationElementDefinition +) => { + const anchoredRegionTag = context.tagFor(AnchoredRegion); + return css` + :host { + contain: size; + overflow: visible; + height: 0; + width: 0; + } + + .tooltip { + box-sizing: border-box; + border-radius: calc(${controlCornerRadius} * 1px); + border: calc(${strokeWidth} * 1px) solid ${focusStrokeOuter}; + box-shadow: 0 0 0 1px ${focusStrokeOuter} inset; + background: ${neutralFillRest}; + color: ${neutralForegroundRest}; + padding: 4px; + height: fit-content; + width: fit-content; + font-family: ${bodyFont}; + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + white-space: nowrap; + /* TODO: a mechanism to manage z-index across components + https://github.com/microsoft/fast/issues/3813 */ + z-index: 10000; + } + + ${anchoredRegionTag} { + display: flex; + justify-content: center; + align-items: center; + overflow: visible; + flex-direction: row; + } + + ${anchoredRegionTag}.right, + ${anchoredRegionTag}.left { + flex-direction: column; + } + + ${anchoredRegionTag}.top .tooltip { + margin-bottom: 4px; + } + + ${anchoredRegionTag}.bottom .tooltip { + margin-top: 4px; + } + + ${anchoredRegionTag}.left .tooltip { + margin-right: 4px; + } + + ${anchoredRegionTag}.right .tooltip { + margin-left: 4px; + } + + ${anchoredRegionTag}.top.left .tooltip, + ${anchoredRegionTag}.top.right .tooltip { + margin-bottom: 0px; + } + + ${anchoredRegionTag}.bottom.left .tooltip, + ${anchoredRegionTag}.bottom.right .tooltip { + margin-top: 0px; + } + + ${anchoredRegionTag}.top.left .tooltip, + ${anchoredRegionTag}.bottom.left .tooltip { + margin-right: 0px; + } + + ${anchoredRegionTag}.top.right .tooltip, + ${anchoredRegionTag}.bottom.right .tooltip { + margin-left: 0px; + } + + `.withBehaviors( + forcedColorsStylesheetBehavior( + css` + :host([disabled]) { + opacity: 1; + } + ` + ) + ); +}; diff --git a/packages/components/src/tree-item/index.ts b/packages/components/src/tree-item/index.ts index dddecf74..b5d8b47f 100644 --- a/packages/components/src/tree-item/index.ts +++ b/packages/components/src/tree-item/index.ts @@ -1,12 +1,9 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - import { - treeItemTemplate as template, - TreeItem, - TreeItemOptions -} from '@microsoft/fast-foundation'; -import { treeItemStyles as styles } from './tree-item.styles'; + treeItemTemplate as template, + TreeItem, + TreeItemOptions, +} from "@microsoft/fast-foundation"; +import { treeItemStyles as styles } from "./tree-item.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#TreeItem} registration for configuring the component with a DesignSystem. @@ -19,10 +16,10 @@ import { treeItemStyles as styles } from './tree-item.styles'; * */ export const jpTreeItem = TreeItem.compose({ - baseName: 'tree-item', - template, - styles, - expandCollapseGlyph: /* html */ ` + baseName: 'tree-item', + template, + styles, + expandCollapseGlyph: /* html */ ` ({ d="M5.00001 12.3263C5.00124 12.5147 5.05566 12.699 5.15699 12.8578C5.25831 13.0167 5.40243 13.1437 5.57273 13.2242C5.74304 13.3047 5.9326 13.3354 6.11959 13.3128C6.30659 13.2902 6.4834 13.2152 6.62967 13.0965L10.8988 8.83532C11.0739 8.69473 11.2153 8.51658 11.3124 8.31402C11.4096 8.11146 11.46 7.88966 11.46 7.66499C11.46 7.44033 11.4096 7.21853 11.3124 7.01597C11.2153 6.81341 11.0739 6.63526 10.8988 6.49467L6.62967 2.22347C6.48274 2.10422 6.30501 2.02912 6.11712 2.00691C5.92923 1.9847 5.73889 2.01628 5.56823 2.09799C5.39757 2.17969 5.25358 2.30817 5.153 2.46849C5.05241 2.62882 4.99936 2.8144 5.00001 3.00369V12.3263Z" /> - ` + `, }); /** diff --git a/packages/components/src/tree-item/tree-item.styles.ts b/packages/components/src/tree-item/tree-item.styles.ts index 5066ea39..6aa31c59 100644 --- a/packages/components/src/tree-item/tree-item.styles.ts +++ b/packages/components/src/tree-item/tree-item.styles.ts @@ -1,78 +1,69 @@ -// Copyright (c) Jupyter Development Team. -// Copyright (c) Microsoft Corporation. -// Distributed under the terms of the Modified BSD License. - -import { css, cssPartial, ElementStyles } from '@microsoft/fast-element'; +import { css, cssPartial, ElementStyles } from "@microsoft/fast-element"; import { - DesignToken, - disabledCursor, - display, - focusVisible, - forcedColorsStylesheetBehavior, - FoundationElementTemplate, - TreeItem, - TreeItemOptions -} from '@microsoft/fast-foundation'; -import { SystemColors } from '@microsoft/fast-web-utilities'; -import type { Swatch } from '../color'; + DesignToken, + disabledCursor, + display, + focusVisible, + forcedColorsStylesheetBehavior, + FoundationElementTemplate, + TreeItem, + TreeItemOptions, +} from "@microsoft/fast-foundation"; +import { SystemColors } from "@microsoft/fast-web-utilities"; +import { Swatch } from "../color/swatch.js"; import { - accentFillFocus, - accentForegroundRest, - baseHeightMultiplier, - bodyFont, - controlCornerRadius, - density, - designUnit, - DirectionalStyleSheetBehavior, - disabledOpacity, - focusStrokeWidth, - neutralFillRecipe, - neutralFillRest, - neutralFillStealthActive, - neutralFillStealthHover, - neutralFillStealthRecipe, - neutralFillStealthRest, - neutralForegroundRest, - strokeWidth, - typeRampBaseFontSize, - typeRampBaseLineHeight -} from '../design-tokens'; -import { heightNumber } from '../styles/index'; + accentForegroundRest, + baseHeightMultiplier, + bodyFont, + controlCornerRadius, + density, + designUnit, + disabledOpacity, + focusStrokeOuter, + focusStrokeWidth, + neutralFillActive, + neutralFillHover, + neutralFillRecipe, + neutralFillRest, + neutralFillStealthActive, + neutralFillStealthHover, + neutralFillStealthRecipe, + neutralFillStealthRest, + neutralForegroundRest, + strokeWidth, + typeRampBaseFontSize, + typeRampBaseLineHeight, +} from "../design-tokens.js"; +import { DirectionalStyleSheetBehavior, heightNumber } from "../styles/index.js"; const ltr = css` - .expand-collapse-glyph { - transform: rotate(0deg); - } - :host(.nested) .expand-collapse-button { - left: var( - --expand-collapse-button-nested-width, - calc(${heightNumber} * -1px) - ); - } - :host([selected])::after { - left: calc(${focusStrokeWidth} * 1px); - } - :host([expanded]) > .positioning-region .expand-collapse-glyph { - transform: rotate(90deg); - } + .expand-collapse-glyph { + transform: rotate(0deg); + } + :host(.nested) .expand-collapse-button { + left: var(--expand-collapse-button-nested-width, calc(${heightNumber} * -1px)); + } + :host([selected])::after { + left: calc(${focusStrokeWidth} * 1px); + } + :host([expanded]) > .positioning-region .expand-collapse-glyph { + transform: rotate(90deg); + } `; const rtl = css` - .expand-collapse-glyph { - transform: rotate(180deg); - } - :host(.nested) .expand-collapse-button { - right: var( - --expand-collapse-button-nested-width, - calc(${heightNumber} * -1px) - ); - } - :host([selected])::after { - right: calc(${focusStrokeWidth} * 1px); - } - :host([expanded]) > .positioning-region .expand-collapse-glyph { - transform: rotate(90deg); - } + .expand-collapse-glyph { + transform: rotate(180deg); + } + :host(.nested) .expand-collapse-button { + right: var(--expand-collapse-button-nested-width, calc(${heightNumber} * -1px)); + } + :host([selected])::after { + right: calc(${focusStrokeWidth} * 1px); + } + :host([expanded]) > .positioning-region .expand-collapse-glyph { + transform: rotate(90deg); + } `; /** @@ -82,260 +73,280 @@ const rtl = css` export const expandCollapseButtonSize = cssPartial`((${baseHeightMultiplier} / 2) * ${designUnit}) + ((${designUnit} * ${density}) / 2)`; const expandCollapseHoverBehavior = DesignToken.create( - 'tree-item-expand-collapse-hover' + "tree-item-expand-collapse-hover" ).withDefault((target: HTMLElement) => { - const recipe = neutralFillStealthRecipe.getValueFor(target); - return recipe.evaluate(target, recipe.evaluate(target).hover).hover; + const recipe = neutralFillStealthRecipe.getValueFor(target); + return recipe.evaluate(target, recipe.evaluate(target).hover).hover; }); const selectedExpandCollapseHoverBehavior = DesignToken.create( - 'tree-item-expand-collapse-selected-hover' + "tree-item-expand-collapse-selected-hover" ).withDefault((target: HTMLElement) => { - const baseRecipe = neutralFillRecipe.getValueFor(target); - const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); - return buttonRecipe.evaluate(target, baseRecipe.evaluate(target).rest).hover; + const baseRecipe = neutralFillRecipe.getValueFor(target); + const buttonRecipe = neutralFillStealthRecipe.getValueFor(target); + return buttonRecipe.evaluate(target, baseRecipe.evaluate(target).rest).hover; }); /** * Styles for Tree Item * @public */ -export const treeItemStyles: FoundationElementTemplate< - ElementStyles, - TreeItemOptions -> = (context, definition) => - css` - ${display('block')} :host { - contain: content; - position: relative; - outline: none; - color: ${neutralForegroundRest}; - background: ${neutralFillStealthRest}; - cursor: pointer; - font-family: ${bodyFont}; - --expand-collapse-button-size: calc(${heightNumber} * 1px); - --tree-item-nested-width: 0; +export const treeItemStyles: FoundationElementTemplate = ( + context, + definition +) => + css` + /** + * This animation exists because when tree item children are conditionally loaded + * there is a visual bug where the DOM exists but styles have not yet been applied (essentially FOUC). + * This subtle animation provides a ever so slight timing adjustment for loading that solves the issue. + */ + @keyframes treeItemLoading { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } - :host(:focus) > .positioning-region { - outline: none; + ${display("block")} :host { + contain: content; + position: relative; + outline: none; + color: ${neutralForegroundRest}; + background: ${neutralFillStealthRest}; + cursor: pointer; + font-family: ${bodyFont}; + --expand-collapse-button-size: calc(${heightNumber} * 1px); + --tree-item-nested-width: 0; } - :host(:focus) .content-region { - outline: none; - } - - :host(:${focusVisible}) .positioning-region { - border-color: ${accentFillFocus}; - box-shadow: 0 0 0 calc((${focusStrokeWidth} - ${strokeWidth}) * 1px) - ${accentFillFocus} inset; - color: ${neutralForegroundRest}; - } - - .positioning-region { - display: flex; - position: relative; - box-sizing: border-box; - border: transparent calc(${strokeWidth} * 1px) solid; - border-radius: calc(${controlCornerRadius} * 1px); - height: calc((${heightNumber} + 1) * 1px); - } - - .positioning-region::before { - content: ''; - display: block; - width: var(--tree-item-nested-width); - flex-shrink: 0; - } - - .positioning-region:hover { - background: ${neutralFillStealthHover}; - } - - .positioning-region:active { - background: ${neutralFillStealthActive}; - } - - .content-region { - display: inline-flex; - align-items: center; - white-space: nowrap; - width: 100%; - min-width: 0; - height: calc(${heightNumber} * 1px); - margin-inline-start: calc(${designUnit} * 2px + 8px); - font-size: ${typeRampBaseFontSize}; - line-height: ${typeRampBaseLineHeight}; - font-weight: 400; - } - - .items { - display: none; - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - font-size: calc(1em + (${designUnit} + 16) * 1px); - } - - .expand-collapse-button { - background: none; - border: none; - outline: none; - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - width: calc((${expandCollapseButtonSize} + (${designUnit} * 2)) * 1px); - height: calc((${expandCollapseButtonSize} + (${designUnit} * 2)) * 1px); - padding: 0; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - margin-left: 6px; - margin-right: 6px; - } - - .expand-collapse-glyph { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - width: 16px; - height: 16px; - transition: transform 0.1s linear; - - pointer-events: none; - fill: currentcolor; - } - - .start, - .end { - display: flex; - fill: currentcolor; - } - - ::slotted(svg) { - /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ - width: 16px; - height: 16px; - } - - .start { - /* TODO: horizontalSpacing https://github.com/microsoft/fast/issues/2766 */ - margin-inline-end: calc(${designUnit} * 2px + 2px); - } - - .end { - /* TODO: horizontalSpacing https://github.com/microsoft/fast/issues/2766 */ - margin-inline-start: calc(${designUnit} * 2px + 2px); - } - - :host([expanded]) > .items { - display: block; - } - - :host([disabled]) .content-region { - opacity: ${disabledOpacity}; - cursor: ${disabledCursor}; - } - - :host(.nested) .content-region { - position: relative; - margin-inline-start: var(--expand-collapse-button-size); - } - - :host(.nested) .expand-collapse-button { - position: absolute; - } - - :host(.nested) .expand-collapse-button:hover { - background: ${expandCollapseHoverBehavior}; - } - - :host([selected]) .positioning-region { - background: ${neutralFillRest}; - } - - :host([selected]) .expand-collapse-button:hover { - background: ${selectedExpandCollapseHoverBehavior}; - } - - :host([selected])::after { - /* The background needs to be calculated based on the selected background state - for this control. We currently have no way of changing that, so setting to - accent-foreground-rest for the time being */ - background: ${accentForegroundRest}; - border-radius: calc(${controlCornerRadius} * 1px); - content: ''; - display: block; - position: absolute; - top: calc((${heightNumber} / 4) * 1px); - width: 3px; - height: calc((${heightNumber} / 2) * 1px); - } - - ::slotted(${context.tagFor(TreeItem)}) { - --tree-item-nested-width: 1em; - --expand-collapse-button-nested-width: calc(${heightNumber} * -1px); - } - `.withBehaviors( - new DirectionalStyleSheetBehavior(ltr, rtl), - forcedColorsStylesheetBehavior(css` - :host { - forced-color-adjust: none; - border-color: transparent; - background: ${SystemColors.Field}; - color: ${SystemColors.FieldText}; - } - :host .content-region .expand-collapse-glyph { - fill: ${SystemColors.FieldText}; - } - :host .positioning-region:hover, - :host([selected]) .positioning-region { - background: ${SystemColors.Highlight}; - } - :host .positioning-region:hover .content-region, - :host([selected]) .positioning-region .content-region { - color: ${SystemColors.HighlightText}; - } - :host .positioning-region:hover .content-region .expand-collapse-glyph, - :host .positioning-region:hover .content-region .start, - :host .positioning-region:hover .content-region .end, - :host([selected]) .content-region .expand-collapse-glyph, - :host([selected]) .content-region .start, - :host([selected]) .content-region .end { - fill: ${SystemColors.HighlightText}; - } - :host([selected])::after { - background: ${SystemColors.Field}; - } - :host(:${focusVisible}) .positioning-region { - border-color: ${SystemColors.FieldText}; - box-shadow: 0 0 0 2px inset ${SystemColors.Field}; - color: ${SystemColors.FieldText}; - } - :host([disabled]) .content-region, - :host([disabled]) .positioning-region:hover .content-region { - opacity: 1; - color: ${SystemColors.GrayText}; - } - :host([disabled]) .content-region .expand-collapse-glyph, - :host([disabled]) .content-region .start, - :host([disabled]) .content-region .end, - :host([disabled]) - .positioning-region:hover - .content-region - .expand-collapse-glyph, - :host([disabled]) .positioning-region:hover .content-region .start, - :host([disabled]) .positioning-region:hover .content-region .end { - fill: ${SystemColors.GrayText}; - } - :host([disabled]) .positioning-region:hover { - background: ${SystemColors.Field}; - } - .expand-collapse-glyph, - .start, - .end { - fill: ${SystemColors.FieldText}; - } - :host(.nested) .expand-collapse-button:hover { - background: ${SystemColors.Field}; - } - :host(.nested) .expand-collapse-button:hover .expand-collapse-glyph { - fill: ${SystemColors.FieldText}; - } - `) - ); + :host(:focus) > .positioning-region { + outline: none; + } + + :host(:focus) .content-region { + outline: none; + } + + :host(:${focusVisible}) .positioning-region { + border: ${focusStrokeOuter} calc(${strokeWidth} * 1px) solid; + border-radius: calc(${controlCornerRadius} * 1px); + color: ${neutralForegroundRest}; + } + + .positioning-region { + display: flex; + position: relative; + box-sizing: border-box; + background: ${neutralFillStealthRest}; + border: transparent calc(${strokeWidth} * 1px) solid; + height: calc((${heightNumber} + 1) * 1px); + } + + .positioning-region::before { + content: ""; + display: block; + width: var(--tree-item-nested-width); + flex-shrink: 0; + } + + :host(:not([disabled])) .positioning-region:hover { + background: ${neutralFillStealthHover}; + } + + :host(:not([disabled])) .positioning-region:active { + background: ${neutralFillStealthActive}; + } + + .content-region { + display: inline-flex; + align-items: center; + white-space: nowrap; + width: 100%; + height: calc(${heightNumber} * 1px); + margin-inline-start: calc(${designUnit} * 2px + 8px); + font-size: ${typeRampBaseFontSize}; + line-height: ${typeRampBaseLineHeight}; + font-weight: 400; + } + + .items { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + font-size: calc(1em + (${designUnit} + 16) * 1px); + } + + .expand-collapse-button { + background: none; + border: none; + outline: none; + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: calc((${expandCollapseButtonSize} + (${designUnit} * 2)) * 1px); + height: calc((${expandCollapseButtonSize} + (${designUnit} * 2)) * 1px); + padding: 0; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + margin-left: 6px; + margin-right: 6px; + } + + .expand-collapse-glyph { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + transition: transform 0.1s linear; + + pointer-events: none; + fill: currentcolor; + } + + .start, + .end { + display: flex; + fill: currentcolor; + } + + ::slotted(svg) { + /* TODO: adaptive typography https://github.com/microsoft/fast/issues/2432 */ + width: 16px; + height: 16px; + } + + .start { + /* TODO: horizontalSpacing https://github.com/microsoft/fast/issues/2766 */ + margin-inline-end: calc(${designUnit} * 2px + 2px); + } + + .end { + /* TODO: horizontalSpacing https://github.com/microsoft/fast/issues/2766 */ + margin-inline-start: calc(${designUnit} * 2px + 2px); + } + + :host([expanded]) > .items { + animation: treeItemLoading ease-in 10ms; + animation-iteration-count: 1; + animation-fill-mode: forwards; + } + + :host([disabled]) .content-region { + opacity: ${disabledOpacity}; + cursor: ${disabledCursor}; + } + + :host(.nested) .content-region { + position: relative; + margin-inline-start: var(--expand-collapse-button-size); + } + + :host(.nested) .expand-collapse-button { + position: absolute; + } + + :host(.nested:not([disabled])) .expand-collapse-button:hover { + background: ${expandCollapseHoverBehavior}; + } + + :host([selected]) .positioning-region { + background: ${neutralFillRest}; + } + + :host([selected]:not([disabled])) .positioning-region:hover { + background: ${neutralFillHover}; + } + + :host([selected]:not([disabled])) .positioning-region:active { + background: ${neutralFillActive}; + } + + :host([selected]:not([disabled])) .expand-collapse-button:hover { + background: ${selectedExpandCollapseHoverBehavior}; + } + + :host([selected])::after { + /* The background needs to be calculated based on the selected background state + for this control. We currently have no way of changing that, so setting to + accent-foreground-rest for the time being */ + background: ${accentForegroundRest}; + border-radius: calc(${controlCornerRadius} * 1px); + content: ""; + display: block; + position: absolute; + top: calc((${heightNumber} / 4) * 1px); + width: 3px; + height: calc((${heightNumber} / 2) * 1px); + } + + ::slotted(${context.tagFor(TreeItem)}) { + --tree-item-nested-width: 1em; + --expand-collapse-button-nested-width: calc(${heightNumber} * -1px); + } + `.withBehaviors( + new DirectionalStyleSheetBehavior(ltr, rtl), + forcedColorsStylesheetBehavior( + css` + :host { + forced-color-adjust: none; + border-color: transparent; + background: ${SystemColors.Field}; + color: ${SystemColors.FieldText}; + } + :host .content-region .expand-collapse-glyph { + fill: ${SystemColors.FieldText}; + } + :host .positioning-region:hover, + :host([selected]) .positioning-region { + background: ${SystemColors.Highlight}; + } + :host .positioning-region:hover .content-region, + :host([selected]) .positioning-region .content-region { + color: ${SystemColors.HighlightText}; + } + :host .positioning-region:hover .content-region .expand-collapse-glyph, + :host .positioning-region:hover .content-region .start, + :host .positioning-region:hover .content-region .end, + :host([selected]) .content-region .expand-collapse-glyph, + :host([selected]) .content-region .start, + :host([selected]) .content-region .end { + fill: ${SystemColors.HighlightText}; + } + :host([selected])::after { + background: ${SystemColors.Field}; + } + :host(:${focusVisible}) .positioning-region { + border-color: ${SystemColors.FieldText}; + box-shadow: 0 0 0 2px inset ${SystemColors.Field}; + color: ${SystemColors.FieldText}; + } + :host([disabled]) .content-region, + :host([disabled]) .positioning-region:hover .content-region { + opacity: 1; + color: ${SystemColors.GrayText}; + } + :host([disabled]) .content-region .expand-collapse-glyph, + :host([disabled]) .content-region .start, + :host([disabled]) .content-region .end, + :host([disabled]) .positioning-region:hover .content-region .expand-collapse-glyph, + :host([disabled]) .positioning-region:hover .content-region .start, + :host([disabled]) .positioning-region:hover .content-region .end { + fill: ${SystemColors.GrayText}; + } + :host([disabled]) .positioning-region:hover { + background: ${SystemColors.Field}; + } + .expand-collapse-glyph, + .start, + .end { + fill: ${SystemColors.FieldText}; + } + :host(.nested) .expand-collapse-button:hover { + background: ${SystemColors.Field}; + } + :host(.nested) .expand-collapse-button:hover .expand-collapse-glyph { + fill: ${SystemColors.FieldText}; + } + ` + ) + ); diff --git a/packages/components/src/tree-view/index.ts b/packages/components/src/tree-view/index.ts index 9b386dfa..d7ecd817 100644 --- a/packages/components/src/tree-view/index.ts +++ b/packages/components/src/tree-view/index.ts @@ -1,11 +1,5 @@ -// Copyright (c) Jupyter Development Team. -// Distributed under the terms of the Modified BSD License. - -import { - treeViewTemplate as template, - TreeView -} from '@microsoft/fast-foundation'; -import { treeViewStyles as styles } from '@microsoft/fast-components'; +import { treeViewTemplate as template, TreeView } from "@microsoft/fast-foundation"; +import { treeViewStyles as styles } from "./tree-view.styles.js"; /** * A function that returns a {@link @microsoft/fast-foundation#TreeView} registration for configuring the component with a DesignSystem. @@ -18,9 +12,9 @@ import { treeViewStyles as styles } from '@microsoft/fast-components'; * */ export const jpTreeView = TreeView.compose({ - baseName: 'tree-view', - template, - styles + baseName: 'tree-view', + template, + styles }); /** diff --git a/packages/components/src/tree-view/tree-view.styles.ts b/packages/components/src/tree-view/tree-view.styles.ts new file mode 100644 index 00000000..b3bb9f3d --- /dev/null +++ b/packages/components/src/tree-view/tree-view.styles.ts @@ -0,0 +1,22 @@ +import { css, ElementStyles } from "@microsoft/fast-element"; +import { display, FoundationElementTemplate } from "@microsoft/fast-foundation"; + +/** + * Styles for Tree View + * @public + */ +export const treeViewStyles: FoundationElementTemplate = ( + context, + definition +) => css` + ${display("flex")} :host { + flex-direction: column; + align-items: stretch; + min-width: fit-content; + font-size: 0; + } + + :host:focus-visible { + outline: none; + } +`; diff --git a/packages/components/src/utilities/behaviors.ts b/packages/components/src/utilities/behaviors.ts new file mode 100644 index 00000000..ea877f1f --- /dev/null +++ b/packages/components/src/utilities/behaviors.ts @@ -0,0 +1,15 @@ +import { ElementStyles } from "@microsoft/fast-element"; +import { PropertyStyleSheetBehavior } from "@microsoft/fast-foundation"; + +/** + * Behavior that will conditionally apply a stylesheet based on the elements + * appearance property + * + * @param value - The value of the appearance property + * @param styles - The styles to be applied when condition matches + * + * @public + */ +export function appearanceBehavior(value: string, styles: ElementStyles) { + return new PropertyStyleSheetBehavior("appearance", value, styles); +} diff --git a/packages/components/src/utilities/theme/applyTheme.ts b/packages/components/src/utilities/theme/applyTheme.ts index 3a0b0d2d..b5871f42 100644 --- a/packages/components/src/utilities/theme/applyTheme.ts +++ b/packages/components/src/utilities/theme/applyTheme.ts @@ -7,15 +7,17 @@ import { parseColor, rgbToHSL } from '@microsoft/fast-colors'; -import { isDark } from '@microsoft/fast-components'; import { DesignToken } from '@microsoft/fast-foundation'; import { Palette, PaletteRGB, - StandardLuminance, +} from '../../color/palette'; +import { Swatch, SwatchRGB -} from '../../color'; +} from '../../color/swatch.js'; +import { StandardLuminance } from '../../color/utilities/base-layer-luminance.js'; +import { isDark } from '../../color/utilities/is-dark.js'; import { accentFillHoverDelta, accentPalette, diff --git a/packages/components/tsconfigbase.json b/packages/components/tsconfigbase.json index 03d3826a..cc99c6e5 100644 --- a/packages/components/tsconfigbase.json +++ b/packages/components/tsconfigbase.json @@ -18,6 +18,7 @@ "strict": true, "strictNullChecks": true, "strictFunctionTypes": false, - "allowJs": true + "allowJs": true, + "types": ["node", "mocha", "webpack-env"] } } diff --git a/yarn.lock b/yarn.lock index a7f4760c..7dee32de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2233,14 +2233,12 @@ __metadata: "@fortawesome/free-solid-svg-icons": ^5.15.4 "@microsoft/api-extractor": ^7.36.0 "@microsoft/fast-colors": ^5.3.1 - "@microsoft/fast-components": ^2.30.6 "@microsoft/fast-element": ^1.12.0 "@microsoft/fast-foundation": ^2.49.4 "@microsoft/fast-web-utilities": ^5.4.1 "@playwright/test": ^1.35.1 "@rollup/plugin-commonjs": ^17.1.0 "@rollup/plugin-node-resolve": ^11.2.0 - "@rollup/plugin-typescript": ^8.2.0 "@storybook/addon-a11y": ^7.5.3 "@storybook/addon-actions": ^7.5.3 "@storybook/addon-docs": ^7.5.3 @@ -2251,7 +2249,13 @@ __metadata: "@storybook/html": ^7.5.3 "@storybook/html-webpack5": ^7.5.3 "@storybook/theming": ^7.5.3 + "@types/chai": ^4.2.11 + "@types/chai-spies": ^1.0.1 "@types/jest": ^29.0.0 + "@types/karma": ^5.0.0 + "@types/mocha": ^8.2.0 + "@types/node": ^18.0.0 + "@types/webpack-env": ^1.15.2 "@typescript-eslint/eslint-plugin": ^5.60.1 eslint: ^8.43.0 eslint-config-prettier: ^8.8.0 @@ -2269,6 +2273,7 @@ __metadata: rollup-plugin-filesize: ^9.1.1 rollup-plugin-terser: ^7.0.2 rollup-plugin-transform-tagged-template: 0.0.3 + rollup-plugin-typescript2: ^0.27.0 storybook: ^7.5.3 ts-jest: ^29.1.0 ts-loader: ^9.4.3 @@ -2838,34 +2843,21 @@ __metadata: languageName: node linkType: hard -"@microsoft/fast-colors@npm:^5.3.0, @microsoft/fast-colors@npm:^5.3.1": +"@microsoft/fast-colors@npm:^5.3.1": version: 5.3.1 resolution: "@microsoft/fast-colors@npm:5.3.1" checksum: ff87f402faadb4b5aeee3d27762566c11807f927cd4012b8bbc7f073ca68de0e2197f95330ff5dfd7038f4b4f0e2f51b11feb64c5d570f5c598d37850a5daf60 languageName: node linkType: hard -"@microsoft/fast-components@npm:^2.30.6": - version: 2.30.6 - resolution: "@microsoft/fast-components@npm:2.30.6" - dependencies: - "@microsoft/fast-colors": ^5.3.0 - "@microsoft/fast-element": ^1.10.1 - "@microsoft/fast-foundation": ^2.46.2 - "@microsoft/fast-web-utilities": ^5.4.1 - tslib: ^1.13.0 - checksum: 1fbf3b7c265bcbf6abcae4d2f72430f7f871104a3d8344f16667a4cc7b123698cdf2bab8b760cbed92ef761c4db350a67f570665c76b132d6996990ac93cbd4f - languageName: node - linkType: hard - -"@microsoft/fast-element@npm:^1.10.1, @microsoft/fast-element@npm:^1.12.0": +"@microsoft/fast-element@npm:^1.12.0": version: 1.12.0 resolution: "@microsoft/fast-element@npm:1.12.0" checksum: bbff4e9c83106d1d74f3eeedc87bf84832429e78fee59c6a4ae8164ee4f42667503f586896bea72341b4d2c76c244a3cb0d4fd0d5d3732755f00357714dd609e languageName: node linkType: hard -"@microsoft/fast-foundation@npm:^2.46.2, @microsoft/fast-foundation@npm:^2.49.4": +"@microsoft/fast-foundation@npm:^2.49.4": version: 2.49.4 resolution: "@microsoft/fast-foundation@npm:2.49.4" dependencies: @@ -4035,23 +4027,6 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-typescript@npm:^8.2.0": - version: 8.5.0 - resolution: "@rollup/plugin-typescript@npm:8.5.0" - dependencies: - "@rollup/pluginutils": ^3.1.0 - resolve: ^1.17.0 - peerDependencies: - rollup: ^2.14.0 - tslib: "*" - typescript: ">=3.7.0" - peerDependenciesMeta: - tslib: - optional: true - checksum: 2f100a73cdeb9bf82feaf8665fe791dabf5dcc17f6e727eb7b1825e7c7cf815ccd3f79f9f43ca53d7806c50686565c71f35fffcfc773b811a5c1e39eab167529 - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^3.1.0": version: 3.1.0 resolution: "@rollup/pluginutils@npm:3.1.0" @@ -5290,6 +5265,22 @@ __metadata: languageName: node linkType: hard +"@types/chai-spies@npm:^1.0.1": + version: 1.0.6 + resolution: "@types/chai-spies@npm:1.0.6" + dependencies: + "@types/chai": "*" + checksum: 2f4e1fd3ed4f317b6f445a4516612b2a40e25c86cf60ba093ce3569012d612d0fc05c96d69223221c26fad60d0f3af75f7c2bb1858e48b209e8da8f0a1d5fccd + languageName: node + linkType: hard + +"@types/chai@npm:*, @types/chai@npm:^4.2.11": + version: 4.3.11 + resolution: "@types/chai@npm:4.3.11" + checksum: d0c05fe5d02b2e6bbca2bd4866a2ab20a59cf729bc04af0060e7a3277eaf2fb65651b90d4c74b0ebf1d152b4b1d49fa8e44143acef276a2bbaa7785fbe5642d3 + languageName: node + linkType: hard + "@types/connect@npm:*": version: 3.4.35 resolution: "@types/connect@npm:3.4.35" @@ -5492,6 +5483,16 @@ __metadata: languageName: node linkType: hard +"@types/karma@npm:^5.0.0": + version: 5.0.1 + resolution: "@types/karma@npm:5.0.1" + dependencies: + "@types/node": "*" + log4js: ^4.0.0 + checksum: a719087211aa627d33c89ca8abeba8d775009c486e09e351e7f286b32b894ba623072eccd3a716a7acd8507f5a9f017bdfe088d19ad96f289581c438eb3df07d + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.167": version: 4.14.195 resolution: "@types/lodash@npm:4.14.195" @@ -5543,6 +5544,13 @@ __metadata: languageName: node linkType: hard +"@types/mocha@npm:^8.2.0": + version: 8.2.3 + resolution: "@types/mocha@npm:8.2.3" + checksum: b43ed1b642a2ee62bf10792a07d5d21d66ab8b4d2cf5d822c8a7643e77b90009aecc000eefab5f6ddc9eb69004192f84119a6f97a8499e1a13ea082e7a5e71bf + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.31 resolution: "@types/ms@npm:0.7.31" @@ -5694,6 +5702,13 @@ __metadata: languageName: node linkType: hard +"@types/webpack-env@npm:^1.15.2": + version: 1.18.4 + resolution: "@types/webpack-env@npm:1.18.4" + checksum: f195b3ae974ac3b631477b57737dad7b6c44ecca86770cf3c29f284e02961c9f2dfc619e3e253d8c23966864cb052b1e8437e9834ede32ac97972e6e2235bb51 + languageName: node + linkType: hard + "@types/webpack-sources@npm:^0.1.5": version: 0.1.9 resolution: "@types/webpack-sources@npm:0.1.9" @@ -6569,6 +6584,15 @@ __metadata: languageName: node linkType: hard +"async@npm:^2.6.2": + version: 2.6.4 + resolution: "async@npm:2.6.4" + dependencies: + lodash: ^4.17.14 + checksum: a52083fb32e1ebe1d63e5c5624038bb30be68ff07a6c8d7dfe35e47c93fc144bd8652cbec869e0ac07d57dde387aa5f1386be3559cdee799cb1f789678d88e19 + languageName: node + linkType: hard + "async@npm:^3.2.3, async@npm:^3.2.4": version: 3.2.4 resolution: "async@npm:3.2.4" @@ -7981,6 +8005,13 @@ __metadata: languageName: node linkType: hard +"date-format@npm:^2.0.0": + version: 2.1.0 + resolution: "date-format@npm:2.1.0" + checksum: ff2c80c76021a315409b6ce2f08997f6e4a61ae68042dbf2cefda450207712a804aa30ac52e235f3de495dc915842507249c74e4668659835cc4870892042394 + languageName: node + linkType: hard + "dateformat@npm:^3.0.3": version: 3.0.3 resolution: "dateformat@npm:3.0.3" @@ -8009,7 +8040,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.2.7": +"debug@npm:^3.2.6, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -9461,6 +9492,13 @@ __metadata: languageName: node linkType: hard +"flatted@npm:^2.0.0": + version: 2.0.2 + resolution: "flatted@npm:2.0.2" + checksum: 473c754db7a529e125a22057098f1a4c905ba17b8cc269c3acf77352f0ffa6304c851eb75f6a1845f74461f560e635129ca6b0b8a78fb253c65cea4de3d776f2 + languageName: node + linkType: hard + "flatted@npm:^3.1.0": version: 3.2.4 resolution: "flatted@npm:3.2.4" @@ -9606,6 +9644,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:8.1.0, fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^4.0.0 + universalify: ^0.1.0 + checksum: bf44f0e6cea59d5ce071bba4c43ca76d216f89e402dc6285c128abc0902e9b8525135aa808adad72c9d5d218e9f4bcc63962815529ff2f684ad532172a284880 + languageName: node + linkType: hard + "fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": version: 10.1.0 resolution: "fs-extra@npm:10.1.0" @@ -9617,18 +9666,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^8.1.0": - version: 8.1.0 - resolution: "fs-extra@npm:8.1.0" - dependencies: - graceful-fs: ^4.2.0 - jsonfile: ^4.0.0 - universalify: ^0.1.0 - checksum: bf44f0e6cea59d5ce071bba4c43ca76d216f89e402dc6285c128abc0902e9b8525135aa808adad72c9d5d218e9f4bcc63962815529ff2f684ad532172a284880 - languageName: node - linkType: hard - -"fs-extra@npm:~7.0.1": +"fs-extra@npm:^7.0.1, fs-extra@npm:~7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" dependencies: @@ -12330,7 +12368,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.15": +"lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -12347,6 +12385,19 @@ __metadata: languageName: node linkType: hard +"log4js@npm:^4.0.0": + version: 4.5.1 + resolution: "log4js@npm:4.5.1" + dependencies: + date-format: ^2.0.0 + debug: ^4.1.1 + flatted: ^2.0.0 + rfdc: ^1.1.4 + streamroller: ^1.0.6 + checksum: ad6d7753f6709363c02172cf017398d1c725a077ce9a7d2967df61528c5d186f9343fe0fb0cffa8808e2b3e6f92b47623e0f8b4dc3de0c50d42c05db11ee63c5 + languageName: node + linkType: hard + "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -15694,6 +15745,15 @@ __metadata: languageName: node linkType: hard +"resolve@npm:1.17.0": + version: 1.17.0 + resolution: "resolve@npm:1.17.0" + dependencies: + path-parse: ^1.0.6 + checksum: 9ceaf83b3429f2d7ff5d0281b8d8f18a1f05b6ca86efea7633e76b8f76547f33800799dfdd24434942dec4fbd9e651ed3aef577d9a6b5ec87ad89c1060e24759 + languageName: node + linkType: hard + "resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:~1.22.1": version: 1.22.3 resolution: "resolve@npm:1.22.3" @@ -15717,6 +15777,15 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@1.17.0#~builtin": + version: 1.17.0 + resolution: "resolve@patch:resolve@npm%3A1.17.0#~builtin::version=1.17.0&hash=c3c19d" + dependencies: + path-parse: ^1.0.6 + checksum: 6fd799f282ddf078c4bc20ce863e3af01fa8cb218f0658d9162c57161a2dbafe092b13015b9a4c58d0e1e801cf7aa7a4f13115fea9db98c3f9a0c43e429bad6f + languageName: node + linkType: hard + "resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@~1.22.1#~builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" @@ -15764,6 +15833,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.1.4": + version: 1.3.0 + resolution: "rfdc@npm:1.3.0" + checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 + languageName: node + linkType: hard + "rimraf@npm:^2.6.1, rimraf@npm:~2.6.2": version: 2.6.3 resolution: "rimraf@npm:2.6.3" @@ -15858,6 +15934,22 @@ __metadata: languageName: node linkType: hard +"rollup-plugin-typescript2@npm:^0.27.0": + version: 0.27.3 + resolution: "rollup-plugin-typescript2@npm:0.27.3" + dependencies: + "@rollup/pluginutils": ^3.1.0 + find-cache-dir: ^3.3.1 + fs-extra: 8.1.0 + resolve: 1.17.0 + tslib: 2.0.1 + peerDependencies: + rollup: ">=1.26.3" + typescript: ">=2.4.0" + checksum: 32fc963bf5bfc0e661b44b3463c12280513e20c572124fcee71e76d0e771ca1f462da9b975af8b5e8ffa68d1b831bf7c1e2ccd53b9b1e4dbdd12c875dfeca27e + languageName: node + linkType: hard + "rollup@npm:^2.40.0": version: 2.79.1 resolution: "rollup@npm:2.79.1" @@ -16486,6 +16578,19 @@ __metadata: languageName: node linkType: hard +"streamroller@npm:^1.0.6": + version: 1.0.6 + resolution: "streamroller@npm:1.0.6" + dependencies: + async: ^2.6.2 + date-format: ^2.0.0 + debug: ^3.2.6 + fs-extra: ^7.0.1 + lodash: ^4.17.14 + checksum: 14c6717c1af1086ba10095b8c3f2dc0ff8c1d40e0e8a0eb069b9613d74899462ce64eb39f605db4ed531b1aa519a3b4134d24616effa4fad9bc67027328c3af9 + languageName: node + linkType: hard + "string-argv@npm:~0.3.1": version: 0.3.1 resolution: "string-argv@npm:0.3.1" @@ -17260,6 +17365,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.0.1": + version: 2.0.1 + resolution: "tslib@npm:2.0.1" + checksum: 507f32fc24a614c5097d414b622373b6cbb99e305413517e7fd49bef1e63570c0dd15b417ae68152088c3496218e82a5d8c7cd6b48c7a32dcee1a3f7191fff74 + languageName: node + linkType: hard + "tslib@npm:^1.13.0, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1"