feat: add dark mode

This commit is contained in:
moonrailgun 2023-12-08 21:49:49 +08:00
parent 626c2a2fa6
commit dc3bbe0b12
12 changed files with 93 additions and 25 deletions

View File

@ -67,6 +67,7 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-easy-sort": "^1.5.3", "react-easy-sort": "^1.5.3",
"react-grid-layout": "1.4.2", "react-grid-layout": "1.4.2",
"react-icons": "^4.12.0",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router": "^6.15.0", "react-router": "^6.15.0",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",

View File

@ -142,6 +142,9 @@ dependencies:
react-grid-layout: react-grid-layout:
specifier: 1.4.2 specifier: 1.4.2
version: 1.4.2(react-dom@18.2.0)(react@18.2.0) version: 1.4.2(react-dom@18.2.0)(react@18.2.0)
react-icons:
specifier: ^4.12.0
version: 4.12.0(react@18.2.0)
react-resizable: react-resizable:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.0.5(react-dom@18.2.0)(react@18.2.0) version: 3.0.5(react-dom@18.2.0)(react@18.2.0)
@ -6932,6 +6935,14 @@ packages:
resize-observer-polyfill: 1.5.1 resize-observer-polyfill: 1.5.1
dev: false dev: false
/react-icons@4.12.0(react@18.2.0):
resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==}
peerDependencies:
react: '*'
dependencies:
react: 18.2.0
dev: false
/react-is@16.13.1: /react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: false dev: false

View File

