mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Preferences page redesign (#2446)
* Removing header part Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Restyling PageLayout Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Restyling .round-black Input Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding Tab navigation to Preferences Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling Application tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add esc button Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Add media queries Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Introducting Switcher component Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling Proxy tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Moving start-up switcher to Other tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling Kubernetes tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling Extensions tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling inputs and selects Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Styling helm chart section Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Create a telemetry tab with extensions Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding lens Select theme Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Remove Other tab Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fix mainBackground color Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Simplifying Tabs boilerplate Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Replacing button font Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing one-column settings layout Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing integration tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixin tests harder Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Showing bottom bar in workspaces Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
33c405bdcf
commit
84cc0cdf55
@ -8,6 +8,7 @@ export default class SurveyRendererExtension extends LensRendererExtension {
|
||||
appPreferences = [
|
||||
{
|
||||
title: "In-App Surveys",
|
||||
showInPreferencesTab: "telemetry",
|
||||
components: {
|
||||
Hint: () => <SurveyPreferenceHint/>,
|
||||
Input: () => <SurveyPreferenceInput survey={surveyPreferencesStore}/>
|
||||
|
||||
@ -8,6 +8,7 @@ export default class TelemetryRendererExtension extends LensRendererExtension {
|
||||
appPreferences = [
|
||||
{
|
||||
title: "Telemetry & Usage Tracking",
|
||||
showInPreferencesTab: "telemetry",
|
||||
id: "telemetry-tracking",
|
||||
components: {
|
||||
Hint: () => <TelemetryPreferenceHint/>,
|
||||
|
||||
@ -36,7 +36,7 @@ describe("Lens integration tests", () => {
|
||||
|
||||
it('shows "add cluster"', async () => {
|
||||
await app.electron.ipcRenderer.send("test-menu-item-click", "File", "Add Cluster");
|
||||
await app.client.waitUntilTextExists("h2", "Add Cluster");
|
||||
await app.client.waitUntilTextExists("h2", "Add Clusters from Kubeconfig");
|
||||
});
|
||||
|
||||
describe("preferences page", () => {
|
||||
@ -44,7 +44,17 @@ describe("Lens integration tests", () => {
|
||||
const appName: string = process.platform === "darwin" ? "Lens" : "File";
|
||||
|
||||
await app.electron.ipcRenderer.send("test-menu-item-click", appName, "Preferences");
|
||||
await app.client.waitUntilTextExists("h2", "Preferences");
|
||||
await app.client.waitUntilTextExists("[data-testid=application-header]", "APPLICATION");
|
||||
});
|
||||
|
||||
it("shows all tabs and their contents", async () => {
|
||||
await app.client.click("[data-testid=application-tab]");
|
||||
await app.client.click("[data-testid=proxy-tab]");
|
||||
await app.client.waitUntilTextExists("[data-testid=proxy-header]", "PROXY");
|
||||
await app.client.click("[data-testid=kube-tab]");
|
||||
await app.client.waitUntilTextExists("[data-testid=kubernetes-header]", "KUBERNETES");
|
||||
await app.client.click("[data-testid=telemetry-tab]");
|
||||
await app.client.waitUntilTextExists("[data-testid=telemetry-header]", "TELEMETRY");
|
||||
});
|
||||
|
||||
it("ensures helm repos", async () => {
|
||||
@ -54,7 +64,8 @@ describe("Lens integration tests", () => {
|
||||
fail("Lens failed to add Bitnami repository");
|
||||
}
|
||||
|
||||
await app.client.waitUntilTextExists("div.repos #message-bitnami", repos[0].name); // wait for the helm-cli to fetch the repo(s)
|
||||
await app.client.click("[data-testid=kube-tab]");
|
||||
await app.client.waitUntilTextExists("div.repos .repoName", repos[0].name); // wait for the helm-cli to fetch the repo(s)
|
||||
await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down
|
||||
await app.client.waitUntilTextExists("div.Select__option", ""); // wait for at least one option to appear (any text)
|
||||
});
|
||||
|
||||
@ -80,7 +80,7 @@ export async function appStart() {
|
||||
export async function clickWhatsNew(app: Application) {
|
||||
await app.client.waitUntilTextExists("h1", "What's new?");
|
||||
await app.client.click("button.primary");
|
||||
await app.client.waitUntilTextExists("h2", "default");
|
||||
await app.client.waitUntilTextExists("h5", "Clusters");
|
||||
}
|
||||
|
||||
export async function clickWelcomeNotification(app: Application) {
|
||||
@ -89,7 +89,7 @@ export async function clickWelcomeNotification(app: Application) {
|
||||
if (itemsText === "0 item") {
|
||||
// welcome notification should be present, dismiss it
|
||||
await app.client.waitUntilTextExists("div.message", "Welcome!");
|
||||
await app.client.click("i.Icon.close");
|
||||
await app.client.click(".notification i.Icon.close");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ export interface AppPreferenceComponents {
|
||||
export interface AppPreferenceRegistration {
|
||||
title: string;
|
||||
id?: string;
|
||||
showInPreferencesTab?: string;
|
||||
components: AppPreferenceComponents;
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,9 @@
|
||||
--width: 100%;
|
||||
--height: 100%;
|
||||
text-align: center;
|
||||
|
||||
|
||||
bottom: 22px; // Making bottom bar visible
|
||||
|
||||
.content-wrapper {
|
||||
|
||||
.content {
|
||||
margin: unset;
|
||||
max-width: unset;
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
.HelmCharts {
|
||||
.repos {
|
||||
margin-top: var(--margin);
|
||||
margin-top: 20px;
|
||||
|
||||
.Badge {
|
||||
display: flex;
|
||||
margin-bottom: 1px!important;
|
||||
padding: 6px 8px;
|
||||
.repo {
|
||||
background: var(--inputControlBackground);
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 0 0 1px var(--secondaryBackground);
|
||||
|
||||
.repoName {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.repoUrl {
|
||||
color: var(--textColorDimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,12 +4,10 @@ import React from "react";
|
||||
import { action, computed, observable } from "mobx";
|
||||
|
||||
import { HelmRepo, repoManager } from "../../../main/helm/helm-repo-manager";
|
||||
import { Badge } from "../badge";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { AddHelmRepoDialog } from "./add-helm-repo-dialog";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
@ -106,6 +104,7 @@ export class HelmCharts extends React.Component {
|
||||
formatOptionLabel={this.formatOptionLabel}
|
||||
controlShouldRenderValue={false}
|
||||
className="box grow"
|
||||
themeName="lens"
|
||||
/>
|
||||
<Button
|
||||
primary
|
||||
@ -116,20 +115,18 @@ export class HelmCharts extends React.Component {
|
||||
<AddHelmRepoDialog onAddRepo={() => this.loadRepos()}/>
|
||||
<div className="repos flex gaps column">
|
||||
{Array.from(this.addedRepos).map(([name, repo]) => {
|
||||
const tooltipId = `message-${name}`;
|
||||
|
||||
return (
|
||||
<Badge key={name} className="added-repo flex gaps align-center justify-space-between">
|
||||
<span id={tooltipId} className="repo">{name}</span>
|
||||
<div key={name} className="repo flex gaps align-center justify-space-between">
|
||||
<div>
|
||||
<div className="repoName">{name}</div>
|
||||
<div className="repoUrl">{repo.url}</div>
|
||||
</div>
|
||||
<Icon
|
||||
material="delete"
|
||||
onClick={() => this.removeRepo(repo)}
|
||||
tooltip="Remove"
|
||||
/>
|
||||
<Tooltip targetId={tooltipId} formatters={{ narrow: true }}>
|
||||
{repo.url}
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { Input, InputValidators } from "../input";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { UserPreferences, userStore } from "../../../common/user-store";
|
||||
import { observer } from "mobx-react";
|
||||
import { bundledKubectlPath } from "../../../main/kubectl";
|
||||
import { SelectOption, Select } from "../select";
|
||||
import { FormSwitch, Switcher } from "../switch";
|
||||
|
||||
export const KubectlBinaries = observer(({ preferences }: { preferences: UserPreferences }) => {
|
||||
const [downloadPath, setDownloadPath] = useState(preferences.downloadBinariesPath || "");
|
||||
@ -24,46 +24,66 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Automatic kubectl binary download"/>
|
||||
<Checkbox
|
||||
label="Download kubectl binaries matching the Kubernetes cluster version"
|
||||
value={preferences.downloadKubectlBinaries}
|
||||
onChange={downloadKubectlBinaries => preferences.downloadKubectlBinaries = downloadKubectlBinaries}
|
||||
/>
|
||||
<SubTitle title="Download mirror" />
|
||||
<Select
|
||||
placeholder="Download mirror for kubectl"
|
||||
options={downloadMirrorOptions}
|
||||
value={preferences.downloadMirror}
|
||||
onChange={({ value }: SelectOption) => preferences.downloadMirror = value}
|
||||
disabled={!preferences.downloadKubectlBinaries}
|
||||
/>
|
||||
<SubTitle title="Directory for binaries" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
value={downloadPath}
|
||||
placeholder={userStore.getDefaultKubectlPath()}
|
||||
validators={pathValidator}
|
||||
onChange={setDownloadPath}
|
||||
onBlur={save}
|
||||
disabled={!preferences.downloadKubectlBinaries}
|
||||
/>
|
||||
<small className="hint">
|
||||
The directory to download binaries into.
|
||||
</small>
|
||||
<SubTitle title="Path to kubectl binary" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
placeholder={bundledKubectlPath()}
|
||||
value={binariesPath}
|
||||
validators={pathValidator}
|
||||
onChange={setBinariesPath}
|
||||
onBlur={save}
|
||||
disabled={preferences.downloadKubectlBinaries}
|
||||
/>
|
||||
<small className="hint">
|
||||
The path to the kubectl binary on the system.
|
||||
</small>
|
||||
<section className="small">
|
||||
<SubTitle title="Kubectl binary download"/>
|
||||
<FormSwitch
|
||||
control={
|
||||
<Switcher
|
||||
checked={preferences.downloadKubectlBinaries}
|
||||
onChange={v => preferences.downloadKubectlBinaries = v.target.checked}
|
||||
name="kubectl-download"
|
||||
/>
|
||||
}
|
||||
label="Download kubectl binaries matching the Kubernetes cluster version"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<hr className="small"/>
|
||||
|
||||
<section className="small">
|
||||
<SubTitle title="Download mirror" />
|
||||
<Select
|
||||
placeholder="Download mirror for kubectl"
|
||||
options={downloadMirrorOptions}
|
||||
value={preferences.downloadMirror}
|
||||
onChange={({ value }: SelectOption) => preferences.downloadMirror = value}
|
||||
disabled={!preferences.downloadKubectlBinaries}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<hr className="small"/>
|
||||
|
||||
<section className="small">
|
||||
<SubTitle title="Directory for binaries" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
value={downloadPath}
|
||||
placeholder={userStore.getDefaultKubectlPath()}
|
||||
validators={pathValidator}
|
||||
onChange={setDownloadPath}
|
||||
onBlur={save}
|
||||
disabled={!preferences.downloadKubectlBinaries}
|
||||
/>
|
||||
<div className="hint">
|
||||
The directory to download binaries into.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr className="small"/>
|
||||
|
||||
<section className="small">
|
||||
<SubTitle title="Path to kubectl binary" />
|
||||
<Input
|
||||
theme="round-black"
|
||||
placeholder={bundledKubectlPath()}
|
||||
value={binariesPath}
|
||||
validators={pathValidator}
|
||||
onChange={setBinariesPath}
|
||||
onBlur={save}
|
||||
disabled={preferences.downloadKubectlBinaries}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@ -6,22 +6,32 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
||||
|
||||
import { userStore } from "../../../common/user-store";
|
||||
import { isWindows } from "../../../common/vars";
|
||||
import { appPreferenceRegistry } from "../../../extensions/registries/app-preference-registry";
|
||||
import { appPreferenceRegistry, RegisteredAppPreference } from "../../../extensions/registries/app-preference-registry";
|
||||
import { themeStore } from "../../theme.store";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { Input } from "../input";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { HelmCharts } from "./helm-charts";
|
||||
import { KubectlBinaries } from "./kubectl-binaries";
|
||||
import { ScrollSpy } from "../scroll-spy/scroll-spy";
|
||||
import { navigation } from "../../navigation";
|
||||
import { Tab, Tabs } from "../tabs";
|
||||
import { FormSwitch, Switcher } from "../switch";
|
||||
|
||||
enum Pages {
|
||||
Application = "application",
|
||||
Proxy = "proxy",
|
||||
Kubernetes = "kubernetes",
|
||||
Telemetry = "telemetry",
|
||||
Extensions = "extensions",
|
||||
Other = "other"
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Preferences extends React.Component {
|
||||
@observable httpProxy = userStore.preferences.httpsProxy || "";
|
||||
@observable shell = userStore.preferences.shell || "";
|
||||
@observable activeTab = Pages.Application;
|
||||
|
||||
@computed get themeOptions(): SelectOption<string>[] {
|
||||
return themeStore.themes.map(theme => ({
|
||||
@ -45,9 +55,46 @@ export class Preferences extends React.Component {
|
||||
]);
|
||||
}
|
||||
|
||||
onTabChange = (tabId: Pages) => {
|
||||
this.activeTab = tabId;
|
||||
};
|
||||
|
||||
renderNavigation() {
|
||||
const extensions = appPreferenceRegistry.getItems().filter(e => !e.showInPreferencesTab);
|
||||
|
||||
return (
|
||||
<Tabs className="flex column" scrollable={false} onChange={this.onTabChange} value={this.activeTab}>
|
||||
<div className="header">Preferences</div>
|
||||
<Tab value={Pages.Application} label="Application" data-testid="application-tab"/>
|
||||
<Tab value={Pages.Proxy} label="Proxy" data-testid="proxy-tab"/>
|
||||
<Tab value={Pages.Kubernetes} label="Kubernetes" data-testid="kube-tab"/>
|
||||
<Tab value={Pages.Telemetry} label="Telemetry" data-testid="telemetry-tab"/>
|
||||
{extensions.length > 0 &&
|
||||
<Tab value={Pages.Extensions} label="Extensions" data-testid="extensions-tab"/>
|
||||
}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
renderExtension({ title, id, components: { Hint, Input } }: RegisteredAppPreference) {
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
<section id={id} className="small">
|
||||
<SubTitle title={title}/>
|
||||
<Input/>
|
||||
<div className="hint">
|
||||
<Hint/>
|
||||
</div>
|
||||
</section>
|
||||
<hr className="small"/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { preferences } = userStore;
|
||||
const header = <h2>Preferences</h2>;
|
||||
const extensions = appPreferenceRegistry.getItems();
|
||||
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry);
|
||||
let defaultShell = process.env.SHELL || process.env.PTYSHELL;
|
||||
|
||||
if (!defaultShell) {
|
||||
@ -59,29 +106,59 @@ export class Preferences extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollSpy htmlFor="ScrollSpyRoot" render={navigation => (
|
||||
<PageLayout
|
||||
showOnTop
|
||||
navigation={navigation}
|
||||
className="Preferences"
|
||||
contentGaps={false}
|
||||
header={header}
|
||||
>
|
||||
<section id="application" title="Application">
|
||||
<section>
|
||||
<h1>Application</h1>
|
||||
</section>
|
||||
<PageLayout
|
||||
showOnTop
|
||||
navigation={this.renderNavigation()}
|
||||
className="Preferences"
|
||||
contentGaps={false}
|
||||
>
|
||||
{this.activeTab == Pages.Application && (
|
||||
<section id="application">
|
||||
<h2 data-testid="application-header">Application</h2>
|
||||
<section id="appearance">
|
||||
<h2>Appearance</h2>
|
||||
<SubTitle title="Theme"/>
|
||||
<Select
|
||||
options={this.themeOptions}
|
||||
value={preferences.colorTheme}
|
||||
onChange={({ value }: SelectOption) => preferences.colorTheme = value}
|
||||
themeName="lens"
|
||||
/>
|
||||
</section>
|
||||
<section id="proxy">
|
||||
<h2>Proxy</h2>
|
||||
|
||||
<hr className="small"/>
|
||||
|
||||
<section id="shell" className="small">
|
||||
<SubTitle title="Terminal Shell Path"/>
|
||||
<Input
|
||||
theme="round-black"
|
||||
placeholder={defaultShell}
|
||||
value={this.shell}
|
||||
onChange={v => this.shell = v}
|
||||
onBlur={() => preferences.shell = this.shell}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<hr/>
|
||||
|
||||
<section id="other">
|
||||
<SubTitle title="Start-up"/>
|
||||
<FormSwitch
|
||||
control={
|
||||
<Switcher
|
||||
checked={preferences.openAtLogin}
|
||||
onChange={v => preferences.openAtLogin = v.target.checked}
|
||||
name="startup"
|
||||
/>
|
||||
}
|
||||
label="Automatically start Lens on login"
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
)}
|
||||
{this.activeTab == Pages.Proxy && (
|
||||
<section id="proxy">
|
||||
<section>
|
||||
<h2 data-testid="proxy-header">Proxy</h2>
|
||||
<SubTitle title="HTTP Proxy"/>
|
||||
<Input
|
||||
theme="round-black"
|
||||
@ -93,12 +170,21 @@ export class Preferences extends React.Component {
|
||||
<small className="hint">
|
||||
Proxy is used only for non-cluster communication.
|
||||
</small>
|
||||
</section>
|
||||
|
||||
<hr className="small"/>
|
||||
|
||||
<section className="small">
|
||||
<SubTitle title="Certificate Trust"/>
|
||||
<Checkbox
|
||||
<FormSwitch
|
||||
control={
|
||||
<Switcher
|
||||
checked={preferences.allowUntrustedCAs}
|
||||
onChange={v => preferences.allowUntrustedCAs = v.target.checked}
|
||||
name="startup"
|
||||
/>
|
||||
}
|
||||
label="Allow untrusted Certificate Authorities"
|
||||
value={preferences.allowUntrustedCAs}
|
||||
onChange={v => preferences.allowUntrustedCAs = v}
|
||||
/>
|
||||
<small className="hint">
|
||||
This will make Lens to trust ANY certificate authority without any validations.{" "}
|
||||
@ -106,63 +192,37 @@ export class Preferences extends React.Component {
|
||||
Does not affect cluster communications!
|
||||
</small>
|
||||
</section>
|
||||
<section id="shell">
|
||||
<h2>Terminal Shell</h2>
|
||||
<SubTitle title="Shell Path"/>
|
||||
<Input
|
||||
theme="round-black"
|
||||
placeholder={defaultShell}
|
||||
value={this.shell}
|
||||
onChange={v => this.shell = v}
|
||||
onBlur={() => preferences.shell = this.shell}
|
||||
/>
|
||||
<small className="hint">
|
||||
The path of the shell that the terminal uses.
|
||||
</small>
|
||||
</section>
|
||||
<section id="startup">
|
||||
<h2>Start-up</h2>
|
||||
<SubTitle title="Automatic Start-up"/>
|
||||
<Checkbox
|
||||
label="Automatically start Lens on login"
|
||||
value={preferences.openAtLogin}
|
||||
onChange={v => preferences.openAtLogin = v}
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{this.activeTab == Pages.Kubernetes && (
|
||||
<section id="kubernetes">
|
||||
<section>
|
||||
<h1>Kubernetes</h1>
|
||||
</section>
|
||||
<section id="kubectl">
|
||||
<h2>Kubectl binary</h2>
|
||||
<h2 data-testid="kubernetes-header">Kubernetes</h2>
|
||||
<KubectlBinaries preferences={preferences}/>
|
||||
</section>
|
||||
<hr/>
|
||||
<section id="helm">
|
||||
<h2>Helm Charts</h2>
|
||||
<HelmCharts/>
|
||||
</section>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section id="extensions">
|
||||
<section>
|
||||
<h1>Extensions</h1>
|
||||
</section>
|
||||
{appPreferenceRegistry.getItems().map(({ title, id, components: { Hint, Input } }, index) => {
|
||||
return (
|
||||
<section key={index} id={title}>
|
||||
<h2 id={id}>{title}</h2>
|
||||
<Input/>
|
||||
<small className="hint">
|
||||
<Hint/>
|
||||
</small>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
{this.activeTab == Pages.Telemetry && (
|
||||
<section id="telemetry">
|
||||
<h2 data-testid="telemetry-header">Telemetry</h2>
|
||||
{telemetryExtensions.map(this.renderExtension)}
|
||||
</section>
|
||||
</PageLayout>
|
||||
)}/>
|
||||
)}
|
||||
|
||||
{this.activeTab == Pages.Extensions && (
|
||||
<section id="extensions">
|
||||
<h2>Extensions</h2>
|
||||
{extensions.filter(e => !e.showInPreferencesTab).map(this.renderExtension)}
|
||||
</section>
|
||||
)}
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,12 +105,6 @@ ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: $margin 0 !important;
|
||||
height: 1px;
|
||||
background: $grey-800;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $textColorPrimary;
|
||||
font-size: 28px;
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
position: relative;
|
||||
overflow: hidden; // required for transition effect on hover
|
||||
color: white;
|
||||
font-family: var(--font-main);
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
@ -96,14 +96,17 @@
|
||||
}
|
||||
|
||||
label {
|
||||
background: $mainBackground;
|
||||
border: 1px solid $borderFaintColor;
|
||||
border-radius: $radius;
|
||||
background: var(--inputControlBackground);
|
||||
border: 1px solid var(--inputControlBorder);
|
||||
border-radius: 4px;
|
||||
padding: $padding;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--inputControlHoverBorder);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border: 2px solid $colorInfo;
|
||||
padding: $padding - 1;
|
||||
border-color: $colorInfo;
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
||||
@ -1,30 +1,22 @@
|
||||
.PageLayout {
|
||||
--width: 60%;
|
||||
--width: 75%;
|
||||
--nav-width: 180px;
|
||||
--nav-column-width: 30vw;
|
||||
--spacing: calc(var(--unit) * 2);
|
||||
--wrapper-padding: calc(var(--spacing) * 2);
|
||||
--header-height: 64px;
|
||||
--header-height-mac: 80px;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid !important;
|
||||
grid-template-rows: min-content 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@include media("<1000px") {
|
||||
--width: 85%;
|
||||
}
|
||||
|
||||
&.showNavigation {
|
||||
--width: 70%;
|
||||
|
||||
grid-template-columns: var(--nav-column-width) 1fr;
|
||||
|
||||
> .content-wrapper {
|
||||
> .content {
|
||||
width: 100%;
|
||||
padding-left: 1px; // Fix visual content crop
|
||||
padding-right: calc(var(--nav-column-width) - var(--nav-width));
|
||||
}
|
||||
> .contentRegion {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,96 +27,172 @@
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 24px;
|
||||
bottom: 0;
|
||||
height: unset;
|
||||
background-color: var(--mainBackground);
|
||||
|
||||
// adds extra space for traffic-light top buttons (mac only)
|
||||
.is-mac & > .header {
|
||||
height: var(--header-height-mac);
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
background-color: var(--settingsBackground);
|
||||
}
|
||||
|
||||
> .header {
|
||||
position: sticky;
|
||||
padding: var(--spacing);
|
||||
background-color: var(--layoutTabsBackground);
|
||||
height: var(--header-height);
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 4;
|
||||
}
|
||||
|
||||
> .content-navigation {
|
||||
> .sidebarRegion {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
overflow-y: auto;
|
||||
margin-top: 32px;
|
||||
background-color: var(--secondaryBackground);
|
||||
|
||||
ul.TreeView {
|
||||
width: var(--nav-width);
|
||||
padding-right: 24px;
|
||||
.sidebar {
|
||||
width: 218px;
|
||||
padding: 60px 10px 60px 20px;
|
||||
|
||||
.Tabs {
|
||||
.header {
|
||||
padding: 6px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Tab {
|
||||
padding: 6px 10px;
|
||||
margin-bottom: 2px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
color: var(--textColorSecondary);
|
||||
|
||||
&::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--navHoverBackground);
|
||||
color: var(--navHoverColor);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--navSelectedBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .content-wrapper {
|
||||
padding: 32px;
|
||||
> .contentRegion {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
justify-content: center;
|
||||
|
||||
> .content {
|
||||
width: var(--width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
padding: 60px 40px 80px;
|
||||
|
||||
p {
|
||||
line-height: 140%;
|
||||
> section {
|
||||
&:last-of-type {
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .toolsRegion {
|
||||
.fixedTools {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
|
||||
.closeBtn {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 2px solid var(--textColorDimmed);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #72767d4d;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.Icon {
|
||||
color: var(--textColorSecondary);
|
||||
}
|
||||
}
|
||||
|
||||
.esc {
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--textColorDimmed);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--colorInfo);
|
||||
}
|
||||
|
||||
.SubTitle {
|
||||
text-transform: none;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.Select {
|
||||
&__control {
|
||||
box-shadow: 0 0 0 1px var(--borderFaintColor);
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: var(--spacing);
|
||||
|
||||
> :not(:last-child) {
|
||||
margin-bottom: var(--spacing);
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 40px;
|
||||
|
||||
&.small {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: var(--textColorAccent);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: x-large;
|
||||
border-bottom: 1px solid var(--borderFaintColor);
|
||||
padding-bottom: var(--padding);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: large;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
small.hint {
|
||||
margin-top: calc(var(--unit) * -1.5);
|
||||
.hint {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.SubTitle {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 40px;
|
||||
height: 1px;
|
||||
border-top: thin solid var(--hrColor);
|
||||
|
||||
&.small {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,19 +3,18 @@ import "./page-layout.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { autobind, cssNames, IClassName } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { navigation } from "../../navigation";
|
||||
import { NavigationTree, RecursiveTreeView } from "../tree-view";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
export interface PageLayoutProps extends React.DOMAttributes<any> {
|
||||
className?: IClassName;
|
||||
header: React.ReactNode;
|
||||
header?: React.ReactNode;
|
||||
headerClass?: IClassName;
|
||||
contentClass?: IClassName;
|
||||
provideBackButtonNavigation?: boolean;
|
||||
contentGaps?: boolean;
|
||||
showOnTop?: boolean; // covers whole app view
|
||||
navigation?: NavigationTree[];
|
||||
navigation?: React.ReactNode;
|
||||
back?: (evt: React.MouseEvent | KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
@ -58,32 +57,34 @@ export class PageLayout extends React.Component<PageLayoutProps> {
|
||||
|
||||
render() {
|
||||
const {
|
||||
contentClass, header, headerClass, provideBackButtonNavigation,
|
||||
contentClass, headerClass, provideBackButtonNavigation,
|
||||
contentGaps, showOnTop, navigation, children, ...elemProps
|
||||
} = this.props;
|
||||
const className = cssNames("PageLayout", { showOnTop, showNavigation: navigation }, this.props.className);
|
||||
|
||||
return (
|
||||
<div {...elemProps} className={className}>
|
||||
<div className={cssNames("header flex gaps align-center", headerClass)}>
|
||||
{header}
|
||||
{provideBackButtonNavigation && (
|
||||
<Icon
|
||||
big material="close"
|
||||
className="back box right"
|
||||
onClick={this.back}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{ navigation && (
|
||||
<nav className="content-navigation">
|
||||
<RecursiveTreeView data={navigation}/>
|
||||
<nav className="sidebarRegion">
|
||||
<div className="sidebar">
|
||||
{navigation}
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
<div className="content-wrapper" id="ScrollSpyRoot">
|
||||
<div className="contentRegion" id="ScrollSpyRoot">
|
||||
<div className={cssNames("content", contentClass, contentGaps && "flex column gaps")}>
|
||||
{children}
|
||||
</div>
|
||||
<div className="toolsRegion">
|
||||
<div className="fixedTools">
|
||||
<div className="closeBtn" role="button" aria-label="Close" onClick={this.back}>
|
||||
<Icon material="close"/>
|
||||
</div>
|
||||
<div className="esc" aria-hidden="true">
|
||||
ESC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -6,7 +6,7 @@ html {
|
||||
|
||||
--select-menu-bgc: #{$menuBackgroundColor};
|
||||
--select-menu-border-color: #{$halfGray};
|
||||
--select-option-selected-color: #{$selectOptionHoveredColor};
|
||||
--select-option-selected-color: #{$inputOptionHoverColor};
|
||||
--select-option-focused-bgc: #{$colorInfo};
|
||||
--select-option-focused-color: #{$textColorAccent};
|
||||
|
||||
@ -14,6 +14,8 @@ html {
|
||||
position: relative;
|
||||
min-width: 220px;
|
||||
|
||||
|
||||
|
||||
* {
|
||||
color: inherit;
|
||||
}
|
||||
@ -33,7 +35,7 @@ html {
|
||||
cursor: pointer;
|
||||
|
||||
&--is-focused {
|
||||
box-shadow: 0 0 0 2px $primary;
|
||||
box-shadow: 0 0 0 1px $primary;
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,8 +75,7 @@ html {
|
||||
min-width: 100%;
|
||||
|
||||
&-list {
|
||||
padding-right: 1px;
|
||||
padding-left: 1px;
|
||||
padding: 6px;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
@ -183,5 +184,54 @@ html {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.theme-lens {
|
||||
:hover {
|
||||
&.Select__control {
|
||||
box-shadow: 0 0 0 1px var(--inputControlHoverBorder);
|
||||
}
|
||||
}
|
||||
|
||||
:focus-within {
|
||||
&.Select__control {
|
||||
box-shadow: 0 0 0 1px $colorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
&.Select__menu {
|
||||
box-shadow: inset 0 0 0 1px var(--inputControlBorder);
|
||||
}
|
||||
|
||||
.Select {
|
||||
&__control {
|
||||
box-shadow: 0 0 0 1px var(--inputControlBorder);
|
||||
background: var(--inputControlBackground);
|
||||
}
|
||||
|
||||
&__menu {
|
||||
&-list {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&__option {
|
||||
border-radius: 4px;
|
||||
|
||||
&:active {
|
||||
background: var(--inputControlBackground);
|
||||
}
|
||||
|
||||
&--is-selected {
|
||||
background: var(--inputControlBackground);
|
||||
color: var(--textColorAccent);
|
||||
}
|
||||
|
||||
&--is-focused {
|
||||
color: var(--textColorPrimary);
|
||||
background: var(--inputControlBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ export interface SelectOption<T = any> {
|
||||
|
||||
export interface SelectProps<T = any> extends ReactSelectProps<T>, CreatableProps<T> {
|
||||
value?: T;
|
||||
themeName?: "dark" | "light" | "outlined";
|
||||
themeName?: "dark" | "light" | "outlined" | "lens";
|
||||
menuClass?: string;
|
||||
isCreatable?: boolean;
|
||||
autoConvertOptions?: boolean; // to internal format (i.e. {value: T, label: string}[]), not working with groups
|
||||
|
||||
28
src/renderer/components/switch/form-switcher.tsx
Normal file
28
src/renderer/components/switch/form-switcher.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import FormControlLabel, { FormControlLabelProps } from "@material-ui/core/FormControlLabel";
|
||||
import { makeStyles } from "@material-ui/styles";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
margin: 0,
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
flex: 1,
|
||||
color: "var(--textColorAccent)"
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export function FormSwitch(props: FormControlLabelProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
control={props.control}
|
||||
labelPlacement="start"
|
||||
label={props.label}
|
||||
className={classes.root}
|
||||
/>
|
||||
);
|
||||
}
|
||||
2
src/renderer/components/switch/index.ts
Normal file
2
src/renderer/components/switch/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./switcher";
|
||||
export * from "./form-switcher";
|
||||
68
src/renderer/components/switch/switcher.tsx
Normal file
68
src/renderer/components/switch/switcher.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React from "react";
|
||||
import { createStyles, withStyles, Theme } from "@material-ui/core/styles";
|
||||
import Switch, { SwitchClassKey, SwitchProps } from "@material-ui/core/Switch";
|
||||
|
||||
interface Styles extends Partial<Record<SwitchClassKey, string>> {
|
||||
focusVisible?: string;
|
||||
}
|
||||
|
||||
interface Props extends SwitchProps {
|
||||
classes: Styles;
|
||||
}
|
||||
|
||||
export const Switcher = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: 40,
|
||||
height: 24,
|
||||
padding: 0,
|
||||
margin: "0 0 0 8px",
|
||||
},
|
||||
switchBase: {
|
||||
padding: 1,
|
||||
paddingLeft: 4,
|
||||
"&$checked": {
|
||||
transform: "translateX(14px)",
|
||||
color: "white",
|
||||
"& + $track": {
|
||||
backgroundColor: "#52d869",
|
||||
opacity: 1,
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
"&$focusVisible $thumb": {
|
||||
color: "#52d869",
|
||||
border: "6px solid #fff",
|
||||
},
|
||||
},
|
||||
thumb: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
marginTop: 2,
|
||||
boxShadow: "none"
|
||||
},
|
||||
track: {
|
||||
borderRadius: 26 / 2,
|
||||
backgroundColor: "#72767b",
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(["background-color", "border"]),
|
||||
},
|
||||
checked: {},
|
||||
focusVisible: {},
|
||||
}),
|
||||
)(({ classes, ...props }: Props) => {
|
||||
return (
|
||||
<Switch
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
disableRipple
|
||||
classes={{
|
||||
root: classes.root,
|
||||
switchBase: classes.switchBase,
|
||||
thumb: classes.thumb,
|
||||
track: classes.track,
|
||||
checked: classes.checked,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -9,12 +9,14 @@
|
||||
"golden": "#ffc63d",
|
||||
"halfGray": "#87909c80",
|
||||
"primary": "#3d90ce",
|
||||
"textColorPrimary": "#87909c",
|
||||
"textColorPrimary": "#8e9297",
|
||||
"textColorSecondary": "#a0a0a0",
|
||||
"textColorAccent": "#ffffff",
|
||||
"textColorDimmed": "#8e92978c",
|
||||
"borderColor": "#4c5053",
|
||||
"borderFaintColor": "#373a3e",
|
||||
"mainBackground": "#1e2124",
|
||||
"secondaryBackground": "#212427",
|
||||
"contentColor": "#262b2f",
|
||||
"layoutBackground": "#2e3136",
|
||||
"layoutTabsBackground": "#252729",
|
||||
@ -112,11 +114,19 @@
|
||||
"chartStripesColor": "#ffffff08",
|
||||
"chartCapacityColor": "#4c545f",
|
||||
"pieChartDefaultColor": "#30353a",
|
||||
"selectOptionHoveredColor": "#87909c",
|
||||
"inputOptionHoverColor": "#87909c",
|
||||
"inputControlBackground": "#00000021",
|
||||
"inputControlBorder": "#202225bf",
|
||||
"inputControlHoverBorder": "#07080880",
|
||||
"lineProgressBackground": "#414448",
|
||||
"radioActiveBackground": "#36393e",
|
||||
"menuActiveBackground": "#36393e",
|
||||
"menuSelectedOptionBgc": "#36393e",
|
||||
"scrollBarColor": "#5f6064"
|
||||
"scrollBarColor": "#5f6064",
|
||||
"settingsBackground": "#2b3035",
|
||||
"navSelectedBackground": "#4f545c52",
|
||||
"navHoverBackground": "#4f545c29",
|
||||
"navHoverColor": "#dcddde",
|
||||
"hrColor": "#ffffff0f"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,11 @@
|
||||
"textColorPrimary": "#555555",
|
||||
"textColorSecondary": "#51575d",
|
||||
"textColorAccent": "#333333",
|
||||
"textColorDimmed": "#5557598c",
|
||||
"borderColor": "#c9cfd3",
|
||||
"borderFaintColor": "#dfdfdf",
|
||||
"mainBackground": "#f1f1f1",
|
||||
"secondaryBackground": "#f2f3f5",
|
||||
"contentColor": "#ffffff",
|
||||
"layoutBackground": "#e8e8e8",
|
||||
"layoutTabsBackground": "#f8f8f8",
|
||||
@ -113,12 +115,20 @@
|
||||
"chartStripesColor": "#00000009",
|
||||
"chartCapacityColor": "#cccccc",
|
||||
"pieChartDefaultColor": "#efefef",
|
||||
"selectOptionHoveredColor": "#ffffff",
|
||||
"inputOptionHoverColor": "#ffffff",
|
||||
"inputControlBackground": "#f6f6f7",
|
||||
"inputControlBorder": "#cccdcf",
|
||||
"inputControlHoverBorder": "#b9bbbe",
|
||||
"lineProgressBackground": "#e8e8e8",
|
||||
"radioActiveBackground": "#f1f1f1",
|
||||
"menuActiveBackground": "#e8e8e8",
|
||||
"menuSelectedOptionBgc": "#e8e8e8",
|
||||
"scrollBarColor": "#bbbbbb",
|
||||
"canvasBackground": "#24292e"
|
||||
"canvasBackground": "#24292e",
|
||||
"settingsBackground": "#ffffff",
|
||||
"navSelectedBackground": "#747f8d3d",
|
||||
"navHoverBackground": "#747f8d14",
|
||||
"navHoverColor": "#2e3135",
|
||||
"hrColor": "#06060714"
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ $iconActiveColor: var(--iconActiveColor);
|
||||
$iconActiveBackground: var(--iconActiveBackground);
|
||||
$filterAreaBackground: var(--filterAreaBackground);
|
||||
|
||||
$selectOptionHoveredColor: var(--selectOptionHoveredColor);
|
||||
$inputOptionHoverColor: var(--inputOptionHoverColor);
|
||||
$lineProgressBackground: var(--lineProgressBackground);
|
||||
$radioActiveBackground: var(--radioActiveBackground);
|
||||
$menuActiveBackground: var(--menuActiveBackground);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user