Added new tabs UI and theme/display-density preferences

This commit is contained in:
Brian Vaughn
2019-02-12 20:41:39 -05:00
parent 8e5a35b762
commit d203ebf165
24 changed files with 684 additions and 79 deletions
+1
View File
@@ -1,4 +1,5 @@
{
"version": "4.0.0",
"repository": "bvaughn/react-devtools-experimental",
"license": "BSD-3-Clause",
"private": true,
+7 -6
View File
@@ -3,7 +3,7 @@
import { createElement } from 'react';
import { createRoot, flushSync } from 'react-dom';
import Bridge from 'src/bridge';
import Elements from 'src/devtools/views/Elements';
import DevTools from 'src/devtools/views/DevTools';
import inject from './inject';
const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0;
@@ -16,7 +16,7 @@ if (IS_CHROME) {
// chrome.devtools.panels added in Chrome 18.
// chrome.devtools.panels.themeName added in Chrome 54.
themeName = chrome.devtools.panels.themeName === 'dark' ? 'Dark' : 'Default';
themeName = chrome.devtools.panels.themeName === 'dark' ? 'dark' : 'light';
} else {
browserName = 'Firefox';
@@ -25,10 +25,10 @@ if (IS_CHROME) {
if (chrome.devtools && chrome.devtools.panels) {
switch (chrome.devtools.panels.themeName) {
case 'dark':
themeName = 'Dark';
themeName = 'dark';
break;
default:
themeName = 'Light';
themeName = 'light';
break;
}
}
@@ -63,10 +63,11 @@ function injectAndInit() {
const root = createRoot(container);
root.render(
createElement(Elements, {
createElement(DevTools, {
bridge,
browserName,
themeName,
browserTheme: themeName,
showTabBar: false,
})
);
+14 -3
View File
@@ -1,9 +1,13 @@
const { readFileSync } = require('fs');
const { resolve } = require('path');
const webpack = require('webpack');
const { DefinePlugin } = require('webpack');
const __DEV__ = process.env.NODE_ENV !== 'production';
const DEVTOOLS_VERSION = JSON.parse(
readFileSync(resolve(__dirname, '../../../package.json'))
).version;
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
@@ -24,12 +28,19 @@ module.exports = {
},
},
plugins: __DEV__
? []
? [
new DefinePlugin({
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
}),
]
: [
// Ensure we get production React
new webpack.DefinePlugin({
new DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
new DefinePlugin({
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
}),
],
module: {
rules: [
-1
View File
@@ -10,7 +10,6 @@
border: none;
}
#devtools {
display: flex;
height: 400px;
max-height: 50%;
overflow: hidden;
+4 -3
View File
@@ -6,7 +6,7 @@ import { createRoot } from 'react-dom';
import Bridge from 'src/bridge';
import { installHook } from 'src/hook';
import { initDevTools } from 'src/devtools';
import Elements from 'src/devtools/views/Elements';
import DevTools from 'src/devtools/views/DevTools';
const iframe = ((document.getElementById('target'): any): HTMLIFrameElement);
@@ -56,10 +56,11 @@ inject('./build/App.js', () => {
const root = createRoot(container);
const batch = root.createBatch();
batch.render(
createElement(Elements, {
createElement(DevTools, {
bridge,
browserName: 'Chrome',
themeName: 'light',
browserTheme: 'light',
showTabBar: true,
})
);
batch.then(() => {
+10
View File
@@ -1,5 +1,10 @@
const { readFileSync } = require('fs');
const { resolve } = require('path');
const { DefinePlugin } = require('webpack');
const DEVTOOLS_VERSION = JSON.parse(
readFileSync(resolve(__dirname, '../../package.json'))
).version;
// TODO Share Webpack configs like alias
@@ -20,6 +25,11 @@ module.exports = {
src: resolve(__dirname, '../../src'),
},
},
plugins: [
new DefinePlugin({
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
}),
],
module: {
rules: [
{
+64
View File
@@ -0,0 +1,64 @@
// @flow
import React, { useMemo, useState } from 'react';
import Store from '../store';
import { BridgeContext, StoreContext } from './context';
import Elements from './Elements';
import Profiler from './Profiler';
import Settings from './Settings';
import TabBar from './TabBar';
import { SettingsContextController } from './SettingsContext';
import { TreeContextController } from './TreeContext';
import './root.css';
import type { Bridge } from '../../types';
export type TabID = 'elements' | 'profiler' | 'settings';
export type BrowserTheme = 'dark' | 'light';
export type Props = {|
bridge: Bridge,
browserName: string,
defaultTab?: TabID,
browserTheme: BrowserTheme,
showTabBar?: boolean,
|};
export default function DevTools({
bridge,
browserName,
defaultTab = 'elements',
browserTheme = 'light',
showTabBar = false,
}: Props) {
const store = useMemo<Store>(() => new Store(bridge), []);
const [tab, setTab] = useState(defaultTab);
let tabElement;
switch (tab) {
case 'profiler':
tabElement = <Profiler />;
break;
case 'settings':
tabElement = <Settings />;
break;
case 'elements':
default:
tabElement = <Elements />;
break;
}
return (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
<SettingsContextController browserTheme={browserTheme}>
<TreeContextController>
{showTabBar && <TabBar currentTab={tab} selectTab={setTab} />}
{tabElement}
</TreeContextController>
</SettingsContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
);
}
+11 -30
View File
@@ -1,41 +1,22 @@
// @flow
import React, { useMemo } from 'react';
import Store from '../store';
import React from 'react';
import Tree from './Tree';
import { BridgeContext, StoreContext } from './context';
import SelectedElement from './SelectedElement';
import { TreeContextController } from './TreeContext';
import styles from './Elements.css';
import './root.css';
import type { Bridge } from '../../types';
export type Props = {|
bridge: Bridge,
browserName: string,
themeName: string,
|};
export default function Elements({ bridge, browserName, themeName }: Props) {
const store = useMemo<Store>(() => new Store(bridge), []);
export type Props = {||};
export default function Elements(_: Props) {
// TODO Flex wrappers below should be user resizable.
return (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
<TreeContextController>
<div className={styles.Elements}>
<div className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.SelectedElementWrapper}>
<SelectedElement />
</div>
</div>
</TreeContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
<div className={styles.Elements}>
<div className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.SelectedElementWrapper}>
<SelectedElement />
</div>
</div>
);
}
+21 -1
View File
@@ -5,7 +5,7 @@ import styles from './Icon.css';
type Props = {|
className?: string,
type: 'arrow' | 'search',
type: 'arrow' | 'elements' | 'profiler' | 'search' | 'settings',
|};
export default function Icon({ className = '', type }: Props) {
@@ -14,9 +14,18 @@ export default function Icon({ className = '', type }: Props) {
case 'arrow':
pathData = PATH_ARROW;
break;
case 'elements':
pathData = PATH_ELEMENTS;
break;
case 'profiler':
pathData = PATH_PROFILER;
break;
case 'search':
pathData = PATH_SEARCH;
break;
case 'settings':
pathData = PATH_SETTINGS;
break;
default:
console.warn(`Unsupported type "${type}" specified for Icon`);
break;
@@ -37,8 +46,19 @@ export default function Icon({ className = '', type }: Props) {
}
const PATH_ARROW = 'M8 5v14l11-7z';
const PATH_ELEMENTS =
'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z';
const PATH_PROFILER = 'M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z';
const PATH_SEARCH = `
M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91
16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99
5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z
`;
const PATH_SETTINGS = `
M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12
2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39
0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69
1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58
1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0
.49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z
`;
+1 -1
View File
@@ -29,7 +29,7 @@
margin-right: 0.5rem;
color: var(--color-component-name);
font-family: var(--font-family-monospace);
font-size: var(--font-size-normal);
font-size: var(--font-size-monospace-normal);
white-space: nowrap;
border-radius: 0.125rem;
}
+12
View File
@@ -0,0 +1,12 @@
.Profiler {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--color-border);
font-family: var(--font-family-sans);
font-size: var(--font-size-sans-normal);
background-color: var(--color-background);
color: var(--color-text-color);
}
+11
View File
@@ -0,0 +1,11 @@
// @flow
import React from 'react';
import styles from './Profiler.css';
export type Props = {||};
export default function Profiler(_: Props) {
return <div className={styles.Profiler}>Under development...</div>;
}
+5
View File
@@ -0,0 +1,5 @@
.ReactLogo {
width: 1.75rem;
height: 1.75rem;
margin: 0 0.75rem 0 0.25rem;
}
+22
View File
@@ -0,0 +1,22 @@
// @flow
import React from 'react';
import styles from './ReactLogo.css';
export default function ReactLogo() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.ReactLogo}
viewBox="-11.5 -10.23174 23 20.46348"
>
<circle cx="0" cy="0" r="2.05" fill="currentColor" />
<g stroke="currentColor" strokeWidth="1" fill="none">
<ellipse rx="11" ry="4.2" />
<ellipse rx="11" ry="4.2" transform="rotate(60)" />
<ellipse rx="11" ry="4.2" transform="rotate(120)" />
</g>
</svg>
);
}
+2 -2
View File
@@ -6,7 +6,7 @@
.Input {
flex: 1;
font-size: 1rem;
font-size: var(--font-size-sans-large);
outline: none;
border: none;
background-color: var(--color-background);
@@ -22,7 +22,7 @@
.IndexLabel {
color: var(--color-dim);
font-size: var(--font-size-normal);
font-size: var(--font-size-sans-normal);
}
.IconButton {
+2 -2
View File
@@ -11,7 +11,7 @@
flex: 0 0 42px;
display: flex;
align-items: center;
font-size: var(--font-size-large);
font-size: var(--font-size-monospace-large);
border-bottom: 1px solid var(--color-border);
padding: 0.5rem;
}
@@ -67,7 +67,7 @@
.InspectedElement {
overflow: auto;
font-family: var(--font-family-monospace);
font-size: var(--font-size-normal);
font-size: var(--font-size-monospace-normal);
line-height: var(--line-height-data);
}
+54
View File
@@ -0,0 +1,54 @@
.Settings {
width: 100%;
height: 100%;
padding: 0.5rem;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
border-top: 1px solid var(--color-border);
font-family: var(--font-family-sans);
font-size: var(--font-size-sans-normal);
background-color: var(--color-background);
color: var(--color-text-color);
}
.Section {
display: flex;
flex-direction: column;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.Header {
margin-bottom: 0.5rem;
font-size: var(--font-size-sans-large);
}
.OptionGroup {
display: flex;
flex-direction: row;
user-select: none;
}
.Option {
cursor: pointer;
padding: 0.5rem;
border: 1px solid var(--color-border);
border-right: none;
}
.Option:hover {
background-color: var(--color-tree-node-hover);
}
.Option:first-of-type {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.Option:last-of-type {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
border-right: 1px solid var(--color-border);
}
+93
View File
@@ -0,0 +1,93 @@
// @flow
import React, { useCallback, useContext } from 'react';
import { SettingsContext } from './SettingsContext';
import styles from './Settings.css';
export type Props = {||};
export default function Settings(_: Props) {
const { displayDensity, setDisplayDensity, theme, setTheme } = useContext(
SettingsContext
);
const updateDisplayDensity = useCallback(
({ currentTarget }) => {
setDisplayDensity(currentTarget.value);
},
[setDisplayDensity]
);
const updateTheme = useCallback(
({ currentTarget }) => {
setTheme(currentTarget.value);
},
[setTheme]
);
return (
<div className={styles.Settings}>
<div className={styles.Section}>
<div className={styles.Header}>Theme</div>
<div className={styles.OptionGroup}>
<label className={styles.Option}>
<input
type="radio"
name="Settings-Settings-theme"
checked={theme === 'auto'}
value="auto"
onChange={updateTheme}
/>{' '}
Auto
</label>
<label className={styles.Option}>
<input
type="radio"
name="Settings-theme"
checked={theme === 'light'}
value="light"
onChange={updateTheme}
/>{' '}
Light
</label>
<label className={styles.Option}>
<input
type="radio"
name="Settings-theme"
checked={theme === 'dark'}
value="dark"
onChange={updateTheme}
/>{' '}
Dark
</label>
</div>
</div>
<div className={styles.Section}>
<div className={styles.Header}>Display density</div>
<div className={styles.OptionGroup}>
<label className={styles.Option}>
<input
type="radio"
name="Settings-displayDensity"
checked={displayDensity === 'compact'}
value="compact"
onChange={updateDisplayDensity}
/>{' '}
Compact
</label>
<label className={styles.Option}>
<input
type="radio"
name="Settings-displayDensity"
checked={displayDensity === 'comfortable'}
value="comfortable"
onChange={updateDisplayDensity}
/>{' '}
Comfortable
</label>
</div>
</div>
</div>
);
}
+166
View File
@@ -0,0 +1,166 @@
// @flow
import React, { createContext, useLayoutEffect, useMemo } from 'react';
import { useLocalStorage } from './hooks';
import type { BrowserTheme } from './DevTools';
export type DisplayDensity = 'compact' | 'comfortable';
export type Theme = 'auto' | 'light' | 'dark';
type Context = {|
displayDensity: DisplayDensity,
setDisplayDensity(value: DisplayDensity): void,
theme: Theme,
setTheme(value: Theme): void,
|};
const SettingsContext = createContext<Context>(((null: any): Context));
// $FlowFixMe displayName is a valid attribute of React$ConsearchText
SettingsContext.displayName = 'SettingsContext';
type Props = {|
browserTheme: BrowserTheme,
children: React$Node,
|};
function SettingsContextController({ browserTheme, children }: Props) {
const [displayDensity, setDisplayDensity] = useLocalStorage<DisplayDensity>(
'displayDensity',
'compact'
);
const [theme, setTheme] = useLocalStorage<Theme>('theme', 'auto');
useLayoutEffect(() => {
switch (displayDensity) {
case 'compact':
updateDisplayDensity('compact');
break;
case 'comfortable':
updateDisplayDensity('comfortable');
break;
default:
throw Error(`Unsupported displayDensity value "${displayDensity}"`);
}
}, [displayDensity]);
useLayoutEffect(() => {
switch (theme) {
case 'light':
updateThemeVariables('light');
break;
case 'dark':
updateThemeVariables('dark');
break;
case 'auto':
updateThemeVariables(browserTheme);
break;
default:
throw Error(`Unsupported theme value "${theme}"`);
}
}, [theme]);
const value = useMemo(
() => ({
displayDensity,
setDisplayDensity,
theme,
setTheme,
}),
[displayDensity, setDisplayDensity, theme, setTheme]
);
return (
<SettingsContext.Provider value={value}>
{children}
</SettingsContext.Provider>
);
}
function setStyleVariable(name: string, value: string) {
(document.documentElement: any).style.setProperty(name, value);
}
function updateDisplayDensity(displayDensity: DisplayDensity): void {
setStyleVariable(
'--font-size-monospace-normal',
`var(--${displayDensity}-font-size-monospace-normal)`
);
setStyleVariable(
'--font-size-monospace-large',
`var(--${displayDensity}-font-size-monospace-large)`
);
setStyleVariable(
'--font-size-sans-normal',
`var(--${displayDensity}-font-size-sans-normal)`
);
setStyleVariable(
'--font-size-sans-large',
`var(--${displayDensity}-font-size-sans-large)`
);
setStyleVariable(
'--line-height-data',
`var(--${displayDensity}-line-height-data)`
);
}
function updateThemeVariables(theme: Theme): void {
setStyleVariable('--color-arrow', `var(--theme-${theme}-arrow)`);
setStyleVariable(
'--color-arrow-inverted',
`var(--theme-${theme}-arrow-inverted)`
);
setStyleVariable(
'--color-attribute-name',
`var(--theme-${theme}-attribute-name)`
);
setStyleVariable(
'--color-attribute-value',
`var(--theme-${theme}-attribute-value)`
);
setStyleVariable('--color-background', `var(--theme-${theme}-background)`);
setStyleVariable('--color-border', `var(--theme-${theme}-color-border)`);
setStyleVariable('--color-button', `var(--theme-${theme}-button)`);
setStyleVariable(
'--color-button-hover',
`var(--theme-${theme}-button-hover)`
);
setStyleVariable(
'--color-component-name',
`var(--theme-${theme}-component-name)`
);
setStyleVariable(
'--color-component-name-inverted',
`var(--theme-${theme}-component-name-inverted)`
);
setStyleVariable('--color-dim', `var(--theme-${theme}-dim)`);
setStyleVariable('--color-dimmer', `var(--theme-${theme}-dimmer)`);
setStyleVariable('--color-dimmest', `var(--theme-${theme}-dimmest)`);
setStyleVariable(
'--color-search-match',
`var(--theme-${theme}-search-match)`
);
setStyleVariable(
'--color-search-match-current',
`var(--theme-${theme}-search-match-current)`
);
setStyleVariable('--color-text-color', `var(--theme-${theme}-text-color)`);
setStyleVariable(
'--color-tree-jsx-arrow-brackets',
`var(--theme-${theme}-jsx-arrow-brackets)`
);
setStyleVariable(
'--color-tree-jsx-arrow-brackets-inverted',
`var(--theme-${theme}-jsx-arrow-brackets-inverted)`
);
setStyleVariable(
'--color-tree-node-selected',
`var(--theme-${theme}-tree-node-selected)`
);
setStyleVariable(
'--color-tree-node-hover',
`var(--theme-${theme}-tree-node-hover)`
);
}
export { SettingsContext, SettingsContextController };
+51
View File
@@ -0,0 +1,51 @@
.TabBar {
display: flex;
align-items: center;
padding: 0 0.5rem;
background-color: var(--color-background);
color: var(--color-tree-node-selected);
border-top: 1px solid var(--color-border);
font-family: var(--font-family-sans);
font-size: var(--font-size-sans-large);
}
.Tabs {
flex: 1 1 auto;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
overflow-x: auto;
}
.TabCurrent,
.Tab {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
cursor: pointer;
border-top: 3px solid transparent;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
}
.TabCurrent:hover,
.Tab:hover {
background-color: var(--color-tree-node-hover);
}
.TabCurrent {
border-bottom: 3px solid var(--color-tree-node-selected);
}
.Input {
width: 0;
margin: 0;
opacity: 0;
}
.Icon {
margin-right: 0.5rem;
width: 1.5rem;
height: 1.5rem;
}
+71
View File
@@ -0,0 +1,71 @@
// @flow
import React, { useCallback } from 'react';
import Icon from './Icon';
import ReactLogo from './ReactLogo';
import styles from './TabBar.css';
import type { TabID } from './DevTools';
export type Props = {|
currentTab: TabID,
selectTab: (tabID: TabID) => void,
|};
export default function TabBar({ currentTab, selectTab }: Props) {
const onChange = useCallback(
({ currentTarget }) => selectTab(currentTarget.value),
[selectTab]
);
return (
<div className={styles.TabBar}>
<ReactLogo /> DevTools {process.env.DEVTOOLS_VERSION}
<div className={styles.Tabs}>
<label
className={currentTab === 'elements' ? styles.TabCurrent : styles.Tab}
>
<input
type="radio"
name="TabBar-tab"
className={styles.Input}
checked={currentTab === 'elements'}
value="elements"
onChange={onChange}
/>
<Icon className={styles.Icon} type="elements" />
Elements
</label>
<label
className={currentTab === 'profiler' ? styles.TabCurrent : styles.Tab}
>
<input
type="radio"
name="TabBar-tab"
className={styles.Input}
checked={currentTab === 'profiler'}
value="profiler"
onChange={onChange}
/>
<Icon className={styles.Icon} type="profiler" />
Profiler
</label>
<label
className={currentTab === 'settings' ? styles.TabCurrent : styles.Tab}
>
<input
type="radio"
name="TabBar-tab"
className={styles.Input}
checked={currentTab === 'settings'}
value="settings"
onChange={onChange}
/>
<Icon className={styles.Icon} type="settings" />
Settings
</label>
</div>
</div>
);
}
+1 -2
View File
@@ -10,7 +10,6 @@
flex: 0 0 42px;
display: flex;
align-items: center;
font-size: var(--font-size-large);
border-bottom: 1px solid var(--color-border);
padding: 0.5rem;
}
@@ -24,7 +23,7 @@
.List {
font-family: var(--font-family-monospace);
font-size: var(--font-size-normal);
font-size: var(--font-size-monospace-normal);
line-height: var(--line-height-data);
}
+43
View File
@@ -0,0 +1,43 @@
// @flow
import { useState } from 'react';
// https://usehooks.com/useLocalStorage/
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | (() => T)) => void] {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = value => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
+18 -28
View File
@@ -1,4 +1,8 @@
:root {
/**
* IMPORTANT: When new theme variables are added below also add them to SettingsContext updateThemeVariables()
*/
/* Light theme */
--theme-light-arrow: #777d88;
--theme-light-arrow-inverted: #ffffff;
@@ -6,7 +10,7 @@
--theme-light-attribute-value: #1a1aa6;
--theme-light-background: #ffffff;
--theme-light-button: #0088fa;
--theme-light-button-hover: #1a1aa6;
--theme-light-button-hover: #3578e5;
--theme-light-color-border: #eeeeee;
--theme-light-component-name: #8155cb;
--theme-light-component-name-inverted: #ffffff;
@@ -41,35 +45,21 @@
--theme-dark-search-match-current: #f7923b;
--theme-dark-text-color: #ffffff;
--theme-dark-tree-node-selected: #31a38d;
--theme-dark-tree-node-hover: #23272f;
--theme-dark-tree-node-hover: #303846;
/* "theme" values referenced by other stylesheets */
--color-arrow: var(--theme-light-arrow);
--color-arrow-inverted: var(--theme-light-arrow-inverted);
--color-attribute-name: var(--theme-light-attribute-name);
--color-attribute-value: var(--theme-light-attribute-value);
--color-background: var(--theme-light-background);
--color-border: var(--theme-light-color-border);
--color-button: var(--theme-light-button);
--color-button-hover: var(--theme-light-button-hover);
--color-component-name: var(--theme-light-component-name);
--color-component-name-inverted: var(--theme-light-component-name-inverted);
--color-dim: var(--theme-light-dim);
--color-dimmer: var(--theme-light-dimmer);
--color-dimmest: var(--theme-light-dimmest);
--color-search-match: var(--theme-light-search-match);
--color-search-match-current: var(--theme-light-search-match-current);
--color-text-color: var(--theme-light-text-color);
--color-tree-jsx-arrow-brackets: var(--theme-light-jsx-arrow-brackets);
--color-tree-jsx-arrow-brackets-inverted: var(
--theme-light-jsx-arrow-brackets-inverted
);
--color-tree-node-selected: var(--theme-light-tree-node-selected);
--color-tree-node-hover: var(--theme-light-tree-node-hover);
/* Compact density */
--compact-font-size-monospace-normal: 11px;
--compact-font-size-monospace-large: 15px;
--compact-font-size-sans-normal: 12px;
--compact-font-size-sans-large: 14px;
--compact-line-height-data: 18px;
--font-size-normal: 11px;
--font-size-large: 15px;
--line-height-data: 18px;
/* Comfortable density */
--comfortable-font-size-monospace-normal: 13px;
--comfortable-font-size-monospace-large: 17px;
--comfortable-font-size-sans-normal: 14px;
--comfortable-font-size-sans-large: 16px;
--comfortable-line-height-data: 22px;
/* GitHub.com system fonts */
--font-family-monospace: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo,