chore(html): add html settings dialog (#37565)
This commit is contained in:
		
							parent
							
								
									ef9ce58723
								
							
						
					
					
						commit
						888da0f89d
					
				|  | @ -454,8 +454,12 @@ SOFTWARE. */ | |||
|   --color-scale-coral-9: #510901 | ||||
| } | ||||
| 
 | ||||
| @media(prefers-color-scheme: dark) { | ||||
|   :root { | ||||
| :root.light-mode { | ||||
|   color-scheme: light; | ||||
| } | ||||
| 
 | ||||
| :root.dark-mode { | ||||
|   color-scheme: dark; | ||||
|   --color-canvas-default-transparent: rgba(13,17,23,0); | ||||
|   --color-marketing-icon-primary: #79c0ff; | ||||
|   --color-marketing-icon-secondary: #1f6feb; | ||||
|  | @ -887,5 +891,4 @@ SOFTWARE. */ | |||
|   --color-scale-coral-7: #872012; | ||||
|   --color-scale-coral-8: #640D04; | ||||
|   --color-scale-coral-9: #460701 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -40,6 +40,12 @@ svg { | |||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .cursor-pointer, | ||||
| .cursor-pointer > * { | ||||
|   cursor: pointer; | ||||
|   user-select: none; | ||||
| } | ||||
| 
 | ||||
| .hbox { | ||||
|   display: flex; | ||||
|   flex: auto; | ||||
|  | @ -296,6 +302,22 @@ article, aside, details, figcaption, figure, footer, header, main, menu, nav, se | |||
|   background-color: var(--color-btn-hover-bg); | ||||
| } | ||||
| 
 | ||||
| input[type="checkbox"] { | ||||
|   outline: var(--color-focus-border); | ||||
|   height: 24px; | ||||
| } | ||||
| 
 | ||||
| dialog { | ||||
|   background-color: var(--color-canvas-subtle); | ||||
|   border: 1px solid var(--color-border-default);   | ||||
|   border-radius: 6px; | ||||
|   padding: 6px; | ||||
| } | ||||
| 
 | ||||
| .subnav-item .octicon.octicon-settings { | ||||
|   margin-right: 0; | ||||
| } | ||||
| 
 | ||||
| @media only screen and (max-width: 600px) { | ||||
|   .subnav-item, .form-control { | ||||
|     border-radius: 0 !important; | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ import { Link, navigate, SearchParamsContext } from './links'; | |||
| import { statusIcon } from './statusIcon'; | ||||
| import { filterWithQuery } from './filter'; | ||||
| import { linkifyText } from '@web/renderUtils'; | ||||
| import { Dialog } from '@web/shared/dialog'; | ||||
| import { useDarkModeSetting } from '@web/theme'; | ||||
| 
 | ||||
| export const HeaderView: React.FC<{ | ||||
|   title: string | undefined, | ||||
|  | @ -90,6 +92,7 @@ const StatsNavView: React.FC<{ | |||
|     <NavLink token='failed' count={stats.unexpected} /> | ||||
|     <NavLink token='flaky' count={stats.flaky} /> | ||||
|     <NavLink token='skipped' count={stats.skipped} /> | ||||
|     <SettingsButton /> | ||||
|   </nav>; | ||||
| }; | ||||
| 
 | ||||
|  | @ -112,3 +115,42 @@ const NavLink: React.FC<{ | |||
|     <span className='d-inline counter'>{count}</span> | ||||
|   </Link>; | ||||
| }; | ||||
| 
 | ||||
| const SettingsButton: React.FC = () => { | ||||
|   const settingsRef = React.useRef<HTMLAnchorElement>(null); | ||||
|   const [settingsOpen, setSettingsOpen] = React.useState(false); | ||||
|   const [darkMode, setDarkMode] = useDarkModeSetting(); | ||||
| 
 | ||||
|   return <> | ||||
|     <Dialog | ||||
|       open={settingsOpen} | ||||
|       width={200} | ||||
|       verticalOffset={8} | ||||
|       requestClose={() => setSettingsOpen(false)} | ||||
|       anchor={settingsRef} | ||||
|       dataTestId='settings-dialog' | ||||
|     > | ||||
|       <div className='hbox cursor-pointer' style={{ alignItems: 'center' }}> | ||||
|         <input type='checkbox' id='dark-mode-setting' checked={darkMode} onChange={() => setDarkMode(!darkMode)}></input> | ||||
|         <label htmlFor='dark-mode-setting'>Dark mode</label> | ||||
|       </div> | ||||
|     </Dialog> | ||||
|     <a | ||||
|       ref={settingsRef} | ||||
|       style={{ cursor: 'pointer' }} | ||||
|       className='subnav-item' | ||||
|       title='Settings' | ||||
|       onClick={e => { | ||||
|         setSettingsOpen(!settingsOpen); | ||||
|         e.preventDefault(); | ||||
|       }} | ||||
|       onMouseDown={preventDefault}> | ||||
|       {icons.settings()} | ||||
|     </a> | ||||
|   </>; | ||||
| }; | ||||
| 
 | ||||
| const preventDefault = (e: any) => { | ||||
|   e.stopPropagation(); | ||||
|   e.preventDefault(); | ||||
| }; | ||||
|  |  | |||
|  | @ -103,3 +103,9 @@ export const copy = () => { | |||
|     <path d='M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z'></path> | ||||
|   </svg>; | ||||
| }; | ||||
| 
 | ||||
| export const settings = () => { | ||||
|   return <svg className='octicon octicon-settings' viewBox='0 0 16 16' width='16' height='16' aria-hidden='true'> | ||||
|     <path d='M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z'></path> | ||||
|   </svg>; | ||||
| }; | ||||
|  |  | |||
|  | @ -28,6 +28,8 @@ const zipjs = zipImport as typeof zip; | |||
| 
 | ||||
| import logo from '@web/assets/playwright-logo.svg'; | ||||
| import { SearchParamsProvider } from './links'; | ||||
| import { applyTheme } from '@web/theme'; | ||||
| 
 | ||||
| const link = document.createElement('link'); | ||||
| link.rel = 'shortcut icon'; | ||||
| link.href = logo; | ||||
|  | @ -49,6 +51,7 @@ const ReportLoader: React.FC = () => { | |||
| }; | ||||
| 
 | ||||
| window.onload = () => { | ||||
|   applyTheme(); | ||||
|   ReactDOM.createRoot(document.querySelector('#root')!).render(<ReportLoader />); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ | |||
|   color: var(--color-fg-default); | ||||
| } | ||||
| 
 | ||||
| @media(prefers-color-scheme: light) { | ||||
| :root.light-mode { | ||||
|   .label-color-0 { | ||||
|     background-color: var(--color-scale-blue-0); | ||||
|     color: var(--color-scale-blue-6); | ||||
|  | @ -68,7 +68,7 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| @media(prefers-color-scheme: dark) { | ||||
| :root.dark-mode { | ||||
|   .label-color-0 { | ||||
|     background-color: var(--color-scale-blue-9); | ||||
|     color: var(--color-scale-blue-2); | ||||
|  |  | |||
|  | @ -45,13 +45,13 @@ | |||
|   padding: 2px 8px; | ||||
| } | ||||
| 
 | ||||
| @media(prefers-color-scheme: light) { | ||||
| :root.light-mode { | ||||
|   .test-result-counter { | ||||
|     background: var(--color-scale-gray-5); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media(prefers-color-scheme: dark) { | ||||
| :root.dark-mode { | ||||
|   .test-result-counter { | ||||
|     background: var(--color-scale-gray-3); | ||||
|   } | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ | |||
|   color: #a1260d; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .recorder .toolbar-button.toggled.circle-large-filled { | ||||
| :root:dark-mode .recorder .toolbar-button.toggled.circle-large-filled { | ||||
|   color: #f48771; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ import { useDarkModeSetting } from '@web/theme'; | |||
| import { copy, useSetting } from '@web/uiUtils'; | ||||
| import yaml from 'yaml'; | ||||
| import { parseAriaSnapshot } from '@isomorphic/ariaSnapshot'; | ||||
| import { Dialog } from '@web/components/dialog'; | ||||
| import { Dialog } from '@web/shared/dialog'; | ||||
| 
 | ||||
| export interface RecorderProps { | ||||
|   sources: Source[], | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ | |||
|   height: var(--browser-frame-header-height); | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .browser-frame-header { | ||||
| :root:dark-mode .browser-frame-header { | ||||
|   background: #444950; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -108,7 +108,7 @@ | |||
|   --action-background-color: #1a85ff66; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .timeline-bar.action.error { | ||||
| :root:dark-mode .timeline-bar.action.error { | ||||
|   --action-color: var(--vscode-errorForeground); | ||||
|   --action-background-color: #f4877166; | ||||
| } | ||||
|  | @ -162,7 +162,7 @@ body.dark-mode .timeline-bar.action.error { | |||
|   background-color: #3879d91a; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .timeline-window-curtain { | ||||
| :root:dark-mode .timeline-window-curtain { | ||||
|   background-color: #161718bf; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ import type { HighlightedElement } from './snapshotTab'; | |||
| import type { TestAnnotation } from '@playwright/test'; | ||||
| import { MetadataWithCommitInfo } from '@testIsomorphic/types'; | ||||
| import type { ActionGroup } from '@isomorphic/protocolFormatter'; | ||||
| import { DialogToolbarButton } from '@web/components/dialog'; | ||||
| import { DialogToolbarButton } from '@web/components/dialogToolbarButton'; | ||||
| import { SettingsView } from './settingsView'; | ||||
| 
 | ||||
| export const Workbench: React.FunctionComponent<{ | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ body .drop-target { | |||
|   background: rgba(255, 255, 255, 0.8); | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .drop-target { | ||||
| :root:dark-mode .drop-target { | ||||
|   background: rgba(0, 0, 0, 0.8); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,7 +20,8 @@ import { MultiTraceModel } from './modelUtil'; | |||
| import './workbenchLoader.css'; | ||||
| import { Workbench } from './workbench'; | ||||
| import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection'; | ||||
| import { Dialog, DialogToolbarButton } from '@web/components/dialog'; | ||||
| import { DialogToolbarButton } from '@web/components/dialogToolbarButton'; | ||||
| import { Dialog } from '@web/shared/dialog'; | ||||
| import { DefaultSettingsView } from './defaultSettingsView'; | ||||
| 
 | ||||
| export const WorkbenchLoader: React.FunctionComponent<{ | ||||
|  |  | |||
|  | @ -79,33 +79,33 @@ | |||
|   color: #267f99; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-def, | ||||
| body.dark-mode .CodeMirror span.cm-tag { | ||||
| :root:dark-mode .CodeMirror span.cm-def, | ||||
| :root:dark-mode .CodeMirror span.cm-tag { | ||||
|   color: var(--vscode-debugView-valueChangedHighlight); | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-comment, | ||||
| body.dark-mode .CodeMirror span.cm-link { | ||||
| :root:dark-mode .CodeMirror span.cm-comment, | ||||
| :root:dark-mode .CodeMirror span.cm-link { | ||||
|   color: #6a9955; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-variable, | ||||
| body.dark-mode .CodeMirror span.cm-variable-2, | ||||
| body.dark-mode .CodeMirror span.cm-atom { | ||||
| :root:dark-mode .CodeMirror span.cm-variable, | ||||
| :root:dark-mode .CodeMirror span.cm-variable-2, | ||||
| :root:dark-mode .CodeMirror span.cm-atom { | ||||
|   color: #4fc1ff; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-property { | ||||
| :root:dark-mode .CodeMirror span.cm-property { | ||||
|   color: #dcdcaa; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-qualifier, | ||||
| body.dark-mode .CodeMirror span.cm-attribute { | ||||
| :root:dark-mode .CodeMirror span.cm-qualifier, | ||||
| :root:dark-mode .CodeMirror span.cm-attribute { | ||||
|   color: #9cdcfe; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode .CodeMirror span.cm-variable-3, | ||||
| body.dark-mode .CodeMirror span.cm-type { | ||||
| :root:dark-mode .CodeMirror span.cm-variable-3, | ||||
| :root:dark-mode .CodeMirror span.cm-type { | ||||
|   color: #4ec9b0; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,55 @@ | |||
| /* | ||||
|   Copyright (c) Microsoft Corporation. | ||||
| 
 | ||||
|   Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|   you may not use this file except in compliance with the License. | ||||
|   You may obtain a copy of the License at | ||||
| 
 | ||||
|       http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
|   Unless required by applicable law or agreed to in writing, software | ||||
|   distributed under the License is distributed on an "AS IS" BASIS, | ||||
|   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|   See the License for the specific language governing permissions and | ||||
|   limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| import * as React from 'react'; | ||||
| import { ToolbarButton } from './toolbarButton'; | ||||
| import { Dialog } from '../shared/dialog'; | ||||
| 
 | ||||
| export interface DialogToolbarButtonProps { | ||||
|   title?: string; | ||||
|   icon?: string; | ||||
|   dialogDataTestId?: string; | ||||
| } | ||||
| 
 | ||||
| export const DialogToolbarButton: React.FC<React.PropsWithChildren<DialogToolbarButtonProps>> = ({ title, icon, dialogDataTestId, children }) => { | ||||
|   const hostingRef = React.useRef<HTMLButtonElement>(null); | ||||
|   const [open, setOpen] = React.useState(false); | ||||
|   return ( | ||||
|     <> | ||||
|       <ToolbarButton | ||||
|         ref={hostingRef} | ||||
|         icon={icon} | ||||
|         title={title} | ||||
|         onClick={() => setOpen(current => !current)} | ||||
|       /> | ||||
|       <Dialog | ||||
|         style={{ | ||||
|           backgroundColor: 'var(--vscode-sideBar-background)', | ||||
|           padding: '4px 8px' | ||||
|         }} | ||||
|         open={open} | ||||
|         width={200} | ||||
|         // TODO: Temporary spacing until design of toolbar buttons is revisited
 | ||||
|         verticalOffset={8} | ||||
|         requestClose={() => setOpen(false)} | ||||
|         anchor={hostingRef} | ||||
|         dataTestId={dialogDataTestId} | ||||
|       > | ||||
|         {children} | ||||
|       </Dialog> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | @ -15,7 +15,6 @@ | |||
| */ | ||||
| 
 | ||||
| import * as React from 'react'; | ||||
| import { ToolbarButton } from './toolbarButton'; | ||||
| 
 | ||||
| export interface DialogProps { | ||||
|   className?: string; | ||||
|  | @ -164,39 +163,3 @@ const buildTopLeftCoordWithAlignment = ( | |||
|     }; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export interface DialogToolbarButtonProps { | ||||
|   title?: string; | ||||
|   icon?: string; | ||||
|   dialogDataTestId?: string; | ||||
| } | ||||
| 
 | ||||
| export const DialogToolbarButton: React.FC<React.PropsWithChildren<DialogToolbarButtonProps>> = ({ title, icon, dialogDataTestId, children }) => { | ||||
|   const hostingRef = React.useRef<HTMLButtonElement>(null); | ||||
|   const [open, setOpen] = React.useState(false); | ||||
|   return ( | ||||
|     <> | ||||
|       <ToolbarButton | ||||
|         ref={hostingRef} | ||||
|         icon={icon} | ||||
|         title={title} | ||||
|         onClick={() => setOpen(current => !current)} | ||||
|       /> | ||||
|       <Dialog | ||||
|         style={{ | ||||
|           backgroundColor: 'var(--vscode-sideBar-background)', | ||||
|           padding: '4px 8px' | ||||
|         }} | ||||
|         open={open} | ||||
|         width={200} | ||||
|         // TODO: Temporary spacing until design of toolbar buttons is revisited
 | ||||
|         verticalOffset={8} | ||||
|         requestClose={() => setOpen(false)} | ||||
|         anchor={hostingRef} | ||||
|         dataTestId={dialogDataTestId} | ||||
|       > | ||||
|         {children} | ||||
|       </Dialog> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | @ -41,7 +41,9 @@ export function applyTheme() { | |||
| 
 | ||||
|   const currentTheme = settings.getString('theme', defaultTheme); | ||||
|   if (currentTheme === 'dark-mode') | ||||
|     document.body.classList.add('dark-mode'); | ||||
|     document.documentElement.classList.add('dark-mode'); | ||||
|   else | ||||
|     document.documentElement.classList.add('light-mode'); | ||||
| } | ||||
| 
 | ||||
| type Theme = 'dark-mode' | 'light-mode'; | ||||
|  | @ -52,8 +54,8 @@ export function toggleTheme() { | |||
|   const newTheme = oldTheme === 'dark-mode' ? 'light-mode' : 'dark-mode'; | ||||
| 
 | ||||
|   if (oldTheme) | ||||
|     document.body.classList.remove(oldTheme); | ||||
|   document.body.classList.add(newTheme); | ||||
|     document.documentElement.classList.remove(oldTheme); | ||||
|   document.documentElement.classList.add(newTheme); | ||||
|   settings.setString('theme', newTheme); | ||||
|   for (const listener of listeners) | ||||
|     listener(newTheme); | ||||
|  | @ -68,7 +70,7 @@ export function removeThemeListener(listener: (theme: Theme) => void) { | |||
| } | ||||
| 
 | ||||
| export function currentTheme(): Theme { | ||||
|   return document.body.classList.contains('dark-mode') ? 'dark-mode' : 'light-mode'; | ||||
|   return document.documentElement.classList.contains('dark-mode') ? 'dark-mode' : 'light-mode'; | ||||
| } | ||||
| 
 | ||||
| export function useDarkModeSetting(): [boolean, (value: boolean) => void] { | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
|  *  Licensed under the MIT License. See License.txt in the project root for license information. | ||||
|  *--------------------------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| body { | ||||
| :root { | ||||
|   --vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; | ||||
|   --vscode-font-weight: normal; | ||||
|   --vscode-font-size: 13px; | ||||
|  | @ -544,13 +544,12 @@ body { | |||
|   --vscode-gitDecoration-submoduleResourceForeground: #1258a7; | ||||
| } | ||||
| 
 | ||||
| body.light-mode { | ||||
| :root.light-mode { | ||||
|   color-scheme: light; | ||||
| } | ||||
| 
 | ||||
| body.dark-mode { | ||||
| :root.dark-mode { | ||||
|   color-scheme: dark; | ||||
| 
 | ||||
|   --vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; | ||||
|   --vscode-font-weight: normal; | ||||
|   --vscode-font-size: 13px; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue