diff --git a/ui/src/app.less b/ui/src/app.less index db80238d10..027868a457 100644 --- a/ui/src/app.less +++ b/ui/src/app.less @@ -62,6 +62,41 @@ body, html { font-family: 'Poppins', sans-serif; } +:root { + --app-bg-layout: #f7f8fa; + --app-bg-elevated: #ffffff; + --app-bg-subtle: #f4f6f9; + --app-bg-overlay: rgba(0, 0, 0, 0.4); + --app-sidebar-bg: #111111; + --app-sidebar-text: #a0b3c2; + --app-sidebar-text-active: #cddae4; + --app-border-subtle: rgba(0, 0, 0, 0.05); + --app-border-strong: #d9d9d9; + --app-text: #454545; + --app-text-secondary: #6b7280; + --app-minimap-border: #9cccf1; +} + +[data-theme='dark'] { + --app-bg-layout: #0f141a; + --app-bg-elevated: #161d24; + --app-bg-subtle: #1b242d; + --app-bg-overlay: rgba(0, 0, 0, 0.55); + --app-sidebar-bg: #0b1016; + --app-sidebar-text: #8da0b3; + --app-sidebar-text-active: #d7e3ef; + --app-border-subtle: rgba(255, 255, 255, 0.08); + --app-border-strong: #2a333d; + --app-text: #d7dee7; + --app-text-secondary: #9cadbd; + --app-minimap-border: #36536b; +} + +body { + background: var(--app-bg-layout); + color: var(--app-text); +} + .ant-btn { box-shadow: none !important; } diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 5ceb990d50..fbdc85eb82 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -9,7 +9,7 @@ import { Project } from '@ui/pages/project'; import { paths } from './config/paths'; import { queryClient } from './config/query-client'; -import { themeConfig } from './config/themeConfig'; +import { getThemeConfig } from './config/themeConfig'; import { AppExtensions } from './extensions/pages/app-extensions'; import { ArgoCDExtension } from './extensions/pages/argocd-extension'; import { ProjectExtensions } from './extensions/pages/project-extensions'; @@ -19,6 +19,8 @@ import { TokenRenew } from './features/auth/token-renew'; import { MainLayout } from './features/common/layout/main-layout'; import { Events } from './features/project/events/events'; import { ProjectSettings } from './features/project/settings/project-settings'; +import { ThemeProvider } from './features/theme/theme-provider'; +import { useThemeContext } from './features/theme/use-theme-context'; import { AnalysisRunLogsPage } from './pages/analysis-run-logs'; import { Downloads } from './pages/downloads/downloads'; import { Login } from './pages/login/login'; @@ -29,59 +31,69 @@ import { User } from './pages/user'; import './app.less'; import 'antd/dist/reset.css'; -export const App = () => ( - - - - - - - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } /> - - } /> - } /> - - - } /> - - } /> +const AppContent = () => { + const { resolvedTheme } = useThemeContext(); + + return ( + + + + + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + } + /> + } /> + + } /> + } /> + + + } /> + + } /> - - } /> - - - } /> - } /> - + + } /> + + + } /> + } /> - } /> - } /> - } /> - - - - + } /> + + } /> + } /> + + + + + ); +}; + +export const App = () => ( + + + + + diff --git a/ui/src/config/themeConfig.ts b/ui/src/config/themeConfig.ts index 5f38b0c983..d1fbdb0279 100644 --- a/ui/src/config/themeConfig.ts +++ b/ui/src/config/themeConfig.ts @@ -1,6 +1,9 @@ +import { theme } from 'antd'; import { ThemeConfig } from 'antd/es/config-provider'; import { MapToken } from 'antd/es/theme/interface'; +import type { ResolvedTheme } from '@ui/features/theme/types'; + export const token: Partial = { colorPrimary: '#30476c', fontSizeHeading1: 28, @@ -8,32 +11,54 @@ export const token: Partial = { fontSizeHeading3: 20, fontSizeHeading4: 18, fontSizeHeading5: 14, - colorText: '#454545', borderRadius: 8, - fontFamily: 'Poppins, sans-serif', - colorBgLayout: '#f7f8fa' + fontFamily: 'Poppins, sans-serif' }; -export const themeConfig: ThemeConfig = { - // ...token, - token, - components: { - Menu: { - itemActiveBg: '#ebedf1', - itemSelectedBg: '#ebedf1', - itemHoverBg: '#f8f9fb', - itemHeight: 36 - }, - Layout: { - headerBg: '#fff', - headerHeight: 50, - headerPadding: '0 16px' - }, - Card: { - borderRadius: 8 +const lightToken: Partial = { + colorText: '#454545', + colorBgLayout: '#f7f8fa', + colorBgContainer: '#ffffff', + colorBorderSecondary: '#eef2f6' +}; + +const darkToken: Partial = { + colorText: '#d7dee7', + colorTextSecondary: '#9cadbd', + colorBgLayout: '#0f141a', + colorBgContainer: '#161d24', + colorBorder: '#2a333d', + colorBorderSecondary: '#222b34' +}; + +export const getThemeConfig = (mode: ResolvedTheme): ThemeConfig => { + const isDark = mode === 'dark'; + + return { + cssVar: true, + algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm, + token: { + ...token, + ...(isDark ? darkToken : lightToken) }, - Button: { - contentFontSizeSM: 13 + components: { + Menu: { + itemActiveBg: isDark ? '#1d2630' : '#ebedf1', + itemSelectedBg: isDark ? '#1d2630' : '#ebedf1', + itemHoverBg: isDark ? '#18212a' : '#f8f9fb', + itemHeight: 36 + }, + Layout: { + headerBg: isDark ? '#161d24' : '#fff', + headerHeight: 50, + headerPadding: '0 16px' + }, + Card: { + borderRadius: 8 + }, + Button: { + contentFontSizeSM: 13 + } } - } + }; }; diff --git a/ui/src/features/assemble-freight/artifact-menu-item.tsx b/ui/src/features/assemble-freight/artifact-menu-item.tsx index b13004d957..513d312139 100644 --- a/ui/src/features/assemble-freight/artifact-menu-item.tsx +++ b/ui/src/features/assemble-freight/artifact-menu-item.tsx @@ -12,10 +12,13 @@ export interface ArtifactMenuItemProps { export const ArtifactMenuItem = ({ onClick, selected, children }: ArtifactMenuItemProps) => (
{children}
diff --git a/ui/src/features/common/code-editor/yaml-editor-lazy.tsx b/ui/src/features/common/code-editor/yaml-editor-lazy.tsx index 91a3644b41..b272104eb0 100644 --- a/ui/src/features/common/code-editor/yaml-editor-lazy.tsx +++ b/ui/src/features/common/code-editor/yaml-editor-lazy.tsx @@ -6,6 +6,8 @@ import { configureMonacoYaml } from 'monaco-yaml'; import React, { FC, useEffect, useRef } from 'react'; import yaml from 'yaml'; +import { useThemeContext } from '@ui/features/theme/use-theme-context'; + import styles from './yaml-editor.module.less'; loader.config({ monaco }); @@ -41,6 +43,8 @@ const YamlEditor: FC = (props) => { resourceType } = props; + const { resolvedTheme } = useThemeContext(); + const handleOnChange = (newValue: string | undefined) => { onChange?.(newValue); }; @@ -100,10 +104,11 @@ const YamlEditor: FC = (props) => {
{label}
(
{children}
diff --git a/ui/src/features/common/layout/main-layout.module.less b/ui/src/features/common/layout/main-layout.module.less index b21c6bd4e7..e77c8de60d 100644 --- a/ui/src/features/common/layout/main-layout.module.less +++ b/ui/src/features/common/layout/main-layout.module.less @@ -8,14 +8,14 @@ display: flex; flex-direction: column; flex: 0 0 110px; - background-color: #111; + background-color: var(--app-sidebar-bg); color: @colorWhite; } .contentWrapper { position: relative; flex: 1; - background-color: @colorBgLayout; + background-color: var(--app-bg-layout); overflow: hidden; } @@ -49,10 +49,19 @@ } .logout { - color: #a0b3c2; + color: var(--app-sidebar-text); margin: ~'@{sizeSM}px 0'; &:hover { - color: #cddae4 !important; + color: var(--app-sidebar-text-active) !important; + } +} + +.themeButton { + color: var(--app-sidebar-text); + margin: 0 auto; + + &:hover { + color: var(--app-sidebar-text-active) !important; } } diff --git a/ui/src/features/common/layout/main-layout.tsx b/ui/src/features/common/layout/main-layout.tsx index 5fdfaa607e..6890115927 100644 --- a/ui/src/features/common/layout/main-layout.tsx +++ b/ui/src/features/common/layout/main-layout.tsx @@ -20,6 +20,7 @@ import { KargoLogo } from '@ui/features/common/logo/logo'; import * as styles from './main-layout.module.less'; import { NavItem } from './nav-item/nav-item'; +import { ThemeToggle } from './theme-toggle'; export const MainLayout = () => { const { logout, JWTInfo } = useAuthContext(); @@ -69,6 +70,7 @@ export const MainLayout = () => { +