Merge pull request #53 from toeverything/feat/layout

Feat/layout
This commit is contained in:
Qi
2022-10-25 18:42:09 +08:00
committed by GitHub
26 changed files with 311 additions and 235 deletions

View File

@@ -9,7 +9,7 @@ import {
StyledMoreMenuItem, StyledMoreMenuItem,
IconButton, IconButton,
} from './styles'; } from './styles';
import { Popover } from '@/components/popover'; import { Popover } from '@/ui/popover';
import { useEditor } from '@/components/editor-provider'; import { useEditor } from '@/components/editor-provider';
import EditorModeSwitch from '@/components/editor-mode-switch'; import EditorModeSwitch from '@/components/editor-mode-switch';
import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons'; import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons';

View File

@@ -13,7 +13,7 @@ export const StyledHeader = styled('div')({
zIndex: '10', zIndex: '10',
}); });
export const StyledTitle = styled('div')({ export const StyledTitle = styled('div')(({ theme }) => ({
width: '720px', width: '720px',
height: '100%', height: '100%',
position: 'absolute', position: 'absolute',
@@ -23,8 +23,8 @@ export const StyledTitle = styled('div')({
margin: 'auto', margin: 'auto',
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
fontSize: '20px', fontSize: theme.font.base,
}); }));
export const StyledTitleWrapper = styled('div')({ export const StyledTitleWrapper = styled('div')({
maxWidth: '720px', maxWidth: '720px',

View File

@@ -25,7 +25,7 @@ import {
StyledLogo, StyledLogo,
StyledModalHeader, StyledModalHeader,
StyledModalHeaderLeft, StyledModalHeaderLeft,
CloseButton, StyledCloseButton,
StyledModalFooter, StyledModalFooter,
} from './style'; } from './style';
@@ -87,13 +87,13 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
<StyledLogo src={logo.src} alt="" /> <StyledLogo src={logo.src} alt="" />
<span>Alpha</span> <span>Alpha</span>
</StyledModalHeaderLeft> </StyledModalHeaderLeft>
<CloseButton <StyledCloseButton
onClick={() => { onClick={() => {
onClose(); onClose();
}} }}
> >
<CloseIcon width={12} height={12} /> <CloseIcon width={12} height={12} />
</CloseButton> </StyledCloseButton>
</StyledModalHeader> </StyledModalHeader>
<StyledContent> <StyledContent>
@@ -129,7 +129,13 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
<StyledModalFooter> <StyledModalFooter>
<p> <p>
<a href="">How is AFFiNE Alpha different?</a> <a
href="https://affine.pro/content/blog/affine-alpha-is-coming/index"
target="_blank"
rel="noreferrer"
>
How is AFFiNE Alpha different?
</a>
</p> </p>
<p>Copyright &copy; 2022 Toeverything</p> <p>Copyright &copy; 2022 Toeverything</p>
</StyledModalFooter> </StyledModalFooter>

View File

@@ -1,5 +1,7 @@
import { absoluteCenter, displayFlex, styled } from '@/styles'; import { absoluteCenter, displayFlex, styled } from '@/styles';
import bg from './bg.png'; import bg from './bg.png';
import CloseIcon from '@mui/icons-material/Close';
export const StyledModalContainer = styled('div')(({ theme }) => { export const StyledModalContainer = styled('div')(({ theme }) => {
return { return {
width: '100vw', width: '100vw',
@@ -175,20 +177,36 @@ export const StyledModalHeaderLeft = styled('div')(({ theme }) => {
}; };
}); });
export const CloseButton = styled('div')(({ theme }) => { export const StyledCloseButton = styled('div')(({ theme }) => {
return { return {
width: '30px', width: '60px',
height: '30px', height: '60px',
borderRadius: '6px',
color: theme.colors.iconColor, color: theme.colors.iconColor,
cursor: 'pointer', cursor: 'pointer',
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
position: 'absolute',
right: '0',
top: '0',
// TODO: we need to add @emotion/babel-plugin
'::after': {
content: '""',
width: '30px',
height: '30px',
borderRadius: '6px',
...absoluteCenter({ horizontal: true, vertical: true }),
},
':hover': { ':hover': {
color: theme.colors.primaryColor,
'::after': {
background: theme.colors.hoverBackground, background: theme.colors.hoverBackground,
}, },
},
svg: { svg: {
width: '20px', width: '20px',
height: '20px', height: '20px',
position: 'relative',
zIndex: 1,
}, },
}; };
}); });

View File

@@ -14,7 +14,7 @@ import {
UndoIcon, UndoIcon,
RedoIcon, RedoIcon,
} from './icons'; } from './icons';
import { Tooltip } from '@/components/tooltip'; import { Tooltip } from '@/ui/tooltip';
import Slide from '@mui/material/Slide'; import Slide from '@mui/material/Slide';
import { useEditor } from '@/components/editor-provider'; import { useEditor } from '@/components/editor-provider';
@@ -28,32 +28,32 @@ const toolbarList1 = [
{ {
flavor: 'text', flavor: 'text',
icon: <TextIcon />, icon: <TextIcon />,
toolTip: 'Text(coming soon)', toolTip: 'Text (coming soon)',
disable: true, disable: true,
}, },
{ {
flavor: 'shape', flavor: 'shape',
icon: <ShapeIcon />, icon: <ShapeIcon />,
toolTip: 'Shape(coming soon)', toolTip: 'Shape (coming soon)',
disable: true, disable: true,
}, },
{ {
flavor: 'sticky', flavor: 'sticky',
icon: <StickerIcon />, icon: <StickerIcon />,
toolTip: 'Sticky(coming soon)', toolTip: 'Sticky (coming soon)',
disable: true, disable: true,
}, },
{ {
flavor: 'pen', flavor: 'pen',
icon: <PenIcon />, icon: <PenIcon />,
toolTip: 'Pen(coming soon)', toolTip: 'Pen (coming soon)',
disable: true, disable: true,
}, },
{ {
flavor: 'connector', flavor: 'connector',
icon: <ConnectorIcon />, icon: <ConnectorIcon />,
toolTip: 'Connector(coming soon)', toolTip: 'Connector (coming soon)',
disable: true, disable: true,
}, },
]; ];
@@ -88,7 +88,7 @@ const UndoRedo = () => {
return ( return (
<StyledToolbarWrapper> <StyledToolbarWrapper>
<Tooltip content="undo" placement="right-start"> <Tooltip content="Undo" placement="right-start">
<StyledToolbarItem <StyledToolbarItem
disable={!canUndo} disable={!canUndo}
onClick={() => { onClick={() => {
@@ -98,7 +98,7 @@ const UndoRedo = () => {
<UndoIcon /> <UndoIcon />
</StyledToolbarItem> </StyledToolbarItem>
</Tooltip> </Tooltip>
<Tooltip content="redo" placement="right-start"> <Tooltip content="Redo" placement="right-start">
<StyledToolbarItem <StyledToolbarItem
disable={!canRedo} disable={!canRedo}
onClick={() => { onClick={() => {

View File

@@ -1,4 +1,4 @@
import { styled, displayFlex, fixedCenter } from '@/styles'; import { styled, displayFlex } from '@/styles';
export const StyledEdgelessToolbar = styled.div(({ theme }) => ({ export const StyledEdgelessToolbar = styled.div(({ theme }) => ({
height: '320px', height: '320px',
@@ -26,7 +26,7 @@ export const StyledToolbarItem = styled.div<{
width: '36px', width: '36px',
height: '36px', height: '36px',
...displayFlex('center', 'center'), ...displayFlex('center', 'center'),
color: disable ? '#C0C0C0' : theme.colors.iconColor, color: disable ? theme.colors.disableColor : theme.colors.iconColor,
cursor: 'pointer', cursor: 'pointer',
svg: { svg: {
width: '36px', width: '36px',

View File

@@ -38,20 +38,25 @@ const EdgelessItem = ({ active }: { active?: boolean }) => {
const AnimateRadioItem = ({ const AnimateRadioItem = ({
active, active,
status, status,
icon, icon: propsIcon,
label, label,
isLeft, isLeft,
...props ...props
}: AnimateRadioItemProps) => { }: AnimateRadioItemProps) => {
return ( const icon = (
<StyledRadioItem active={active} status={status} {...props}>
<StyledIcon shrink={status === 'shrink'} isLeft={isLeft}> <StyledIcon shrink={status === 'shrink'} isLeft={isLeft}>
{cloneElement(icon, { {cloneElement(propsIcon, {
active, active,
})} })}
</StyledIcon> </StyledIcon>
);
<StyledLabel shrink={status !== 'stretch'}>{label}</StyledLabel> return (
<StyledRadioItem active={active} status={status} {...props}>
{isLeft ? icon : null}
<StyledLabel shrink={status !== 'stretch'} isLeft={isLeft}>
{label}
</StyledLabel>
{isLeft ? null : icon}
</StyledRadioItem> </StyledRadioItem>
); );
}; };

View File

@@ -99,12 +99,23 @@ export const StyledRadioItem = styled('div')<{
export const StyledLabel = styled('div')<{ export const StyledLabel = styled('div')<{
shrink: boolean; shrink: boolean;
}>(({ shrink }) => { isLeft: boolean;
}>(({ shrink, isLeft }) => {
const animateScaleStretch = keyframes`${toString( const animateScaleStretch = keyframes`${toString(
spring({ scale: 0 }, { scale: 1 }, { preset: 'gentle' }) spring(
{ width: '0px' },
{ width: isLeft ? '65px' : '75px' },
{ preset: 'gentle' }
)
)}`; )}`;
const animateScaleShrink = keyframes( const animateScaleShrink = keyframes(
`${toString(spring({ scale: 1 }, { scale: 0 }, { preset: 'gentle' }))}` `${toString(
spring(
{ width: isLeft ? '65px' : '75px' },
{ width: '0px' },
{ preset: 'gentle' }
)
)}`
); );
const shrinkStyle = shrink const shrinkStyle = shrink
? { ? {
@@ -117,10 +128,12 @@ export const StyledLabel = styled('div')<{
return { return {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: isLeft ? 'flex-start' : 'flex-end',
fontSize: '16px', fontSize: '16px',
flexShrink: '0', flexShrink: '0',
transition: `transform ${ANIMATE_DURATION}ms`, transition: `transform ${ANIMATE_DURATION}ms`,
fontWeight: 'normal', fontWeight: 'normal',
overflow: 'hidden',
...shrinkStyle, ...shrinkStyle,
}; };
}); });

View File

@@ -7,7 +7,7 @@ import {
} from './style'; } from './style';
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons'; import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import Grow from '@mui/material/Grow'; import Grow from '@mui/material/Grow';
import { Tooltip } from '../tooltip'; import { Tooltip } from '@/ui/tooltip';
import { useEditor } from '@/components/editor-provider'; import { useEditor } from '@/components/editor-provider';
import { useModal } from '@/components/global-modal-provider'; import { useModal } from '@/components/global-modal-provider';
import { useTheme } from '@/styles'; import { useTheme } from '@/styles';

View File

@@ -6,6 +6,7 @@ export const StyledShortcutsModal = styled.div(({ theme }) => ({
paddingBottom: '28px', paddingBottom: '28px',
backgroundColor: theme.colors.popoverBackground, backgroundColor: theme.colors.popoverBackground,
boxShadow: theme.shadow.popover, boxShadow: theme.shadow.popover,
borderRadius: `${theme.radius.popover} 0 ${theme.radius.popover} ${theme.radius.popover}`,
color: theme.colors.popoverColor, color: theme.colors.popoverColor,
overflow: 'auto', overflow: 'auto',
boxRadius: '10px', boxRadius: '10px',
@@ -71,6 +72,7 @@ export const CloseButton = styled('div')(({ theme }) => {
}, },
':hover': { ':hover': {
background: theme.colors.hoverBackground, background: theme.colors.hoverBackground,
color: theme.colors.primaryColor,
}, },
}; };
}); });

View File

@@ -1,29 +0,0 @@
import { styled } from '@/styles';
import type { ReactNode, CSSProperties } from 'react';
import type { PopoverDirection } from './interface';
export interface PopoverContainerProps {
children?: ReactNode;
/**
* The pop-up window points to. The pop-up window has three rounded corners, one is a right angle, and the right angle is the direction of the pop-up window.
*/
direction: PopoverDirection;
style?: CSSProperties;
}
const border_radius_map: Record<PopoverContainerProps['direction'], string> = {
none: '10px',
'left-top': '0 10px 10px 10px',
'left-bottom': '10px 10px 10px 0',
'right-top': '10px 0 10px 10px',
'right-bottom': '10px 10px 0 10px',
};
export const PopoverContainer = styled('div')<
Pick<PopoverContainerProps, 'direction'>
>(({ direction, style }) => {
const borderRadius =
border_radius_map[direction] || border_radius_map['left-top'];
return {
borderRadius: borderRadius,
...style,
};
});

View File

@@ -1,65 +0,0 @@
import { type CSSProperties, type PropsWithChildren } from 'react';
import { PopoverContainer } from './Container';
import { Popper, type PopperProps } from '../popper';
import { useTheme } from '@/styles';
import type { PopperPlacementType, TooltipProps } from '@mui/material';
import type { PopoverDirection } from './interface';
export const placementToContainerDirection: Record<
PopperPlacementType,
PopoverDirection
> = {
top: 'none',
'top-start': 'left-bottom',
'top-end': 'right-bottom',
right: 'none',
'right-start': 'left-top',
'right-end': 'left-bottom',
bottom: 'none',
'bottom-start': 'left-top',
'bottom-end': 'right-top',
left: 'none',
'left-start': 'right-top',
'left-end': 'right-bottom',
auto: 'none',
'auto-start': 'none',
'auto-end': 'none',
};
const useTooltipStyle = (): CSSProperties => {
const { theme, mode } = useTheme();
return {
boxShadow: '1px 1px 4px rgba(0, 0, 0, 0.14)',
padding: '4px 12px',
backgroundColor:
mode === 'dark'
? theme.colors.popoverBackground
: theme.colors.primaryColor,
color: '#fff',
fontSize: theme.font.xs,
};
};
export const Tooltip = (
props: PropsWithChildren<PopperProps & Omit<TooltipProps, 'title'>>
) => {
const { content, placement = 'top-start' } = props;
const style = useTooltipStyle();
// If there is no content, hide forever
const visibleProp = content ? {} : { visible: false };
return (
<Popper
{...visibleProp}
placement="top"
{...props}
showArrow={false}
content={
<PopoverContainer
style={style}
direction={placementToContainerDirection[placement]}
>
{content}
</PopoverContainer>
}
/>
);
};

View File

@@ -1,9 +0,0 @@
export type TooltipProps = {
showArrow?: boolean;
};
export type PopoverDirection =
| 'none'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom';

View File

@@ -17,13 +17,13 @@ function MyApp({ Component, pageProps }: AppProps) {
return ( return (
<> <>
<Logger /> <Logger />
<EditorProvider>
<ThemeProvider> <ThemeProvider>
<ModalProvider> <ModalProvider>
<EditorProvider>
<Component {...pageProps} /> <Component {...pageProps} />
</EditorProvider>
</ModalProvider> </ModalProvider>
</ThemeProvider> </ThemeProvider>
</EditorProvider>
</> </>
); );
} }

View File

@@ -2,6 +2,6 @@ export type { ThemeMode, ThemeProviderProps, AffineTheme } from './types';
export * from './styled'; export * from './styled';
export { ThemeProvider } from './themeProvider'; export { ThemeProvider } from './themeProvider';
export { lightTheme, darkTheme } from './theme'; export * from './theme';
export { useTheme } from './hooks'; export { useTheme } from './hooks';
export * from './helper'; export * from './helper';

View File

@@ -1,10 +1,16 @@
import '@emotion/react'; import '@emotion/react';
import { AffineTheme, AffineThemeCSSVariables, ThemeMode } from './types'; import { AffineTheme, AffineThemeCSSVariables, ThemeMode } from './types';
import { EditorContainer } from '@blocksuite/editor';
const basicFontFamily = const basicFontFamily =
'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'; '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';
export const lightTheme: AffineTheme = { export const getLightTheme = (
editorMode: EditorContainer['mode']
): AffineTheme => {
return {
mode: 'light',
editorMode,
colors: { colors: {
primaryColor: '#6880FF', primaryColor: '#6880FF',
@@ -25,6 +31,7 @@ export const lightTheme: AffineTheme = {
placeHolderColor: '#C7C7C7', placeHolderColor: '#C7C7C7',
selectedColor: 'rgba(104, 128, 255, 0.1)', selectedColor: 'rgba(104, 128, 255, 0.1)',
borderColor: '#D0D7E3', borderColor: '#D0D7E3',
disableColor: '#C0C0C0',
}, },
font: { font: {
xs: '12px', xs: '12px',
@@ -43,21 +50,35 @@ export const lightTheme: AffineTheme = {
'4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06);', '4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06);',
modal: modal:
'4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06);', '4px 4px 7px rgba(58, 76, 92, 0.04), -4px -4px 13px rgba(58, 76, 92, 0.02), 6px 6px 36px rgba(58, 76, 92, 0.06);',
tooltip: '1px 1px 4px rgba(0, 0, 0, 0.14)',
}, },
space: { space: {
paragraph: '8px', paragraph: '8px',
}, },
radius: {
popover: '10px',
},
};
}; };
export const darkTheme: AffineTheme = { export const getDarkTheme = (
editorMode: EditorContainer['mode']
): AffineTheme => {
const lightTheme = getLightTheme(editorMode);
return {
...lightTheme, ...lightTheme,
mode: 'dark',
colors: { colors: {
primaryColor: '#6880FF', primaryColor: '#6880FF',
pageBackground: '#2c2c2c', pageBackground: '#2c2c2c',
hoverBackground: '#3C3C42', hoverBackground: '#3C3C42',
popoverBackground: '#1F2021', popoverBackground: '#1F2021',
codeBackground: '#505662', codeBackground:
editorMode === 'edgeless'
? lightTheme.colors.codeBackground
: '#505662',
textColor: '#fff', textColor: '#fff',
edgelessTextColor: '#3A4C5C', edgelessTextColor: '#3A4C5C',
@@ -66,18 +87,22 @@ export const darkTheme: AffineTheme = {
linkColor2: '#6880FF', linkColor2: '#6880FF',
linkVisitedColor: '#505FAB', linkVisitedColor: '#505FAB',
popoverColor: '#C6CBD9', popoverColor: '#C6CBD9',
codeColor: '#BDDBFD', codeColor:
editorMode === 'edgeless' ? lightTheme.colors.codeColor : '#BDDBFD',
quoteColor: '#C6CBD9', quoteColor: '#C6CBD9',
placeHolderColor: '#C7C7C7', placeHolderColor: '#C7C7C7',
selectedColor: 'rgba(240, 242, 255, 0.8)', selectedColor: 'rgba(104, 128, 255, 0.1)',
borderColor: '#4D4C53', borderColor: '#4D4C53',
disableColor: '#4b4b4b',
}, },
shadow: { shadow: {
popover: popover:
'0px 1px 10px -6px rgba(24, 39, 75, 0.08), 0px 3px 16px -6px rgba(24, 39, 75, 0.04)', '0px 1px 10px -6px rgba(24, 39, 75, 0.08), 0px 3px 16px -6px rgba(24, 39, 75, 0.04)',
modal: modal:
'0px 1px 10px -6px rgba(24, 39, 75, 0.08), 0px 3px 16px -6px rgba(24, 39, 75, 0.04)', '0px 1px 10px -6px rgba(24, 39, 75, 0.08), 0px 3px 16px -6px rgba(24, 39, 75, 0.04)',
tooltip: '1px 1px 4px rgba(0, 0, 0, 0.14)',
}, },
};
}; };
export const globalThemeVariables: ( export const globalThemeVariables: (
@@ -85,6 +110,9 @@ export const globalThemeVariables: (
theme: AffineTheme theme: AffineTheme
) => AffineThemeCSSVariables = (mode, theme) => { ) => AffineThemeCSSVariables = (mode, theme) => {
return { return {
'--affine-theme-mode': theme.mode,
'--affine-editor-mode': theme.editorMode,
'--affine-primary-color': theme.colors.primaryColor, '--affine-primary-color': theme.colors.primaryColor,
'--affine-page-background': theme.colors.pageBackground, '--affine-page-background': theme.colors.pageBackground,
@@ -105,9 +133,11 @@ export const globalThemeVariables: (
'--affine-selected-color': theme.colors.selectedColor, '--affine-selected-color': theme.colors.selectedColor,
'--affine-placeholder-color': theme.colors.placeHolderColor, '--affine-placeholder-color': theme.colors.placeHolderColor,
'--affine-border-color': theme.colors.borderColor, '--affine-border-color': theme.colors.borderColor,
'--affine-disable-color': theme.colors.disableColor,
'--affine-modal-shadow': theme.shadow.modal, '--affine-modal-shadow': theme.shadow.modal,
'--affine-popover-shadow': theme.shadow.popover, '--affine-popover-shadow': theme.shadow.popover,
'--affine-tooltip-shadow': theme.shadow.tooltip,
'--affine-font-xs': theme.font.xs, // tiny '--affine-font-xs': theme.font.xs, // tiny
'--affine-font-sm': theme.font.sm, // small '--affine-font-sm': theme.font.sm, // small
@@ -121,5 +151,6 @@ export const globalThemeVariables: (
'--affine-font-family2': theme.font.family2, '--affine-font-family2': theme.font.family2,
'--affine-paragraph-space': theme.space.paragraph, '--affine-paragraph-space': theme.space.paragraph,
'--affine-popover-radius': theme.radius.popover,
}; };
}; };

View File

@@ -11,13 +11,14 @@ import {
ThemeProviderProps, ThemeProviderProps,
ThemeProviderValue, ThemeProviderValue,
} from './types'; } from './types';
import { lightTheme, darkTheme, globalThemeVariables } from './theme'; import { getLightTheme, getDarkTheme, globalThemeVariables } from './theme';
import { SystemThemeHelper, localStorageThemeHelper } from './utils'; import { SystemThemeHelper, localStorageThemeHelper } from './utils';
import { useEditor } from '@/components/editor-provider';
export const ThemeContext = createContext<ThemeProviderValue>({ export const ThemeContext = createContext<ThemeProviderValue>({
mode: 'light', mode: 'light',
changeMode: () => {}, changeMode: () => {},
theme: lightTheme, theme: getLightTheme('page'),
}); });
export const ThemeProvider = ({ export const ThemeProvider = ({
@@ -26,8 +27,9 @@ export const ThemeProvider = ({
}: PropsWithChildren<ThemeProviderProps>) => { }: PropsWithChildren<ThemeProviderProps>) => {
const [theme, setTheme] = useState<Theme>(defaultTheme); const [theme, setTheme] = useState<Theme>(defaultTheme);
const [mode, setMode] = useState<ThemeMode>('auto'); const [mode, setMode] = useState<ThemeMode>('auto');
const { mode: editorMode } = useEditor();
const themeStyle = theme === 'light' ? lightTheme : darkTheme; const themeStyle =
theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode);
const changeMode = (themeMode: ThemeMode) => { const changeMode = (themeMode: ThemeMode) => {
themeMode !== mode && setMode(themeMode); themeMode !== mode && setMode(themeMode);
// Remember the theme mode which user selected for next time // Remember the theme mode which user selected for next time

View File

@@ -1,3 +1,5 @@
import { EditorContainer } from '@blocksuite/editor';
export type Theme = 'light' | 'dark'; export type Theme = 'light' | 'dark';
export type ThemeMode = Theme | 'auto'; export type ThemeMode = Theme | 'auto';
@@ -12,6 +14,8 @@ export type ThemeProviderValue = {
}; };
export interface AffineTheme { export interface AffineTheme {
mode: Theme;
editorMode: EditorContainer['mode'];
colors: { colors: {
primaryColor: string; primaryColor: string;
@@ -35,6 +39,7 @@ export interface AffineTheme {
placeHolderColor: string; placeHolderColor: string;
selectedColor: string; selectedColor: string;
borderColor: string; borderColor: string;
disableColor: string;
}; };
font: { font: {
xs: string; // tiny xs: string; // tiny
@@ -53,13 +58,20 @@ export interface AffineTheme {
shadow: { shadow: {
modal: string; modal: string;
popover: string; popover: string;
tooltip: string;
}; };
space: { space: {
paragraph: string; paragraph: string;
}; };
radius: {
popover: string;
};
} }
export interface AffineThemeCSSVariables { export interface AffineThemeCSSVariables {
'--affine-theme-mode': Theme;
'--affine-editor-mode': EditorContainer['mode'];
'--affine-primary-color': AffineTheme['colors']['primaryColor']; '--affine-primary-color': AffineTheme['colors']['primaryColor'];
'--affine-page-background': AffineTheme['colors']['pageBackground']; '--affine-page-background': AffineTheme['colors']['pageBackground'];
'--affine-popover-background': AffineTheme['colors']['popoverBackground']; '--affine-popover-background': AffineTheme['colors']['popoverBackground'];
@@ -79,9 +91,11 @@ export interface AffineThemeCSSVariables {
'--affine-placeholder-color': AffineTheme['colors']['placeHolderColor']; '--affine-placeholder-color': AffineTheme['colors']['placeHolderColor'];
'--affine-selected-color': AffineTheme['colors']['selectedColor']; '--affine-selected-color': AffineTheme['colors']['selectedColor'];
'--affine-border-color': AffineTheme['colors']['borderColor']; '--affine-border-color': AffineTheme['colors']['borderColor'];
'--affine-disable-color': AffineTheme['colors']['disableColor'];
'--affine-modal-shadow': AffineTheme['shadow']['popover']; '--affine-modal-shadow': AffineTheme['shadow']['modal'];
'--affine-popover-shadow': AffineTheme['shadow']['modal']; '--affine-popover-shadow': AffineTheme['shadow']['popover'];
'--affine-tooltip-shadow': AffineTheme['shadow']['tooltip'];
'--affine-font-xs': AffineTheme['font']['xs']; // tiny '--affine-font-xs': AffineTheme['font']['xs']; // tiny
'--affine-font-sm': AffineTheme['font']['sm']; // small '--affine-font-sm': AffineTheme['font']['sm']; // small
@@ -95,6 +109,8 @@ export interface AffineThemeCSSVariables {
'--affine-font-family2': AffineTheme['font']['family2']; '--affine-font-family2': AffineTheme['font']['family2'];
'--affine-paragraph-space': AffineTheme['space']['paragraph']; '--affine-paragraph-space': AffineTheme['space']['paragraph'];
'--affine-popover-radius': AffineTheme['radius']['popover'];
} }
declare module '@emotion/react' { declare module '@emotion/react' {

View File

@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import type { CSSProperties, PropsWithChildren, ReactNode } from 'react'; import type { CSSProperties, PropsWithChildren, ReactNode } from 'react';
import Grow from '@mui/material/Grow'; import Grow from '@mui/material/Grow';
import ClickAwayListener from '@mui/base/ClickAwayListener';
import { styled } from '@/styles'; import { styled } from '@/styles';
@@ -41,16 +42,15 @@ export const Popover = ({
}: PropsWithChildren<PopoverProps>) => { }: PropsWithChildren<PopoverProps>) => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
return ( return (
<ClickAwayListener
onClickAway={() => {
setShow(false);
}}
>
<StyledPopoverContainer <StyledPopoverContainer
onClick={() => { onClick={() => {
setShow(!show); setShow(!show);
}} }}
onMouseEnter={() => {
setShow(true);
}}
onMouseLeave={() => {
setShow(false);
}}
style={style} style={style}
> >
{children} {children}
@@ -60,5 +60,6 @@ export const Popover = ({
</StyledPopoverWrapper> </StyledPopoverWrapper>
</Grow> </Grow>
</StyledPopoverContainer> </StyledPopoverContainer>
</ClickAwayListener>
); );
}; };

View File

@@ -0,0 +1,53 @@
import { styled } from '@/styles';
import { PopperPlacementType } from '@mui/material';
export type PopperDirection =
| 'none'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom';
const getBorderRadius = (direction: PopperDirection, radius = '0') => {
const map: Record<PopperDirection, string> = {
none: `${radius}`,
'left-top': `0 ${radius} ${radius} ${radius}`,
'left-bottom': `${radius} ${radius} ${radius} 0`,
'right-top': `${radius} 0 ${radius} ${radius}`,
'right-bottom': `${radius} ${radius} 0 ${radius}`,
};
return map[direction];
};
export const placementToContainerDirection: Record<
PopperPlacementType,
PopperDirection
> = {
top: 'none',
'top-start': 'left-bottom',
'top-end': 'right-bottom',
right: 'none',
'right-start': 'left-top',
'right-end': 'left-bottom',
bottom: 'none',
'bottom-start': 'left-top',
'bottom-end': 'right-top',
left: 'none',
'left-start': 'right-top',
'left-end': 'right-bottom',
auto: 'none',
'auto-start': 'none',
'auto-end': 'none',
};
export const StyledPopperContainer = styled('div')<{
placement?: PopperPlacementType;
}>(({ theme, placement = 'top' }) => {
const direction = placementToContainerDirection[placement];
const borderRadius = getBorderRadius(direction, theme.radius.popover);
return {
borderRadius,
};
});
export default StyledPopperContainer;

View File

@@ -0,0 +1,32 @@
import { type PropsWithChildren } from 'react';
import StyledPopperContainer from '../shared/Container';
import { Popper, type PopperProps } from '../popper';
import { styled } from '@/styles';
import type { TooltipProps } from '@mui/material';
const StyledTooltip = styled(StyledPopperContainer)(({ theme }) => {
return {
boxShadow: theme.shadow.tooltip,
padding: '4px 12px',
backgroundColor:
theme.mode === 'dark'
? theme.colors.popoverBackground
: theme.colors.primaryColor,
color: '#fff',
fontSize: theme.font.xs,
};
});
export const Tooltip = (
props: PropsWithChildren<PopperProps & Omit<TooltipProps, 'title'>>
) => {
const { content, placement = 'top-start' } = props;
// If there is no content, hide forever
return content ? (
<Popper
{...props}
showArrow={false}
content={<StyledTooltip placement={placement}>{content}</StyledTooltip>}
/>
) : null;
};