Skip to content

Commit

Permalink
feat(ui): support auto theme (#20080)
Browse files Browse the repository at this point in the history
* feat(theme): support auto theme

Signed-off-by: linghaoSu <linghao.su@daocloud.io>

* fix(ui): set default theme as light

Signed-off-by: linghaoSu <linghao.su@daocloud.io>

* fix(ui): only register listener when theme is auto

Signed-off-by: linghaoSu <linghao.su@daocloud.io>

---------

Signed-off-by: linghaoSu <linghao.su@daocloud.io>
Co-authored-by: Dan Garfield <dan@codefresh.io>
  • Loading branch information
linghaoSu and todaywasawesome authored Oct 3, 2024
1 parent 8d268e7 commit 3088906
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 25 deletions.
4 changes: 2 additions & 2 deletions ui/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import applications from './applications';
import help from './help';
import login from './login';
import settings from './settings';
import {Layout} from './shared/components/layout/layout';
import {Layout, ThemeWrapper} from './shared/components/layout/layout';
import {Page} from './shared/components/page/page';
import {VersionPanel} from './shared/components/version-info/version-info-panel';
import {AuthSettingsCtx, Provider} from './shared/context';
Expand Down Expand Up @@ -194,7 +194,7 @@ export class App extends React.Component<
<PageContext.Provider value={{title: 'Argo CD'}}>
<Provider value={{history, popup: this.popupManager, notifications: this.notificationsManager, navigation: this.navigationManager, baseHref: base}}>
<DataLoader load={() => services.viewPreferences.getPreferences()}>
{pref => <div className={pref.theme ? 'theme-' + pref.theme : 'theme-light'}>{this.state.popupProps && <Popup {...this.state.popupProps} />}</div>}
{pref => <ThemeWrapper theme={pref.theme}>{this.state.popupProps && <Popup {...this.state.popupProps} />}</ThemeWrapper>}
</DataLoader>
<AuthSettingsCtx.Provider value={this.state.authSettings}>
<Router history={history}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
}
border-radius: 4px;
box-shadow: 1px 2px 3px rgba(#000, 0.1);
}
& .row {
justify-content: space-between;
align-items: center;

&__button {
position: absolute;
top: 25%;
right: 30px;
.select {
min-width: 160px;
}
}
}
}
}
21 changes: 11 additions & 10 deletions ui/src/app/settings/components/appearance-list/appearance-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {DataLoader, Page} from '../../../shared/components';
import {services} from '../../../shared/services';
import {Select, SelectOption} from 'argo-ui';

require('./appearance-list.scss');

