diff --git a/.prettierrc b/.prettierrc index 5d67dbb7..c4db7bf9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -25,6 +25,12 @@ "options": { "printWidth": 110 } + }, + { + "files": "src/design-tokens.ts", + "options": { + "printWidth": 200 + } } ] } diff --git a/package.json b/package.json index 025fae21..8988e8eb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "main": "dist/esm/index.js", "types": "dist/dts/index.d.ts", + "sideEffects": false, "scripts": { "start": "start-storybook -p 6006", "build": "rollup -c && tsc -p ./tsconfig.json && npm run doc", diff --git a/src/design-tokens.ts b/src/design-tokens.ts index ff70aeac..73589abd 100644 --- a/src/design-tokens.ts +++ b/src/design-tokens.ts @@ -1,216 +1,128 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import {DesignToken} from '@microsoft/fast-foundation'; - -const {create} = DesignToken; +import {create} from './utilities/design-tokens/create'; /** * Global design tokens. */ -export const designUnit = create('design-unit').withDefault(4); + export const borderWidth = create('border-width').withDefault(1); -export const contrastActiveBorder = create( - 'contrast-active-border' -).withDefault('#f38518'); -export const contrastBorder = - create('contrast-border').withDefault('#6fc3df'); +export const contrastActiveBorder = create('contrast-active-border', '--vscode-contrastActiveBorder').withDefault('#f38518'); +export const contrastBorder = create('contrast-border', '--vscode-contrastBorder').withDefault('#6fc3df'); export const cornerRadius = create('corner-radius').withDefault(0); -export const disabledOpacity = - create('disabled-opacity').withDefault(0.4); -export const focusBorder = - create('focus-border').withDefault('#007fd4'); -export const foreground = create('foreground').withDefault('#cccccc'); -export const fontFamily = create('font-family').withDefault( +export const designUnit = create('design-unit').withDefault(4); +export const disabledOpacity = create('disabled-opacity').withDefault(0.4); +export const focusBorder = create('focus-border', '--vscode-focusBorder').withDefault('#007fd4'); +export const fontFamily = create('font-family', '--vscode-font-family').withDefault( '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol' ); -export const fontWeight = create('font-weight').withDefault('400'); +export const fontWeight = create('font-weight', '--vscode-font-weight').withDefault('400'); +export const foreground = create('foreground', '--vscode-foreground').withDefault('#cccccc'); export const inputHeight = create('input-height').withDefault('26'); -export const inputMinWidth = - create('input-min-width').withDefault('100px'); - -/** - * Type-ramp font-size and line-height design tokens. - */ -export const typeRampBaseFontSize = create( - 'type-ramp-base-font-size' -).withDefault('13px'); -export const typeRampBaseLineHeight = create( - 'type-ramp-base-line-height' -).withDefault('normal'); -export const typeRampMinus1FontSize = create( - 'type-ramp-minus1-font-size' -).withDefault('11px'); -export const typeRampMinus1LineHeight = create( - 'type-ramp-minus1-line-height' -).withDefault('16px'); -export const typeRampMinus2FontSize = create( - 'type-ramp-minus2-font-size' -).withDefault('9px'); -export const typeRampMinus2LineHeight = create( - 'type-ramp-minus2-line-height' -).withDefault('16px'); -export const typeRampPlus1FontSize = create( - 'type-ramp-plus1-font-size' -).withDefault('16px'); -export const typeRampPlus1LineHeight = create( - 'type-ramp-plus1-line-height' -).withDefault('24px'); +export const inputMinWidth = create('input-min-width').withDefault('100px'); +export const typeRampBaseFontSize = create('type-ramp-base-font-size', '--vscode-font-size').withDefault('13px'); +export const typeRampBaseLineHeight = create('type-ramp-base-line-height').withDefault('normal'); +export const typeRampMinus1FontSize = create('type-ramp-minus1-font-size').withDefault('11px'); +export const typeRampMinus1LineHeight = create('type-ramp-minus1-line-height').withDefault('16px'); +export const typeRampMinus2FontSize = create('type-ramp-minus2-font-size').withDefault('9px'); +export const typeRampMinus2LineHeight = create('type-ramp-minus2-line-height').withDefault('16px'); +export const typeRampPlus1FontSize = create('type-ramp-plus1-font-size').withDefault('16px'); +export const typeRampPlus1LineHeight = create('type-ramp-plus1-line-height').withDefault('24px'); /** * Badge design tokens. */ -export const badgeForeground = - create('badge-foreground').withDefault('#ffffff'); -export const badgeBackground = - create('badge-background').withDefault('#4d4d4d'); + +export const badgeBackground = create('badge-background', '--vscode-badge-background').withDefault('#4d4d4d'); +export const badgeForeground = create('badge-foreground', '--vscode-badge-foreground').withDefault('#ffffff'); /** * Button design tokens. */ -export const buttonPrimaryForeground = create( - 'button-primary-foreground' -).withDefault('#ffffff'); -export const buttonPrimaryBackground = create( - 'button-primary-background' -).withDefault('#0e639c'); -export const buttonPrimaryHoverBackground = create( - 'button-primary-hover-background' -).withDefault('#1177bb'); -export const buttonSecondaryForeground = create( - 'button-secondary-foreground' -).withDefault('#ffffff'); -export const buttonSecondaryBackground = create( - 'button-secondary-background' -).withDefault('#3a3d41'); -export const buttonSecondaryHoverBackground = create( - 'button-secondary-hover-background' -).withDefault('#45494e'); -export const buttonPaddingHorizontal = create( - 'button-padding-horizontal' -).withDefault('11px'); -export const buttonPaddingVertical = create( - 'button-padding-vertical' -).withDefault('6px'); -export const buttonIconBackground = create( - 'button-icon-background' -).withDefault('transparent'); -export const buttonIconHoverBackground = create( - 'button-icon-hover-background' -).withDefault('rgba(90, 93, 94, 0.31)'); -export const buttonIconPadding = create( - 'button-icon-padding' -).withDefault('3px'); -export const buttonIconCornerRadius = create( - 'button-icon-corner-radius' -).withDefault('5px'); -export const buttonIconFocusBorderOffset = create( - 'button-icon-outline-offset' -).withDefault(0); -// Note: Button Border is used only for high contrast themes and should be -// left as transparent otherwise. -export const buttonBorder = - create('button-border').withDefault('transparent'); + +// Note: Button border is used only for high contrast themes and should be left as transparent otherwise. +export const buttonBorder = create('button-border', '--vscode-button-border').withDefault('transparent'); +export const buttonIconBackground = create('button-icon-background').withDefault('transparent'); +export const buttonIconCornerRadius = create('button-icon-corner-radius').withDefault('5px'); +export const buttonIconFocusBorderOffset = create('button-icon-outline-offset').withDefault(0); +export const buttonIconHoverBackground = create('button-icon-hover-background').withDefault('rgba(90, 93, 94, 0.31)'); +export const buttonIconPadding = create('button-icon-padding').withDefault('3px'); +export const buttonPrimaryBackground = create('button-primary-background', '--vscode-button-background').withDefault('#0e639c'); +export const buttonPrimaryForeground = create('button-primary-foreground', '--vscode-button-foreground').withDefault('#ffffff'); +export const buttonPrimaryHoverBackground = create('button-primary-hover-background', '--vscode-button-hoverBackground').withDefault('#1177bb'); +export const buttonSecondaryBackground = create('button-secondary-background', '--vscode-button-secondaryBackground').withDefault('#3a3d41'); +export const buttonSecondaryForeground = create('button-secondary-foreground', '--vscode-button-secondaryForeground').withDefault('#ffffff'); +export const buttonSecondaryHoverBackground = create('button-secondary-hover-background', '--vscode-button-secondaryHoverBackground').withDefault('#45494e'); +export const buttonPaddingHorizontal = create('button-padding-horizontal').withDefault('11px'); +export const buttonPaddingVertical = create('button-padding-vertical').withDefault('6px'); /** * Checkbox design tokens. */ -export const checkboxBackground = create( - 'checkbox-background' -).withDefault('#3c3c3c'); -export const checkboxForeground = create( - 'checkbox-foreground' -).withDefault('#f0f0f0'); -export const checkboxBorder = - create('checkbox-border').withDefault('#3c3c3c'); -export const checkboxCornerRadius = create( - 'checkbox-corner-radius' -).withDefault(3); + +export const checkboxBackground = create('checkbox-background', '--vscode-checkbox-background').withDefault('#3c3c3c'); +export const checkboxBorder = create('checkbox-border', '--vscode-checkbox-border').withDefault('#3c3c3c'); +export const checkboxCornerRadius = create('checkbox-corner-radius').withDefault(3); +export const checkboxForeground = create('checkbox-foreground', '--vscode-checkbox-foreground').withDefault('#f0f0f0'); /** * Data Grid design tokens */ -export const listActiveSelectionBackground = create( - 'list-active-selection-background' -).withDefault('#094771'); -export const listActiveSelectionForeground = create( - 'list-active-selection-foreground' -).withDefault('#ffffff'); -export const listHoverBackground = create( - 'list-hover-background' -).withDefault('#2a2d2e'); -export const quickInputBackground = create( - 'quick-input-background' -).withDefault('#252526'); + +export const listActiveSelectionBackground = create('list-active-selection-background', '--vscode-list-activeSelectionBackground').withDefault('#094771'); +export const listActiveSelectionForeground = create('list-active-selection-foreground', '--vscode-list-activeSelectionForeground').withDefault('#ffffff'); +export const listHoverBackground = create('list-hover-background', '--vscode-list-hoverBackground').withDefault('#2a2d2e'); +export const quickInputBackground = create('quick-input-background', '--vscode-quickInput-background').withDefault('#252526'); /** * Divider design tokens. */ -export const dividerBackground = - create('divider-background').withDefault('#454545'); + +export const dividerBackground = create('divider-background', '--vscode-settings-dropdownListBorder').withDefault('#454545'); /** * Dropdown design tokens. */ -export const dropdownBackground = create( - 'dropdown-background' -).withDefault('#3c3c3c'); -export const dropdownForeground = create( - 'dropdown-foreground' -).withDefault('#f0f0f0'); -export const dropdownBorder = - create('dropdown-border').withDefault('#3c3c3c'); -export const dropdownListMaxHeight = create( - 'dropdown-list-max-height' -).withDefault('200px'); + +export const dropdownBackground = create('dropdown-background', '--vscode-dropdown-background').withDefault('#3c3c3c'); +export const dropdownBorder = create('dropdown-border', '--vscode-dropdown-border').withDefault('#3c3c3c'); +export const dropdownForeground = create('dropdown-foreground', '--vscode-dropdown-foreground').withDefault('#f0f0f0'); +export const dropdownListMaxHeight = create('dropdown-list-max-height').withDefault('200px'); /** * Text Field & Area design tokens. */ -export const inputBackground = - create('input-background').withDefault('#3c3c3c'); -export const inputForeground = - create('input-foreground').withDefault('#cccccc'); -export const inputPlaceholderForeground = create( - 'input-placeholder-foreground' -).withDefault('#cccccc'); + +export const inputBackground = create('input-background', '--vscode-input-background').withDefault('#3c3c3c'); +export const inputForeground = create('input-foreground', '--vscode-input-foreground').withDefault('#cccccc'); +export const inputPlaceholderForeground = create('input-placeholder-foreground', '--vscode-input-placeholderForeground').withDefault('#cccccc'); /** * Link design tokens. */ -export const linkForeground = create( - 'link-active-foreground' -).withDefault('#3794ff'); -export const linkActiveForeground = - create('link-foreground').withDefault('#3794ff'); + +export const linkActiveForeground = create('link-foreground', '--vscode-textLink-activeForeground').withDefault('#3794ff'); +export const linkForeground = create('link-active-foreground', '--vscode-textLink-foreground').withDefault('#3794ff'); /** - * Progress Bar & Ring design tokens. + * Progress ring design tokens. */ -export const progressBackground = create( - 'progress-background' -).withDefault('#0e70c0'); + +export const progressBackground = create('progress-background', '--vscode-progressBar-background').withDefault('#0e70c0'); /** * Panels design tokens. */ -export const panelViewBackground = create( - 'panel-view-background' -).withDefault('#1e1e1e'); -export const panelViewBorder = - create('panel-view-border').withDefault('#80808059'); -export const panelTabForeground = create( - 'panel-tab-foreground' -).withDefault('#e7e7e799'); -export const panelTabActiveForeground = create( - 'panel-tab-active-foreground' -).withDefault('#e7e7e7'); -export const panelTabActiveBorder = create( - 'panel-tab-active-border' -).withDefault('#e7e7e7'); + +export const panelTabActiveBorder = create('panel-tab-active-border', '--vscode-panelTitle-activeBorder').withDefault('#e7e7e7'); +export const panelTabActiveForeground = create('panel-tab-active-foreground', '--vscode-panelTitle-activeForeground').withDefault('#e7e7e7'); +export const panelTabForeground = create('panel-tab-foreground', '--vscode-panelTitle-inactiveForeground').withDefault('#e7e7e799'); +export const panelViewBackground = create('panel-view-background', '--vscode-panel-background').withDefault('#1e1e1e'); +export const panelViewBorder = create('panel-view-border', '--vscode-panel-border').withDefault('#80808059'); /** * Tag design tokens. */ -export const tagCornerRadius = - create('tag-corner-radius').withDefault('2px'); + +export const tagCornerRadius = create('tag-corner-radius').withDefault('2px'); diff --git a/src/index.ts b/src/index.ts index de39bc4e..c86dab17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,3 @@ export * from './radio/index'; export * from './tag/index'; export * from './text-area/index'; export * from './text-field/index'; - -// Export utility functions -export * from './utilities/theme/applyTheme'; diff --git a/src/utilities/design-tokens/create.ts b/src/utilities/design-tokens/create.ts new file mode 100644 index 00000000..cb9647c8 --- /dev/null +++ b/src/utilities/design-tokens/create.ts @@ -0,0 +1,54 @@ +import {CSSDesignToken, DesignToken} from '@microsoft/fast-foundation'; +import {initThemeChangeListener} from '../theme/applyTheme'; + +/** + * The possible CSSDesignToken generic types. + */ +export type T = + | string + | number + | boolean + | symbol + | any[] + | Uint8Array + | ({createCSS?(): string} & Record) + | null; + +/** + * A mapping of all the Visual Studio Code theme CSS variables mapped to the + * toolkit design tokens. + */ +export const tokenMappings: {[index: string]: CSSDesignToken} = {}; + +/** + * Boolean flag that ensures the VS Code theme listener is initialized once. + */ +let isThemeListenerInitialized = false; + +/** + * Given a design token name, return a new FAST CSSDesignToken. + * + * @remarks A VS Code theme CSS variable can be optionally passed to be + * associated with the design token. + * + * @remarks On the first execution the VS Code theme listener will also be + * initialized. + * + * @param name A design token name. + * @param vscodeThemeVar A VS Code theme CSS variable name to be associated with + * the design token. + * @returns A FAST CSSDesignToken that emits a CSS custom property. + */ +export function create(name: string, vscodeThemeVar?: string) { + const designToken = DesignToken.create(name); + + if (vscodeThemeVar) { + tokenMappings[vscodeThemeVar] = designToken; + } + if (!isThemeListenerInitialized) { + initThemeChangeListener(tokenMappings); + isThemeListenerInitialized = true; + } + + return designToken; +} diff --git a/src/utilities/theme/applyTheme.ts b/src/utilities/theme/applyTheme.ts index 2f440a0c..0d2764d0 100644 --- a/src/utilities/theme/applyTheme.ts +++ b/src/utilities/theme/applyTheme.ts @@ -1,27 +1,35 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import {CSSDesignToken} from '@microsoft/fast-foundation'; +import type {T} from '../design-tokens/create'; + /** - * This script configures a MutationObserver to watch for Visual Studio Code theme changes and + * Configures a MutationObserver to watch for Visual Studio Code theme changes and * applies the current Visual Studio Code theme to the toolkit components. */ +export function initThemeChangeListener(tokenMappings: { + [index: string]: CSSDesignToken; +}) { + window.addEventListener('load', () => { + const observer = new MutationObserver(() => { + applyCurrentTheme(tokenMappings); + }); + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class'], + }); -import {tokenMappings} from './tokenMappings'; - -window.addEventListener('load', () => { - const observer = new MutationObserver(applyCurrentTheme); - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class'], + applyCurrentTheme(tokenMappings); }); - - applyCurrentTheme(); -}); +} /** * Applies the current Visual Studio Code theme to the toolkit components. */ -function applyCurrentTheme() { +function applyCurrentTheme(tokenMappings: { + [index: string]: CSSDesignToken; +}) { // Get all the styles applied to the tag in the webview HTML // Importantly this includes all the CSS variables associated with the // current Visual Studio Code theme diff --git a/src/utilities/theme/tokenMappings.ts b/src/utilities/theme/tokenMappings.ts deleted file mode 100644 index 58c88b9b..00000000 --- a/src/utilities/theme/tokenMappings.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import {CSSDesignToken} from '@microsoft/fast-foundation'; -import { - badgeBackground, - badgeForeground, - buttonBorder, - buttonPrimaryBackground, - buttonPrimaryForeground, - buttonPrimaryHoverBackground, - buttonSecondaryBackground, - buttonSecondaryForeground, - buttonSecondaryHoverBackground, - checkboxBackground, - checkboxBorder, - checkboxForeground, - contrastActiveBorder, - contrastBorder, - dividerBackground, - dropdownBackground, - dropdownBorder, - dropdownForeground, - focusBorder, - fontFamily, - fontWeight, - foreground, - inputBackground, - inputForeground, - inputPlaceholderForeground, - linkActiveForeground, - linkForeground, - listActiveSelectionBackground, - listActiveSelectionForeground, - listHoverBackground, - panelTabActiveBorder, - panelTabActiveForeground, - panelTabForeground, - panelViewBackground, - panelViewBorder, - progressBackground, - quickInputBackground, - typeRampBaseFontSize, -} from '../../design-tokens'; - -/** - * A mapping of all the Visual Studio Code theme CSS variables mapped to the - * toolkit design tokens. - */ -export const tokenMappings: {[index: string]: CSSDesignToken} = { - '--vscode-badge-background': badgeBackground, - '--vscode-badge-foreground': badgeForeground, - '--vscode-button-border': buttonBorder, - '--vscode-button-background': buttonPrimaryBackground, - '--vscode-button-foreground': buttonPrimaryForeground, - '--vscode-button-hoverBackground': buttonPrimaryHoverBackground, - '--vscode-button-secondaryBackground': buttonSecondaryBackground, - '--vscode-button-secondaryForeground': buttonSecondaryForeground, - '--vscode-button-secondaryHoverBackground': buttonSecondaryHoverBackground, - '--vscode-checkbox-background': checkboxBackground, - '--vscode-checkbox-border': checkboxBorder, - '--vscode-checkbox-foreground': checkboxForeground, - '--vscode-contrastActiveBorder': contrastActiveBorder, - '--vscode-contrastBorder': contrastBorder, - '--vscode-dropdown-background': dropdownBackground, - '--vscode-dropdown-border': dropdownBorder, - '--vscode-dropdown-foreground': dropdownForeground, - '--vscode-focusBorder': focusBorder, - '--vscode-font-family': fontFamily, - '--vscode-font-size': typeRampBaseFontSize, - '--vscode-font-weight': fontWeight, - '--vscode-foreground': foreground, - '--vscode-input-background': inputBackground, - '--vscode-input-foreground': inputForeground, - '--vscode-input-placeholderForeground': inputPlaceholderForeground, - '--vscode-list-activeSelectionBackground': listActiveSelectionBackground, - '--vscode-list-activeSelectionForeground': listActiveSelectionForeground, - '--vscode-list-hoverBackground': listHoverBackground, - '--vscode-panel-background': panelViewBackground, - '--vscode-panel-border': panelViewBorder, - '--vscode-panelTitle-activeBorder': panelTabActiveBorder, - '--vscode-panelTitle-activeForeground': panelTabActiveForeground, - '--vscode-panelTitle-inactiveForeground': panelTabForeground, - '--vscode-progressBar-background': progressBackground, - '--vscode-quickInput-background': quickInputBackground, - '--vscode-settings-dropdownListBorder': dividerBackground, - '--vscode-textLink-activeForeground': linkActiveForeground, - '--vscode-textLink-foreground': linkForeground, -};