diff --git a/scripts/setup-webview-test-env.js b/scripts/setup-webview-test-env.js index eb6eabb6..1e95f371 100644 --- a/scripts/setup-webview-test-env.js +++ b/scripts/setup-webview-test-env.js @@ -14,7 +14,7 @@ async function main() { if (!fs.existsSync('./test-webview')) { try { console.log(color(['dim'], 'Copying webview test environment locally...')); - await execShellCommand('npx degit microsoft/vscode-webview-ui-toolkit-samples/all-components test-webview'); + await execShellCommand('npx degit microsoft/vscode-webview-ui-toolkit-samples/default/all-components test-webview'); } catch (err) { console.log(`${color(['red'], 'Error: Could not copy webview test environment locally')}\n ${err}`); process.exit(); diff --git a/src/button/button.styles.ts b/src/button/button.styles.ts index 9865a5c0..a054dd32 100644 --- a/src/button/button.styles.ts +++ b/src/button/button.styles.ts @@ -25,6 +25,7 @@ import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, + contrastActiveBorder, cornerRadius, designUnit, disabledOpacity, @@ -36,8 +37,24 @@ import { } from '../design-tokens'; /** + * Developer note: + * + * The prettier-ignore command is used on this block of code because when removed the + * '.control:${focusVisible}' CSS selector will be automatically reformatted to + * '.control: ${focusVisible}' (note the space between the colon and dollar sign). + * + * This results in non-valid CSS that will not render a focus outline on base buttons. + * + * Additionally, this prettier command must be declared on the entire code block and not + * directly above the CSS selector line because the below code block is a template literal + * string which will end up being used directly in the final component CSS. + * + * Thus having '// prettier-ignore' directly in the final CSS will also break the component + * styling. + * * @internal */ +// prettier-ignore const BaseButtonStyles = css` ${display('inline-flex')} :host { outline: none; @@ -76,7 +93,7 @@ const BaseButtonStyles = css` :host(:active) { background: ${buttonPrimaryBackground}; } - .control: ${focusVisible} { + .control:${focusVisible} { outline: calc(${borderWidth} * 1px) solid ${focusBorder}; outline-offset: calc(${borderWidth} * 2px); } @@ -161,9 +178,12 @@ const IconButtonStyles = css` } :host([appearance='icon']:hover) { background: ${buttonIconHoverBackground}; + outline: 1px dotted ${contrastActiveBorder}; + outline-offset: -1px; } :host([appearance='icon']) .control { padding: ${buttonIconPadding}; + border: calc(${borderWidth} * 1px) solid transparent; } :host([appearance='icon']:active) .control:active { background: ${buttonIconHoverBackground}; diff --git a/src/data-grid/data-grid-row.styles.ts b/src/data-grid/data-grid-row.styles.ts index 3aa1d922..e4ad4b5c 100644 --- a/src/data-grid/data-grid-row.styles.ts +++ b/src/data-grid/data-grid-row.styles.ts @@ -7,9 +7,10 @@ import { FoundationElementDefinition, } from '@microsoft/fast-foundation'; import { + background, + contrastActiveBorder, designUnit, listHoverBackground, - quickInputBackground, } from '../design-tokens'; export const dataGridRowStyles = ( @@ -26,11 +27,13 @@ export const dataGridRowStyles = ( :host(.header) { } :host(.sticky-header) { - background: ${quickInputBackground}; + background: ${background}; position: sticky; top: 0; } :host(:hover) { background: ${listHoverBackground}; + outline: 1px dotted ${contrastActiveBorder}; + outline-offset: -1px; } `; diff --git a/src/design-tokens.ts b/src/design-tokens.ts index 1a182dd1..7d2b191a 100644 --- a/src/design-tokens.ts +++ b/src/design-tokens.ts @@ -3,10 +3,28 @@ import {create} from './utilities/design-tokens/create'; +/** + * Developer note: + * + * There are some tokens defined in this file that make use of `--fake-vscode-token`. This is + * done when a toolkit token should be added to the tokenMappings map (and subsequently altered + * in the applyTheme function) but does not have a corresponding VS Code token that can be used. + * + * An example is buttonIconHoverBackground token which does not have a corresponding VS Code token + * at this time (it's a hardcoded value in VS Code), but needs to be adjusted to be transparent when a + * high contrast theme is applied. + * + * As a rule of thumb, if there are special cases where a token needs to be adjusted based on the + * VS Code theme and does not have a corresponding VS Code token, `--fake-vscode-token` can be used + * to indicate that it should be added to the tokenMappings map and thus make it accessible to the + * applyTheme function where it can be dynamically adjusted. + */ + /** * Global design tokens. */ +export const background = create('background', '--vscode-editor-background').withDefault('#1e1e1e'); export const borderWidth = create('border-width').withDefault(1); export const contrastActiveBorder = create('contrast-active-border', '--vscode-contrastActiveBorder').withDefault('#f38518'); export const contrastBorder = create('contrast-border', '--vscode-contrastBorder').withDefault('#6fc3df'); @@ -46,7 +64,8 @@ export const buttonBorder = create('button-border', '--vscode-button-bor 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)'); +// Note usage of `--fake-vscode-token` (refer to doc comment at top of file for explanation). +export const buttonIconHoverBackground = create('button-icon-hover-background', '--fake-vscode-token').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'); @@ -73,7 +92,6 @@ export const checkboxForeground = create('checkbox-foreground', '--vscod 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. diff --git a/src/utilities/design-tokens/create.ts b/src/utilities/design-tokens/create.ts index cb9647c8..66b6327d 100644 --- a/src/utilities/design-tokens/create.ts +++ b/src/utilities/design-tokens/create.ts @@ -18,7 +18,7 @@ export type T = * A mapping of all the Visual Studio Code theme CSS variables mapped to the * toolkit design tokens. */ -export const tokenMappings: {[index: string]: CSSDesignToken} = {}; +export const tokenMappings: Map> = new Map(); /** * Boolean flag that ensures the VS Code theme listener is initialized once. @@ -43,7 +43,13 @@ export function create(name: string, vscodeThemeVar?: string) { const designToken = DesignToken.create(name); if (vscodeThemeVar) { - tokenMappings[vscodeThemeVar] = designToken; + // If the fake vscode token is passed in, attach a unique ID to it so that it can + // be added to the tokenMappings map without overriding a previous fake token value + if (vscodeThemeVar.includes('--fake-vscode-token')) { + const uniqueId = 'id' + Math.random().toString(16).slice(2); + vscodeThemeVar = `${vscodeThemeVar}-${uniqueId}`; + } + tokenMappings.set(vscodeThemeVar, designToken); } if (!isThemeListenerInitialized) { initThemeChangeListener(tokenMappings); diff --git a/src/utilities/theme/applyTheme.ts b/src/utilities/theme/applyTheme.ts index 0d2764d0..66bfb03f 100644 --- a/src/utilities/theme/applyTheme.ts +++ b/src/utilities/theme/applyTheme.ts @@ -8,9 +8,9 @@ import type {T} from '../design-tokens/create'; * 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; -}) { +export function initThemeChangeListener( + tokenMappings: Map> +) { window.addEventListener('load', () => { const observer = new MutationObserver(() => { applyCurrentTheme(tokenMappings); @@ -27,21 +27,51 @@ export function initThemeChangeListener(tokenMappings: { /** * Applies the current Visual Studio Code theme to the toolkit components. */ -function applyCurrentTheme(tokenMappings: { - [index: string]: CSSDesignToken; -}) { +function applyCurrentTheme(tokenMappings: Map>) { // 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 const styles = getComputedStyle(document.body); + const body = document.querySelector('body'); - for (const vscodeTokenName in tokenMappings) { - const toolkitTokenName = tokenMappings[vscodeTokenName]; - const body = document.querySelector('body'); - const value = styles.getPropertyValue(vscodeTokenName).toString(); + if (body) { + const themeKind = body.getAttribute('data-vscode-theme-kind'); + for (const [vscodeTokenName, toolkitToken] of tokenMappings) { + let value = styles.getPropertyValue(vscodeTokenName).toString(); - if (body) { - toolkitTokenName.setValueFor(body, value); + // Handle a couple of styling edge cases when a high contrast theme is applied + if (themeKind === 'vscode-high-contrast') { + // Developer note: + // + // There are a handful of VS Code theme tokens that have no value when a high + // contrast theme is applied. + // + // This is an issue because when no value is set the toolkit tokens will fall + // back to their default color values (aka the VS Code dark theme color palette). + // This results in the backgrounds of a couple of components having default dark + // theme colors––thus breaking the high contrast theme. + // + // The below code, catches these tokens which have no value and are also background + // tokens, then overrides their value to be transparent. + if ( + value.length === 0 && + toolkitToken.name.includes('background') + ) { + value = 'transparent'; + } + + // Set icon button hover to be transparent in high contrast themes + if (toolkitToken.name === 'button-icon-hover-background') { + value = 'transparent'; + } + } else { + // Set contrast-active-border token to be transparent in non-high-contrast themes + if (toolkitToken.name === 'contrast-active-border') { + value = 'transparent'; + } + } + + toolkitToken.setValueFor(body, value); } } }