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,
IconButton,
} from './styles';
import { Popover } from '@/components/popover';
import { Popover } from '@/ui/popover';
import { useEditor } from '@/components/editor-provider';
import EditorModeSwitch from '@/components/editor-mode-switch';
import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons';

View File

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

View File

@@ -25,7 +25,7 @@ import {
StyledLogo,
StyledModalHeader,
StyledModalHeaderLeft,
CloseButton,
StyledCloseButton,
StyledModalFooter,
} from './style';
@@ -87,13 +87,13 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
<StyledLogo src={logo.src} alt="" />
<span>Alpha</span>
</StyledModalHeaderLeft>
<CloseButton
<StyledCloseButton
onClick={() => {
onClose();
}}
>
<CloseIcon width={12} height={12} />
</CloseButton>
</StyledCloseButton>
</StyledModalHeader>
<StyledContent>
@@ -129,7 +129,13 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {
<StyledModalFooter>
<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>Copyright &copy; 2022 Toeverything</p>
</StyledModalFooter>

View File

@@ -1,5 +1,7 @@
import { absoluteCenter, displayFlex, styled } from '@/styles';
import bg from './bg.png';
import CloseIcon from '@mui/icons-material/Close';
export const StyledModalContainer = styled('div')(({ theme }) => {
return {
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 {
width: '30px',
height: '30px',
borderRadius: '6px',
width: '60px',
height: '60px',
color: theme.colors.iconColor,
cursor: 'pointer',
...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': {
background: theme.colors.hoverBackground,
color: theme.colors.primaryColor,
'::after': {
background: theme.colors.hoverBackground,
},
},
svg: {
width: '20px',
height: '20px',
position: 'relative',
zIndex: 1,
},
};
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,64 +0,0 @@
import { useState } from 'react';
import type { CSSProperties, PropsWithChildren, ReactNode } from 'react';
import Grow from '@mui/material/Grow';
import { styled } from '@/styles';
type PopoverProps = {
popoverContent?: ReactNode;
style?: CSSProperties;
};
const StyledPopoverContainer = styled('div')({
position: 'relative',
cursor: 'pointer',
});
const StyledPopoverWrapper = styled('div')({
position: 'absolute',
bottom: '0',
right: '0',
paddingTop: '46px',
zIndex: 1000,
});
const StyledPopover = styled('div')(({ theme }) => {
return {
width: '248px',
background: theme.colors.popoverBackground,
boxShadow: theme.shadow.popover,
color: theme.colors.popoverColor,
borderRadius: '10px 0px 10px 10px',
padding: '8px 4px',
position: 'absolute',
top: '46px',
right: '0',
};
});
export const Popover = ({
children,
popoverContent,
style = {},
}: PropsWithChildren<PopoverProps>) => {
const [show, setShow] = useState(false);
return (
<StyledPopoverContainer
onClick={() => {
setShow(!show);
}}
onMouseEnter={() => {
setShow(true);
}}
onMouseLeave={() => {
setShow(false);
}}
style={style}
>
{children}
<Grow in={show}>
<StyledPopoverWrapper>
<StyledPopover>{popoverContent}</StyledPopover>
</StyledPopoverWrapper>
</Grow>
</StyledPopoverContainer>
);
};

View File

@@ -1,93 +0,0 @@
import { forwardRef } from 'react';
import { styled } from '@/styles';
import { PopperArrowProps } from './interface';
// eslint-disable-next-line react/display-name
export const PopperArrow = forwardRef<HTMLElement, PopperArrowProps>(
({ placement }, ref) => {
return <StyledArrow placement={placement} ref={ref} />;
}
);
const getArrowStyle = (placement: PopperArrowProps['placement']) => {
if (placement.indexOf('bottom') === 0) {
return {
top: 0,
left: 0,
marginTop: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '0 1em 1em 1em',
borderColor: `transparent transparent #98ACBD transparent`,
},
};
}
if (placement.indexOf('top') === 0) {
return {
bottom: 0,
left: 0,
marginBottom: '-0.9em',
width: '3em',
height: '1em',
'&::before': {
borderWidth: '1em 1em 0 1em',
borderColor: `#98ACBD transparent transparent transparent`,
},
};
}
if (placement.indexOf('left') === 0) {
return {
right: 0,
marginRight: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 0 1em 1em',
borderColor: `transparent transparent transparent #98ACBD`,
},
};
}
if (placement.indexOf('right') === 0) {
return {
left: 0,
marginLeft: '-0.9em',
height: '3em',
width: '1em',
'&::before': {
borderWidth: '1em 1em 1em 0',
borderColor: `transparent #98ACBD transparent transparent`,
},
};
}
return {
display: 'none',
};
};
const StyledArrow = styled('span')<{
placement: PopperArrowProps['placement'];
}>(({ placement }) => {
return {
position: 'absolute',
fontSize: '7px',
width: '3em',
'::before': {
content: '""',
margin: 'auto',
display: 'block',
width: 0,
height: 0,
borderStyle: 'solid',
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
...getArrowStyle(placement),
};
});

View File

@@ -1,189 +0,0 @@
import {
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import PopperUnstyled from '@mui/base/PopperUnstyled';
import ClickAwayListener from '@mui/base/ClickAwayListener';
import Grow from '@mui/material/Grow';
import { styled } from '@/styles';
import { PopperProps, VirtualElement } from './interface';
import { PopperArrow } from './PopoverArrow';
export const Popper = ({
children,
content,
anchorEl: propsAnchorEl,
placement = 'top-start',
defaultVisible = false,
visible: propsVisible,
trigger = 'hover',
pointerEnterDelay = 100,
pointerLeaveDelay = 100,
onVisibleChange,
popoverStyle,
popoverClassName,
anchorStyle,
anchorClassName,
zIndex,
offset = [0, 5],
showArrow = false,
popperHandlerRef,
onClick,
onClickAway,
...popperProps
}: PopperProps) => {
// @ts-ignore
const [anchorEl, setAnchorEl] = useState<VirtualElement>(null);
const [visible, setVisible] = useState(defaultVisible);
// @ts-ignore
const [arrowRef, setArrowRef] = useState<HTMLElement>(null);
const popperRef = useRef();
const pointerLeaveTimer = useRef<number>();
const pointerEnterTimer = useRef<number>();
const visibleControlledByParent = typeof propsVisible !== 'undefined';
const isAnchorCustom = typeof propsAnchorEl !== 'undefined';
const hasHoverTrigger = useMemo(() => {
return (
trigger === 'hover' ||
(Array.isArray(trigger) && trigger.includes('hover'))
);
}, [trigger]);
const hasClickTrigger = useMemo(() => {
return (
trigger === 'click' ||
(Array.isArray(trigger) && trigger.includes('click'))
);
}, [trigger]);
const onPointerEnterHandler = () => {
if (!hasHoverTrigger || visibleControlledByParent) {
return;
}
window.clearTimeout(pointerLeaveTimer.current);
pointerEnterTimer.current = window.setTimeout(() => {
setVisible(true);
}, pointerEnterDelay);
};
const onPointerLeaveHandler = () => {
if (!hasHoverTrigger || visibleControlledByParent) {
return;
}
window.clearTimeout(pointerEnterTimer.current);
pointerLeaveTimer.current = window.setTimeout(() => {
setVisible(false);
}, pointerLeaveDelay);
};
useEffect(() => {
onVisibleChange?.(visible);
}, [visible, onVisibleChange]);
useImperativeHandle(popperHandlerRef, () => {
return {
setVisible: (visible: boolean) => {
!visibleControlledByParent && setVisible(visible);
},
};
});
// @ts-ignore
return (
<ClickAwayListener
onClickAway={() => {
if (visibleControlledByParent) {
onClickAway?.();
} else {
setVisible(false);
}
}}
>
<Container>
{isAnchorCustom ? null : (
<div
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
onClick={e => {
if (!hasClickTrigger || visibleControlledByParent) {
onClick?.(e);
return;
}
setVisible(!visible);
}}
onPointerEnter={onPointerEnterHandler}
onPointerLeave={onPointerLeaveHandler}
style={anchorStyle}
className={anchorClassName}
>
{children}
</div>
)}
<BasicStyledPopper
// @ts-ignore
popperRef={popperRef}
open={visibleControlledByParent ? propsVisible : visible}
zIndex={zIndex}
anchorEl={isAnchorCustom ? propsAnchorEl : anchorEl}
placement={placement}
transition
modifiers={[
{
name: 'offset',
options: {
offset,
},
},
{
name: 'arrow',
enabled: showArrow,
options: {
element: arrowRef,
},
},
]}
{...popperProps}
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<div
onPointerEnter={onPointerEnterHandler}
onPointerLeave={onPointerLeaveHandler}
style={popoverStyle}
className={popoverClassName}
>
{showArrow && (
// @ts-ignore
<PopperArrow placement={placement} ref={setArrowRef} />
)}
{content}
</div>
</Grow>
)}
</BasicStyledPopper>
</Container>
</ClickAwayListener>
);
};
// The children of ClickAwayListener must be a DOM Node to judge whether the click is outside, use node.contains
const Container = styled('div')({
display: 'contents',
});
const BasicStyledPopper = styled(PopperUnstyled, {
shouldForwardProp: (propName: string) =>
!['zIndex'].some(name => name === propName),
})<{
zIndex?: number;
}>(({ zIndex, theme }) => {
return {
zIndex: zIndex,
};
});

View File

@@ -1,2 +0,0 @@
export * from './interface';
export * from './Popper';

View File

@@ -1,66 +0,0 @@
import type { CSSProperties, ReactNode, Ref } from 'react';
import {
type PopperPlacementType,
type PopperUnstyledProps,
} from '@mui/base/PopperUnstyled';
export type VirtualElement = {
getBoundingClientRect: () => ClientRect | DOMRect;
contextElement?: Element;
};
export type PopperHandler = {
setVisible: (visible: boolean) => void;
};
export type PopperArrowProps = {
placement: PopperPlacementType;
};
export type PopperProps = {
// Popover content
content: ReactNode;
// Popover trigger
children?: ReactNode;
// Whether the default is implicit
defaultVisible?: boolean;
// Used to manually control the visibility of the Popover
visible?: boolean;
// TODO: support focus
trigger?: 'hover' | 'click' | 'focus' | ('click' | 'hover' | 'focus')[];
// How long does it take for the mouse to display the Popover, in milliseconds
pointerEnterDelay?: number;
// How long does it take to hide the Popover after the mouse moves out, in milliseconds
pointerLeaveDelay?: number;
// Callback fired when the component closed or open
onVisibleChange?: (visible: boolean) => void;
// Popover container style
popoverStyle?: CSSProperties;
// Popover container class name
popoverClassName?: string;
// Anchor style
anchorStyle?: CSSProperties;
// Anchor class name
anchorClassName?: string;
// Popover z-index
zIndex?: number;
offset?: [number, number];
showArrow?: boolean;
popperHandlerRef?: Ref<PopperHandler>;
onClickAway?: () => void;
} & Omit<PopperUnstyledProps, 'open' | 'ref'>;

View File

@@ -6,6 +6,7 @@ export const StyledShortcutsModal = styled.div(({ theme }) => ({
paddingBottom: '28px',
backgroundColor: theme.colors.popoverBackground,
boxShadow: theme.shadow.popover,
borderRadius: `${theme.radius.popover} 0 ${theme.radius.popover} ${theme.radius.popover}`,
color: theme.colors.popoverColor,
overflow: 'auto',
boxRadius: '10px',
@@ -71,6 +72,7 @@ export const CloseButton = styled('div')(({ theme }) => {
},
':hover': {
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 +0,0 @@
export * from './Tooltip';

View File

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