feat: add dark mode
This commit is contained in:
parent
626c2a2fa6
commit
dc3bbe0b12
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
30
src/client/components/ColorSchemeSwitcher.tsx
Normal file
30
src/client/components/ColorSchemeSwitcher.tsx
Normal 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';
|
@ -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,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
@ -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'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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={{
|
||||||
|
@ -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">
|
||||||
|
@ -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',
|
||||||
}));
|
}));
|
||||||
|
@ -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
19
tailwind.config.ts
Normal 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;
|
Loading…
Reference in New Issue
Block a user