@ -15,6 +15,9 @@ import { MonitorPage } from './pages/Monitor';
import { WebsitePage } from './pages/Website'; import { WebsitePage } from './pages/Website';
import { useGlobalConfig } from './hooks/useConfig'; import { useGlobalConfig } from './hooks/useConfig';
import { useInjectWebsiteScript } from './hooks/useInjectWebsiteScript'; import { useInjectWebsiteScript } from './hooks/useInjectWebsiteScript';
import { ConfigProvider, theme } from 'antd';
import { useGlobalStateStore } from './store/global';
import clsx from 'clsx';
export const AppRoutes: React.FC = React.memo(() => { export const AppRoutes: React.FC = React.memo(() => {
const { info } = useUserStore(); const { info } = useUserStore();
@ -51,14 +54,24 @@ export const AppRoutes: React.FC = React.memo(() => {
AppRoutes.displayName = 'AppRoutes'; AppRoutes.displayName = 'AppRoutes';
export const App: React.FC = React.memo(() => { export const App: React.FC = React.memo(() => {
const colorScheme = useGlobalStateStore((state) => state.colorScheme);
const algorithm =
colorScheme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm;
return ( return (
<div className="App"> <div
className={clsx('App', {
dark: colorScheme === 'dark',
})}
>
<trpc.Provider client={trpcClient} queryClient={queryClient}> <trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter> <BrowserRouter>
<ConfigProvider theme={{ algorithm }}>
<TokenLoginContainer> <TokenLoginContainer>
<AppRoutes /> <AppRoutes />
</TokenLoginContainer> </TokenLoginContainer>
</ConfigProvider>
</BrowserRouter> </BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</trpc.Provider> </trpc.Provider>

View File

@ -0,0 +1,30 @@
import React from 'react';
import { useGlobalStateStore } from '../store/global';
import { LuMoon, LuSun } from 'react-icons/lu';
import { useEvent } from '../hooks/useEvent';
import { Button } from 'antd';
export const ColorSchemeSwitcher: React.FC = React.memo(() => {
const colorScheme = useGlobalStateStore((state) => state.colorScheme);
const handleSwitchColorScheme = useEvent(() => {
useGlobalStateStore.setState({
colorScheme: colorScheme === 'dark' ? 'light' : 'dark',
});
});
return (
<Button
icon={
colorScheme === 'dark' ? (
<LuMoon className="anticon" />
) : (
<LuSun className="anticon" />
)
}
shape="circle"
size="large"
onClick={handleSwitchColorScheme}
/>
);
});
ColorSchemeSwitcher.displayName = 'ColorSchemeSwitcher';

View File

@ -14,8 +14,8 @@ export const NavItem: React.FC<{
<Link to={props.to}> <Link to={props.to}>
<div <div
className={clsx('leading-[3.75rem] border-b-2', { className={clsx('leading-[3.75rem] border-b-2', {
'text-gray-950 border-blue-500': isCurrent, 'text-gray-950 dark:text-gray-200 border-blue-500': isCurrent,
'text-gray-900 border-transparent hover:text-gray-950 hover:border-blue-500': 'text-gray-900 dark:text-gray-400 border-transparent hover:text-gray-950 hover:border-blue-500':
!isCurrent, !isCurrent,
})} })}
> >

View File

@ -49,7 +49,7 @@ export const Dashboard: React.FC = React.memo(() => {
<div className="py-4"> <div className="py-4">
<div <div
className={clsx( className={clsx(
'flex gap-2 justify-end bg-white py-2', 'flex gap-2 justify-end bg-white dark:bg-gray-900 py-2',
isEditMode && 'sticky top-0 z-10' isEditMode && 'sticky top-0 z-10'
)} )}
> >

View File

@ -47,7 +47,7 @@ export const MonitorEventList: React.FC<MonitorEventListProps> = React.memo(
{item.type} {item.type}
</div> </div>
<div className="text-black text-opacity-60"> <div className="text-black text-opacity-60 dark:text-white dark:text-opacity-60">
{dayjs(item.createdAt).format('YYYY-MM-DD HH:mm:ss')} {dayjs(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}
</div> </div>

View File

@ -5,6 +5,7 @@ import { UserOutlined } from '@ant-design/icons';
import { Button, Dropdown } from 'antd'; import { Button, Dropdown } from 'antd';
import { useUserStore } from '../store/user'; import { useUserStore } from '../store/user';
import { useLogout } from '../api/model/user'; import { useLogout } from '../api/model/user';
import { ColorSchemeSwitcher } from '../components/ColorSchemeSwitcher';
export const Layout: React.FC = React.memo(() => { export const Layout: React.FC = React.memo(() => {
const [params] = useSearchParams(); const [params] = useSearchParams();
@ -25,12 +26,12 @@ export const Layout: React.FC = React.memo(() => {
const showHeader = !params.has('hideHeader'); const showHeader = !params.has('hideHeader');
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full dark:bg-gray-900 dark:text-gray-300">
{showHeader && ( {showHeader && (
<div className="flex items-center bg-gray-100 px-4 sticky top-0 z-20"> <div className="flex items-center bg-gray-100 dark:bg-gray-800 px-4 sticky top-0 z-20">
<div className="px-2 mr-10 font-bold flex items-center"> <div className="px-2 mr-10 font-bold flex items-center">
<img src="/icon.svg" className="w-10 h-10 mr-2" /> <img src="/icon.svg" className="w-10 h-10 mr-2" />
<span className="text-xl">Tianji</span> <span className="text-xl dark:text-gray-200">Tianji</span>
</div> </div>
<div className="flex gap-8"> <div className="flex gap-8">
<NavItem to="/dashboard" label="Dashboard" /> <NavItem to="/dashboard" label="Dashboard" />
@ -42,7 +43,9 @@ export const Layout: React.FC = React.memo(() => {
<div className="flex-1" /> <div className="flex-1" />
<div> <div className="flex gap-2">
<ColorSchemeSwitcher />
<Dropdown <Dropdown
placement="bottomRight" placement="bottomRight"
menu={{ menu={{

View File

@ -14,7 +14,7 @@ export const MonitorPage: React.FC = React.memo(() => {
<div> <div>
<div className="px-4 pt-4"> <div className="px-4 pt-4">
<div <div
className="px-3 py-2 rounded-full bg-green-400 hover:bg-green-500 text-white inline-block cursor-pointer" className="px-3 py-2 rounded-full bg-green-400 hover:bg-green-500 text-white dark:text-gray-700 inline-block cursor-pointer"
onClick={() => navigate('/monitor/add')} onClick={() => navigate('/monitor/add')}
> >
Add new Montior Add new Montior
@ -22,7 +22,7 @@ export const MonitorPage: React.FC = React.memo(() => {
</div> </div>
</div> </div>
<div className="py-5 flex flex-1 overflow-hidden"> <div className="py-5 flex flex-1 overflow-hidden">
<div className="w-5/12 bg-gray-50"> <div className="w-5/12 bg-gray-50 dark:bg-gray-800">
<MonitorList /> <MonitorList />
</div> </div>
<div className="w-7/12"> <div className="w-7/12">

View File

@ -18,10 +18,12 @@ interface GlobalState {
dateRange: DateRange; dateRange: DateRange;
startDate: Dayjs | null; startDate: Dayjs | null;
endDate: Dayjs | null; endDate: Dayjs | null;
colorScheme: 'light' | 'dark';
} }
export const useGlobalStateStore = create<GlobalState>(() => ({ export const useGlobalStateStore = create<GlobalState>(() => ({
dateRange: DateRange.Last24Hours, dateRange: DateRange.Last24Hours,
startDate: null, startDate: null,
endDate: null, endDate: null,
colorScheme: 'light',
})); }));

View File

@ -1,11 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

19
tailwind.config.ts Normal file
View File

@ -0,0 +1,19 @@
import type { Config } from 'tailwindcss';
import colors from 'tailwindcss/colors';
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
gray: colors.neutral,
},
},
},
darkMode: 'class',
corePlugins: {
preflight: false,
},
plugins: [],
} satisfies Config;