diff --git a/style/base.css b/style/base.css index b3c192fc..7134da09 100644 --- a/style/base.css +++ b/style/base.css @@ -364,13 +364,40 @@ .chat-mode-select { height: 24px; - background-color: initial; + + /* The footer's own background, so the select blends in as it did when this + was transparent; an explicit color (rather than transparent) is what + keeps the native popup from falling back to the light palette. */ + background-color: var(--jp-cell-editor-background, var(--jp-layout-color1)); color: var(--jp-ui-font-color1); padding: 0 5px; border-color: var(--jp-border-color0); outline: none; } +/* The native option list is drawn from the option's own colors, not the + select's, in some browsers (Firefox) and falls back to the light UI palette + otherwise. Without these, light theme text lands on a light popup in a dark + theme and the choices are unreadable. The fallbacks keep a theme that leaves + a variable out from bringing that back. */ + +/* The chat mode popup matches the select it belongs to, so the closed and open + states are the same tone. */ +.chat-mode-select option { + background-color: var( + --jp-cell-editor-background, + var(--jp-layout-color1, Canvas) + ); + color: var(--jp-ui-font-color0, CanvasText); +} + +.nbi-form-field select option, +.config-dialog-body select option, +.nbi-perf-control select option { + background-color: var(--jp-layout-color2, var(--jp-layout-color1, Canvas)); + color: var(--jp-ui-font-color0, CanvasText); +} + .permission-mode-container { position: relative; display: flex; diff --git a/tests/ts/select-theme-css.test.ts b/tests/ts/select-theme-css.test.ts new file mode 100644 index 00000000..bef627b7 --- /dev/null +++ b/tests/ts/select-theme-css.test.ts @@ -0,0 +1,66 @@ +// Copyright (c) Mehmet Bektas + +// The chat mode dropdown reset its background to `initial` while keeping a +// theme-derived text color, so in a dark JupyterLab theme the native option +// list rendered light text on a light popup. jsdom cannot render a native +// popup, so pin the stylesheet declarations that prevent it. +import * as fs from 'fs'; +import * as path from 'path'; + +const css = fs + .readFileSync(path.join(__dirname, '..', '..', 'style', 'base.css'), 'utf8') + .replace(/\/\*[\s\S]*?\*\//g, ''); + +function declarations(selector: string): string { + const rules = css + .split('}') + .map(chunk => chunk.split('{')) + .filter( + ([sel, body]) => + body !== undefined && + sel + .split(',') + .map(part => part.trim()) + .includes(selector) + ); + return rules.map(([, body]) => body).join('\n'); +} + +// What the chat mode select sits on, so it looks as it did when transparent. +const FOOTER_BACKGROUND = /var\(\s*--jp-cell-editor-background/; + +describe('select colors follow the JupyterLab theme', () => { + it('gives the chat mode select the footer background, not transparent', () => { + const body = declarations('.chat-mode-select'); + expect(body).toMatch( + new RegExp(`background-color:\\s*${FOOTER_BACKGROUND.source}`) + ); + expect(body).not.toMatch(/background-color:\s*initial/); + }); + + it('matches the chat mode popup to the select it belongs to, with fallbacks', () => { + const body = declarations('.chat-mode-select option'); + expect(body).toMatch( + /background-color:\s*var\(\s*--jp-cell-editor-background,\s*var\(\s*--jp-layout-color1,\s*Canvas\s*\)\s*\)/ + ); + expect(body).toMatch( + /\bcolor:\s*var\(\s*--jp-ui-font-color0,\s*CanvasText\s*\)/ + ); + }); + + it.each([ + '.nbi-form-field select option', + '.config-dialog-body select option', + '.nbi-perf-control select option' + ])('themes the option list for %s, with fallbacks', selector => { + const body = declarations(selector); + // A theme that omits --jp-layout-color2 must not leave the option + // transparent, which is the original bug. + expect(body).toMatch( + /background-color:\s*var\(\s*--jp-layout-color2,\s*var\(\s*--jp-layout-color1,\s*Canvas\s*\)\s*\)/ + ); + expect(body).toMatch( + /\bcolor:\s*var\(\s*--jp-ui-font-color0,\s*CanvasText\s*\)/ + ); + }); +});