Expand All @@ -16,16 +17,16 @@ export const AppearanceList = () => {
<div className='appearance-list'>
<div className='argo-container'>
<div className='appearance-list__panel'>
<div className='columns'>Dark Theme</div>
<div className='columns'>
<button
className='argo-button argo-button--base appearance-list__button'
onClick={() => {
const targetTheme = pref.theme === 'light' ? 'dark' : 'light';
services.viewPreferences.updatePreferences({theme: targetTheme});
}}>
{pref.theme === 'light' ? 'Enable' : 'Disable'}
</button>
<div className='row'>
<span>Dark Theme</span>
<Select
value={pref.theme}
onChange={(value: SelectOption) => services.viewPreferences.updatePreferences({theme: value.value})}
options={[
{value: 'auto', title: 'Auto'},
{value: 'light', title: 'Light'},
{value: 'dark', title: 'Dark'}
]}></Select>
</div>
</div>
</div>
Expand Down
17 changes: 13 additions & 4 deletions ui/src/app/shared/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {Sidebar} from '../../../sidebar/sidebar';
import {ViewPreferences} from '../../services';
import {useTheme} from '../../utils';

require('./layout.scss');

Expand All @@ -13,15 +14,23 @@ export interface LayoutProps {

const getBGColor = (theme: string): string => (theme === 'light' ? '#dee6eb' : '#100f0f');

export const ThemeWrapper = (props: {children: React.ReactNode; theme: string}) => {
const [systemTheme] = useTheme({
theme: props.theme
});
return <div className={'theme-' + systemTheme}>{props.children}</div>;
};

export const Layout = (props: LayoutProps) => {
const [theme] = useTheme({theme: props.pref.theme});
React.useEffect(() => {
if (props.pref.theme) {
document.body.style.background = getBGColor(props.pref.theme);
if (theme) {
document.body.style.background = getBGColor(theme);
}
}, [props.pref.theme]);
}, [theme]);

return (
<div className={props.pref.theme ? 'theme-' + props.pref.theme : 'theme-light'}>
<div className={`theme-${theme}`}>
<div className={'cd-layout'}>
<Sidebar onVersionClick={props.onVersionClick} navItems={props.navItems} pref={props.pref} />
<div className={`cd-layout__content ${props.pref.hideSidebar ? 'cd-layout__content--sb-collapsed' : 'cd-layout__content--sb-expanded'} custom-styles`}>
Expand Down
18 changes: 17 additions & 1 deletion ui/src/app/shared/components/monaco-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';

import * as monacoEditor from 'monaco-editor';
import {services} from '../services';
import {getTheme, useSystemTheme} from '../utils';

export interface EditorInput {
text: string;
Expand All @@ -28,10 +29,25 @@ const MonacoEditorLazy = React.lazy(() =>
import('monaco-editor').then(monaco => {
const Component = (props: MonacoProps) => {
const [height, setHeight] = React.useState(0);
const [theme, setTheme] = React.useState('dark');

React.useEffect(() => {
const destroySystemThemeListener = useSystemTheme(systemTheme => {
if (theme === 'auto') {
monaco.editor.setTheme(systemTheme === 'dark' ? 'vs-dark' : 'vs');
}
});

return () => {
destroySystemThemeListener();
};
}, [theme]);

React.useEffect(() => {
const subscription = services.viewPreferences.getPreferences().subscribe(preferences => {
monaco.editor.setTheme(preferences.theme === 'dark' ? 'vs-dark' : 'vs');
setTheme(preferences.theme);

monaco.editor.setTheme(getTheme(preferences.theme) === 'dark' ? 'vs-dark' : 'vs');
});

return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {DataLoader, SlidingPanel, Tooltip} from 'argo-ui';
import * as React from 'react';
import {VersionMessage} from '../../models';
import {services} from '../../services';
import {ThemeWrapper} from '../layout/layout';

interface VersionPanelProps {
isShown: boolean;
Expand All @@ -26,14 +27,14 @@ export class VersionPanel extends React.Component<VersionPanelProps, {copyState:
<DataLoader load={() => this.props.version}>
{version => {
return (
<div className={'theme-' + pref.theme}>
<ThemeWrapper theme={pref.theme}>
<SlidingPanel header={this.header} isShown={this.props.isShown} onClose={() => this.props.onClose()} hasCloseButton={true} isNarrow={true}>
<div className='argo-table-list'>{this.buildVersionTable(version)}</div>
<div>
<Tooltip content='Copy all version info as JSON'>{this.getCopyButton(version)}</Tooltip>
</div>
</SlidingPanel>
</div>
</ThemeWrapper>
);
}}
</DataLoader>
Expand Down
70 changes: 70 additions & 0 deletions ui/src/app/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from 'react';

export function hashCode(str: string) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
Expand Down Expand Up @@ -34,3 +36,71 @@ export function isValidURL(url: string): boolean {
}
}
}

export const colorSchemes = {
light: '(prefers-color-scheme: light)',
dark: '(prefers-color-scheme: dark)'
};

/**
* quick method to check system theme
* @param theme auto, light, dark
* @returns dark or light
*/
export function getTheme(theme: string) {
if (theme !== 'auto') {
return theme;
}

const dark = window.matchMedia(colorSchemes.dark);

return dark.matches ? 'dark' : 'light';
}

/**
* create a listener for system theme
* @param cb callback for theme change
* @returns destroy listener
*/
export const useSystemTheme = (cb: (theme: string) => void) => {
const dark = window.matchMedia(colorSchemes.dark);
const light = window.matchMedia(colorSchemes.light);

const listener = () => {
cb(dark.matches ? 'dark' : 'light');
};

dark.addEventListener('change', listener);
light.addEventListener('change', listener);

return () => {
dark.removeEventListener('change', listener);
light.removeEventListener('change', listener);
};
};

export const useTheme = (props: {theme: string}) => {
const [theme, setTheme] = React.useState(getTheme(props.theme));

React.useEffect(() => {
let destroyListener: (() => void) | undefined;

// change theme by system, only register listener when theme is auto
if (props.theme === 'auto') {
destroyListener = useSystemTheme(systemTheme => {
setTheme(systemTheme);
});
}

// change theme manually
if (props.theme !== theme) {
setTheme(getTheme(props.theme));
}

return () => {
destroyListener?.();
};
}, [props.theme]);

return [theme];
};

0 comments on commit 3088906

Please sign in to comment.