diff --git a/packages/app/src/components/Header/icons.tsx b/packages/app/src/components/Header/icons.tsx index 9e27fc7875..6f9d5ebee1 100644 --- a/packages/app/src/components/Header/icons.tsx +++ b/packages/app/src/components/Header/icons.tsx @@ -22,46 +22,6 @@ export const LogoIcon = ({ style = {}, ...props }: IconProps) => { ); }; -export const MoonIcon = ({ style = {}, ...props }: IconProps) => { - return ( - - - - ); -}; - -export const SunIcon = ({ style = {}, ...props }: IconProps) => { - return ( - - - - ); -}; - export const MoreIcon = ({ style = {}, ...props }: IconProps) => { return ( { - const { changeMode, mode } = useTheme(); - - return ( - <> - {mode === 'dark' ? ( - { - changeMode('light'); - }} - > - ) : ( - { - changeMode('dark'); - }} - > - )} - - ); -}; +import ThemeModeSwitch from '@/components/theme-mode-switch'; const PopoverContent = () => { const { editor, mode, setMode } = useEditor(); @@ -109,7 +85,7 @@ export const Header = () => { - + } style={{ marginLeft: '20px' }} diff --git a/packages/app/src/components/editor-mode-switch/style.ts b/packages/app/src/components/editor-mode-switch/style.ts index aea2af5738..f12dd45763 100644 --- a/packages/app/src/components/editor-mode-switch/style.ts +++ b/packages/app/src/components/editor-mode-switch/style.ts @@ -3,7 +3,7 @@ import { keyframes, styled } from '@/styles'; import spring, { toString } from 'css-spring'; import type { ItemStatus } from './type'; -const ANIMATE_DURATION = 300; +const ANIMATE_DURATION = 400; export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>( ({ shrink }) => { diff --git a/packages/app/src/components/theme-mode-switch/icons.tsx b/packages/app/src/components/theme-mode-switch/icons.tsx new file mode 100644 index 0000000000..43d9688d08 --- /dev/null +++ b/packages/app/src/components/theme-mode-switch/icons.tsx @@ -0,0 +1,44 @@ +import type { DOMAttributes, CSSProperties } from 'react'; +type IconProps = { + style?: CSSProperties; +} & DOMAttributes; + +export const MoonIcon = ({ style = {}, ...props }: IconProps) => { + return ( + + + + ); +}; + +export const SunIcon = ({ style = {}, ...props }: IconProps) => { + return ( + + + + ); +}; diff --git a/packages/app/src/components/theme-mode-switch/index.tsx b/packages/app/src/components/theme-mode-switch/index.tsx new file mode 100644 index 0000000000..324a3cfdae --- /dev/null +++ b/packages/app/src/components/theme-mode-switch/index.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { useTheme } from '@/styles'; +import { MoonIcon, SunIcon } from './icons'; +import { StyledThemeModeSwitch, StyledSwitchItem } from './style'; + +export const ThemeModeSwitch = () => { + const { mode, changeMode } = useTheme(); + const [isHover, setIsHover] = useState(false); + const [firstTrigger, setFirstTrigger] = useState(false); + return ( + { + setIsHover(true); + if (!firstTrigger) { + setFirstTrigger(true); + } + }} + onMouseLeave={() => { + setIsHover(false); + }} + > + { + changeMode('light'); + }} + > + + + { + changeMode('dark'); + }} + > + + + + ); +}; + +export default ThemeModeSwitch; diff --git a/packages/app/src/components/theme-mode-switch/style.ts b/packages/app/src/components/theme-mode-switch/style.ts new file mode 100644 index 0000000000..605fd304e2 --- /dev/null +++ b/packages/app/src/components/theme-mode-switch/style.ts @@ -0,0 +1,67 @@ +import { keyframes, styled } from '@/styles'; +import { CSSProperties } from 'react'; +// @ts-ignore +import spring, { toString } from 'css-spring'; + +const ANIMATE_DURATION = 400; + +export const StyledThemeModeSwitch = styled('div')({ + width: '32px', + height: '32px', + borderRadius: '5px', + overflow: 'hidden', + backgroundColor: 'transparent', + position: 'relative', +}); +export const StyledSwitchItem = styled('div')<{ + active: boolean; + isHover: boolean; + firstTrigger: boolean; +}>(({ active, isHover, firstTrigger, theme }) => { + const activeRaiseAnimate = keyframes`${toString( + spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' }) + )}`; + const raiseAnimate = keyframes`${toString( + spring({ top: '100%' }, { top: '0' }, { preset: 'gentle' }) + )}`; + const activeDeclineAnimate = keyframes`${toString( + spring({ top: '-100%' }, { top: '0' }, { preset: 'gentle' }) + )}`; + const declineAnimate = keyframes`${toString( + spring({ top: '0' }, { top: '100%' }, { preset: 'gentle' }) + )}`; + const activeStyle = active + ? { + color: theme.colors.disabled, + top: '0', + animation: firstTrigger + ? `${ + isHover ? activeRaiseAnimate : activeDeclineAnimate + } ${ANIMATE_DURATION}ms forwards` + : 'unset', + animationDirection: isHover ? 'normal' : 'alternate', + } + : ({ + top: '100%', + color: theme.colors.highlight, + backgroundColor: theme.colors.hoverBackground, + animation: firstTrigger + ? `${ + isHover ? raiseAnimate : declineAnimate + } ${ANIMATE_DURATION}ms forwards` + : 'unset', + animationDirection: isHover ? 'normal' : 'alternate', + } as CSSProperties); + + return { + width: '32px', + height: '32px', + display: 'flex', + position: 'absolute', + left: '0', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer', + ...activeStyle, + }; +}); diff --git a/packages/app/src/pages/affine.tsx b/packages/app/src/pages/affine.tsx index 73b35a6689..2d5bbe9b30 100644 --- a/packages/app/src/pages/affine.tsx +++ b/packages/app/src/pages/affine.tsx @@ -1,5 +1,23 @@ +import { styled } from '@/styles'; +import { ThemeModeSwitch } from '@/components/theme-mode-switch'; + +export const StyledHeader = styled('div')({ + height: '60px', + width: '100vw', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + position: 'relative', + padding: '0 22px', + borderBottom: '1px solid #e5e5e5', +}); + const Affine = () => { - return
affine page
; + return ( + + + + ); }; export default Affine; diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts index 5b00dd2583..464403c3c1 100644 --- a/packages/app/src/styles/theme.ts +++ b/packages/app/src/styles/theme.ts @@ -7,6 +7,7 @@ export const lightTheme: AffineTheme = { highlight: '#7389FD', disabled: '#9096A5', background: '#fff', + hoverBackground: '#F1F3FF', }, font: { xs: '12px', @@ -22,6 +23,7 @@ export const darkTheme: AffineTheme = { highlight: '#7389FD', disabled: '#9096A5', background: '#3d3c3f', + hoverBackground: '#F1F3FF', }, }; diff --git a/packages/app/src/styles/types.ts b/packages/app/src/styles/types.ts index 7c042957c5..9eec4c335c 100644 --- a/packages/app/src/styles/types.ts +++ b/packages/app/src/styles/types.ts @@ -17,6 +17,7 @@ export interface AffineTheme { highlight: string; disabled: string; background: string; + hoverBackground: string; }; font: { xs: string; // tiny