mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-16 13:57:02 +08:00
chore: change to monorepo
This commit is contained in:
4
packages/app/src/styles/hooks.ts
Normal file
4
packages/app/src/styles/hooks.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from './themeProvider';
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
6
packages/app/src/styles/index.ts
Normal file
6
packages/app/src/styles/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export type { ThemeMode, ThemeProviderProps, AffineTheme } from './types';
|
||||
|
||||
export { styled } from './styled';
|
||||
export { ThemeProvider } from './themeProvider';
|
||||
export { lightTheme, darkTheme } from './theme';
|
||||
export { useTheme } from './hooks';
|
||||
3
packages/app/src/styles/styled.ts
Normal file
3
packages/app/src/styles/styled.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import emotionStyled from '@emotion/styled';
|
||||
|
||||
export const styled = emotionStyled;
|
||||
55
packages/app/src/styles/theme.ts
Normal file
55
packages/app/src/styles/theme.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import '@emotion/react';
|
||||
import { AffineTheme, ThemeMode } from './types';
|
||||
|
||||
export const lightTheme: AffineTheme = {
|
||||
colors: {
|
||||
primary: '#0070f3',
|
||||
},
|
||||
};
|
||||
|
||||
export const darkTheme: AffineTheme = {
|
||||
colors: {
|
||||
primary: '#000',
|
||||
},
|
||||
};
|
||||
|
||||
export const globalThemeConstant = (mode: ThemeMode, theme: AffineTheme) => {
|
||||
const isDark = mode === 'dark';
|
||||
return {
|
||||
'--color-primary': theme.colors.primary,
|
||||
'--page-background-color': isDark ? '#3d3c3f' : '#fff',
|
||||
'--page-text-color': isDark ? '#fff' : '#3a4c5c',
|
||||
|
||||
// editor style variables
|
||||
'--affine-primary-color': isDark ? '#fff' : '#3a4c5c',
|
||||
'--affine-muted-color': '#a6abb7',
|
||||
'--affine-highlight-color': '#6880ff',
|
||||
'--affine-placeholder-color': '#c7c7c7',
|
||||
'--affine-selected-color': 'rgba(104, 128, 255, 0.1)',
|
||||
|
||||
'--affine-font-family':
|
||||
'Avenir Next, apple-system, BlinkMacSystemFont,Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,Segoe UI Symbol, Noto Color Emoji',
|
||||
|
||||
'--affine-font-family2':
|
||||
'Roboto Mono, apple-system, BlinkMacSystemFont,Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,Segoe UI Symbol, Noto Color Emoji',
|
||||
};
|
||||
};
|
||||
|
||||
const editorStyleVariable = {
|
||||
'--affine-primary-color': '#3a4c5c',
|
||||
'--affine-muted-color': '#a6abb7',
|
||||
'--affine-highlight-color': '#6880ff',
|
||||
'--affine-placeholder-color': '#c7c7c7',
|
||||
'--affine-selected-color': 'rgba(104, 128, 255, 0.1)',
|
||||
|
||||
'--affine-font-family':
|
||||
'Avenir Next, apple-system, BlinkMacSystemFont,Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,Segoe UI Symbol, Noto Color Emoji',
|
||||
|
||||
'--affine-font-family2':
|
||||
'Roboto Mono, apple-system, BlinkMacSystemFont,Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial,Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji,Segoe UI Symbol, Noto Color Emoji',
|
||||
};
|
||||
|
||||
const pageStyleVariable = {
|
||||
'--page-background-color': '#fff',
|
||||
'--page-text-color': '#3a4c5c',
|
||||
};
|
||||
79
packages/app/src/styles/themeProvider.tsx
Normal file
79
packages/app/src/styles/themeProvider.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
ThemeProvider as EmotionThemeProvider,
|
||||
Global,
|
||||
css,
|
||||
} from '@emotion/react';
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import {
|
||||
Theme,
|
||||
ThemeMode,
|
||||
ThemeProviderProps,
|
||||
ThemeProviderValue,
|
||||
} from './types';
|
||||
import { lightTheme, darkTheme, globalThemeConstant } from './theme';
|
||||
import { SystemThemeHelper, localStorageThemeHelper } from './utils';
|
||||
|
||||
export const ThemeContext = createContext<ThemeProviderValue>({
|
||||
mode: 'light',
|
||||
changeMode: () => {},
|
||||
theme: lightTheme,
|
||||
});
|
||||
|
||||
export const ThemeProvider = ({
|
||||
defaultTheme = 'light',
|
||||
children,
|
||||
}: PropsWithChildren<ThemeProviderProps>) => {
|
||||
const [theme, setTheme] = useState<Theme>(defaultTheme);
|
||||
const [mode, setMode] = useState<ThemeMode>('auto');
|
||||
|
||||
const themeStyle = theme === 'light' ? lightTheme : darkTheme;
|
||||
const changeMode = (themeMode: ThemeMode) => {
|
||||
themeMode !== mode && setMode(themeMode);
|
||||
// Remember the theme mode which user selected for next time
|
||||
localStorageThemeHelper.set(themeMode);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMode(localStorageThemeHelper.get() || 'auto');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const systemThemeHelper = new SystemThemeHelper();
|
||||
const selectedThemeMode = localStorageThemeHelper.get();
|
||||
|
||||
const themeMode = selectedThemeMode || mode;
|
||||
if (themeMode === 'auto') {
|
||||
setTheme(systemThemeHelper.get());
|
||||
} else {
|
||||
setTheme(themeMode);
|
||||
}
|
||||
|
||||
// When system theme changed, change the theme mode
|
||||
systemThemeHelper.onChange(() => {
|
||||
// TODO: There may be should be provided a way to let user choose whether to
|
||||
if (mode === 'auto') {
|
||||
setTheme(systemThemeHelper.get());
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
systemThemeHelper.dispose();
|
||||
};
|
||||
}, [mode]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
|
||||
<Global
|
||||
styles={css`
|
||||
:root {
|
||||
${globalThemeConstant(mode, themeStyle)}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<EmotionThemeProvider theme={themeStyle}>{children}</EmotionThemeProvider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
22
packages/app/src/styles/types.ts
Normal file
22
packages/app/src/styles/types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export type Theme = 'light' | 'dark';
|
||||
export type ThemeMode = Theme | 'auto';
|
||||
|
||||
export type ThemeProviderProps = {
|
||||
defaultTheme?: Theme;
|
||||
};
|
||||
|
||||
export type ThemeProviderValue = {
|
||||
theme: AffineTheme;
|
||||
mode: ThemeMode;
|
||||
changeMode: (newMode: ThemeMode) => void;
|
||||
};
|
||||
|
||||
export interface AffineTheme {
|
||||
colors: {
|
||||
primary: string;
|
||||
};
|
||||
}
|
||||
|
||||
declare module '@emotion/react' {
|
||||
export interface Theme extends AffineTheme {}
|
||||
}
|
||||
2
packages/app/src/styles/utils/index.ts
Normal file
2
packages/app/src/styles/utils/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './systemThemeHelper';
|
||||
export * from './localStorageThemeHelper';
|
||||
13
packages/app/src/styles/utils/localStorageThemeHelper.ts
Normal file
13
packages/app/src/styles/utils/localStorageThemeHelper.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ThemeMode } from '../types';
|
||||
|
||||
export class LocalStorageThemeHelper {
|
||||
name = 'Affine-theme-mode';
|
||||
get = (): ThemeMode | null => {
|
||||
return localStorage.getItem(this.name) as ThemeMode | null;
|
||||
};
|
||||
set = (mode: ThemeMode) => {
|
||||
localStorage.setItem(this.name, mode);
|
||||
};
|
||||
}
|
||||
|
||||
export const localStorageThemeHelper = new LocalStorageThemeHelper();
|
||||
29
packages/app/src/styles/utils/systemThemeHelper.ts
Normal file
29
packages/app/src/styles/utils/systemThemeHelper.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Theme } from '../types';
|
||||
|
||||
export class SystemThemeHelper {
|
||||
media: MediaQueryList = window.matchMedia('(prefers-color-scheme: light)');
|
||||
eventList: Array<(e: Event) => void> = [];
|
||||
eventHandler = (e: Event) => {
|
||||
this.eventList.forEach(fn => fn(e));
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.media.addEventListener('change', this.eventHandler);
|
||||
}
|
||||
|
||||
get = (): Theme => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 'light';
|
||||
}
|
||||
return this.media.matches ? 'light' : 'dark';
|
||||
};
|
||||
|
||||
onChange = (callback: () => void) => {
|
||||
this.eventList.push(callback);
|
||||
};
|
||||
|
||||
dispose = () => {
|
||||
this.eventList = [];
|
||||
this.media.removeEventListener('change', this.eventHandler);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user