diff --git a/public/globals.css b/public/globals.css index 1c45aaea00..5af0a061bd 100644 --- a/public/globals.css +++ b/public/globals.css @@ -2,6 +2,7 @@ -webkit-overflow-scrolling: touch; -webkit-tap-highlight-color: rgba(255, 255, 255, 0); box-sizing: border-box; + transition: all 0.1s; } html, body, @@ -144,7 +145,7 @@ button, select, keygen, legend { - color: var(--affine-primary-color); + color: var(--page-text-color); outline: 0; font-size: 18px; line-height: 1.5; @@ -155,7 +156,7 @@ body { } a, a:hover { - color: var(--affine-primary-color); + color: var(--page-text-color); } input { diff --git a/public/variable.css b/public/variable.css index 8b567ef536..bba6bedc45 100644 --- a/public/variable.css +++ b/public/variable.css @@ -1,17 +1,22 @@ -:root { - --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); +/*:root {*/ +/* --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-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; -} +/* --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;*/ +/*}*/ + +/*:root {*/ +/* --page-background-color: #fff;*/ +/* --page-text-color: #3a4c5c;*/ +/*}*/ diff --git a/src/components/Header/icons.tsx b/src/components/Header/icons.tsx index ba3150dc3a..b0f5374169 100644 --- a/src/components/Header/icons.tsx +++ b/src/components/Header/icons.tsx @@ -6,7 +6,7 @@ type IconProps = { } & DOMAttributes; export const LogoIcon = ({ - color = '#000', + color, style: propsStyle = {}, ...props }: IconProps) => { @@ -30,7 +30,7 @@ export const LogoIcon = ({ }; export const EdgelessIcon = ({ - color = '#000', + color, style: propsStyle = {}, ...props }: IconProps) => { @@ -60,7 +60,7 @@ export const EdgelessIcon = ({ }; export const MoonIcon = ({ - color = '#000', + color, style: propsStyle = {}, ...props }: IconProps) => { @@ -85,7 +85,7 @@ export const MoonIcon = ({ }; export const PaperIcon = ({ - color = '#000', + color, style: propsStyle = {}, ...props }: IconProps) => { @@ -116,7 +116,7 @@ export const PaperIcon = ({ }; export const SunIcon = ({ - color = '#000', + color, style: propsStyle = {}, ...props }: IconProps) => { @@ -139,3 +139,50 @@ export const SunIcon = ({ ); }; + +export const MoreIcon = ({ + color, + style: propsStyle = {}, + ...props +}: IconProps) => { + const style = { fill: color, ...propsStyle, transform: 'rotate(90deg)' }; + + return ( + + + + + + ); +}; +export const ExportIcon = ({ + color, + style: propsStyle = {}, + ...props +}: IconProps) => { + const style = { fill: color, ...propsStyle }; + return ( + + + + ); +}; diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 2e57a49167..c4a02503a7 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -1,51 +1,27 @@ import React, { useEffect, useState } from 'react'; -import { styled } from '@/styles'; -import { LogoIcon, PaperIcon, EdgelessIcon, SunIcon, MoonIcon } from './icons'; +import { + LogoIcon, + PaperIcon, + EdgelessIcon, + SunIcon, + MoonIcon, + MoreIcon, + ExportIcon, +} from './icons'; +import { + StyledHeader, + StyledTitle, + StyledTitleWrapper, + StyledLogo, + StyledModeSwitch, + StyledHeaderRightSide, + StyledMoreMenuItem, +} from './styles'; +import { Popover } from '@/components/popover'; +import { useTheme } from '@/styles'; +import { useEditor } from '@/components/editor-provider'; -const StyledHeader = styled('div')({ - height: '60px', - width: '100vw', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - position: 'relative', - padding: '0 22px', -}); - -const StyledTitle = styled('div')({ - width: '720px', - height: '100%', - position: 'absolute', - left: 0, - right: 0, - top: 0, - margin: 'auto', - - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - fontWeight: '600', - fontSize: '20px', -}); - -const StyledTitleWrapper = styled('div')({ - maxWidth: '720px', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - position: 'relative', -}); - -const StyledLogo = styled('div')({}); - -const StyledModeSwitch = styled('div')({ - height: '100%', - display: 'flex', - alignItems: 'center', - marginRight: '15px', -}); - -const ModeSwitch = () => { +const EditorModeSwitch = () => { const [mode, setMode] = useState<'page' | 'edgeless'>('page'); const handleModeSwitch = (mode: 'page' | 'edgeless') => { @@ -75,27 +51,67 @@ const ModeSwitch = () => { }; const DarkModeSwitch = () => { - const [darkMode, setDarkMode] = useState(false); + const { changeMode, mode } = useTheme(); + + return ( + <> + {mode === 'dark' ? ( + { + changeMode('light'); + }} + > + ) : ( + { + changeMode('dark'); + }} + > + )} + + ); +}; + +const PopoverContent = () => { + const { editor } = useEditor(); return (
- - + { + editor && editor.contentParser.onExportHtml(); + }} + > + + Export to HTML + + { + editor && editor.contentParser.onExportMarkdown(); + }} + > + + Export to markdown +
); }; export const Header = () => { const [title, setTitle] = useState(''); + const { editor } = useEditor(); useEffect(() => { - setTimeout(() => { - const editor = window.editor; + if (editor) { setTitle(editor.model.title || ''); editor.model.propsUpdated.on(() => { setTitle(editor.model.title); }); - }, 500); - }, []); + } + }, [editor]); return ( @@ -103,9 +119,16 @@ export const Header = () => { {}} /> - + {title} + + + + }> + + + ); }; diff --git a/src/components/Header/styles.ts b/src/components/Header/styles.ts new file mode 100644 index 0000000000..e2175e7aa7 --- /dev/null +++ b/src/components/Header/styles.ts @@ -0,0 +1,73 @@ +import { styled } from '@/styles'; + +export const StyledHeader = styled('div')({ + height: '60px', + width: '100vw', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + position: 'relative', + padding: '0 22px', +}); + +export const StyledTitle = styled('div')({ + width: '720px', + height: '100%', + position: 'absolute', + left: 0, + right: 0, + top: 0, + margin: 'auto', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + fontWeight: '600', + fontSize: '20px', +}); + +export const StyledTitleWrapper = styled('div')({ + maxWidth: '720px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + position: 'relative', +}); + +export const StyledLogo = styled('div')({}); + +export const StyledModeSwitch = styled('div')({ + height: '100%', + display: 'flex', + alignItems: 'center', + marginRight: '12px', +}); + +export const StyledHeaderRightSide = styled('div')({ + height: '100%', + display: 'flex', + alignItems: 'center', +}); + +export const StyledMoreMenuItem = styled('div')({ + height: '32px', + display: 'flex', + alignItems: 'center', + borderRadius: '5px', + fontSize: '14px', + color: '#4C6275', + padding: '0 14px', + svg: { + fill: '#4C6275', + width: '16px', + height: '16px', + marginRight: '14px', + }, + ':hover': { + color: 'var(--affine-highlight-color)', + background: '#F1F3FF', + svg: { + fill: 'var(--affine-highlight-color)', + }, + }, +}); diff --git a/src/components/editor-provider.tsx b/src/components/editor-provider.tsx new file mode 100644 index 0000000000..de6d95d845 --- /dev/null +++ b/src/components/editor-provider.tsx @@ -0,0 +1,31 @@ +import type { EditorContainer } from '@blocksuite/editor'; + +import { createContext, useContext, useEffect, useState } from 'react'; +import type { PropsWithChildren } from 'react'; + +type EditorContextValue = { + editor: EditorContainer | null; + setEditor: (editor: EditorContainer) => void; +}; +type EditorContextProps = PropsWithChildren<{}>; + +export const EditorContext = createContext({ + editor: null, + setEditor: () => {}, +}); + +export const useEditor = () => useContext(EditorContext); + +export const EditorProvider = ({ + children, +}: PropsWithChildren) => { + const [editor, setEditor] = useState(null); + + return ( + + {children} + + ); +}; + +export default EditorProvider; diff --git a/src/components/editor.tsx b/src/components/editor.tsx index d5941cf8c1..d6cf8b504e 100644 --- a/src/components/editor.tsx +++ b/src/components/editor.tsx @@ -4,16 +4,13 @@ import { Text } from '@blocksuite/store'; import '@blocksuite/blocks'; import '@blocksuite/editor'; import '@blocksuite/blocks/style'; +import { useEditor } from '@/components/editor-provider'; -declare global { - interface Window { - editor: EditorContainer; - } -} export const Editor = () => { const editorRef = useRef(); - + const { setEditor } = useEditor(); useEffect(() => { + setEditor(editorRef.current!); const { store } = editorRef.current as EditorContainer; const pageId = store.addBlock({ diff --git a/src/components/popover/index.tsx b/src/components/popover/index.tsx new file mode 100644 index 0000000000..7541a4b4f1 --- /dev/null +++ b/src/components/popover/index.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import type { PropsWithChildren } from 'react'; +import { styled } from '@/styles'; + +type PopoverProps = { + popoverContent?: React.ReactNode; +}; + +const StyledPopoverContainer = styled('div')({ + position: 'relative', + cursor: 'pointer', +}); + +const StyledPopoverWrapper = styled('div')({ + position: 'absolute', + bottom: '0', + right: '0', + paddingTop: '46px', +}); +const StyledPopover = styled('div')<{ show: boolean }>(({ show }) => { + return { + width: '248px', + background: '#fff', + boxShadow: + '0px 1px 10px -6px rgba(24, 39, 75, 0.5), 0px 3px 16px -6px rgba(24, 39, 75, 0.04)', + borderRadius: '10px 0px 10px 10px', + padding: '8px 4px', + display: show ? 'block' : 'none', + position: 'absolute', + top: '46px', + right: '0', + }; +}); +export const Popover = ({ + children, + popoverContent, +}: PropsWithChildren) => { + const [show, setShow] = useState(false); + return ( + { + setShow(!show); + }} + onMouseEnter={() => { + setShow(true); + }} + onMouseLeave={() => { + setShow(false); + }} + > + {children} + + {popoverContent} + + + ); +}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 81efb41989..b56c143a59 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -3,6 +3,7 @@ import dynamic from 'next/dynamic'; import '../../public/globals.css'; import '../../public/variable.css'; import './temporary.css'; +import { EditorProvider } from '@/components/editor-provider'; const ThemeProvider = dynamic(() => import('@/styles/themeProvider'), { ssr: false, @@ -11,7 +12,9 @@ const ThemeProvider = dynamic(() => import('@/styles/themeProvider'), { function MyApp({ Component, pageProps }: AppProps) { return ( - + + + ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4f9f3f806f..632b2ef9f5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -16,6 +16,8 @@ const StyledPage = styled('div')({ height: '100vh', display: 'flex', flexDirection: 'column', + backgroundColor: 'var(--page-background-color)', + transition: 'background-color .5s', }); const DynamicEditor = dynamic(() => import('../components/editor'), { @@ -24,7 +26,6 @@ const DynamicEditor = dynamic(() => import('../components/editor'), { }); const Home: NextPage = () => { - const { changeMode, mode } = useTheme(); return (
diff --git a/src/styles/theme.ts b/src/styles/theme.ts index 7afc298e8c..de9c321b78 100644 --- a/src/styles/theme.ts +++ b/src/styles/theme.ts @@ -1,5 +1,5 @@ import '@emotion/react'; -import { AffineTheme } from './types'; +import { AffineTheme, ThemeMode } from './types'; export const lightTheme: AffineTheme = { colors: { @@ -13,8 +13,43 @@ export const darkTheme: AffineTheme = { }, }; -export const globalThemeConstant = (theme: AffineTheme) => { +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', +}; diff --git a/src/styles/themeProvider.tsx b/src/styles/themeProvider.tsx index af87a29e7c..4cdd4e9b00 100644 --- a/src/styles/themeProvider.tsx +++ b/src/styles/themeProvider.tsx @@ -67,7 +67,7 @@ export const ThemeProvider = ({