init: the first public commit for AFFiNE

This commit is contained in:
DarkSky
2022-07-22 15:49:21 +08:00
commit e3e3741393
1451 changed files with 108124 additions and 0 deletions
@@ -0,0 +1,92 @@
import React, { forwardRef } from 'react';
import { PopperArrowProps } from './interface';
import { styled } from '../styled';
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),
};
});
+186
View File
@@ -0,0 +1,186 @@
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import type { PropsWithChildren } from 'react';
import {
MuiPopper,
MuiClickAwayListener as ClickAwayListener,
MuiGrow as Grow,
} from '../mui';
import { styled } from '../styled';
import { PopperProps, PopperHandler, VirtualElement } from './interface';
import { useTheme } from '../theme';
import { PopperArrow } from './PopoverArrow';
export const Popper = forwardRef<PopperHandler, PropsWithChildren<PopperProps>>(
(
{
children,
content,
anchor: propsAnchor,
placement = 'top-start',
defaultVisible = false,
container,
keepMounted = false,
visible: propsVisible,
trigger = 'hover',
pointerEnterDelay = 100,
pointerLeaveDelay = 100,
onVisibleChange,
popoverStyle,
popoverClassName,
anchorStyle,
anchorClassName,
zIndex,
offset = [0, 5],
showArrow = false,
},
ref
) => {
const [anchorEl, setAnchorEl] = useState<VirtualElement>(null);
const [visible, setVisible] = useState(defaultVisible);
const [arrowRef, setArrowRef] = useState<HTMLElement>(null);
const pointerLeaveTimer = useRef<number>();
const pointerEnterTimer = useRef<number>();
const visibleControlledByParent = typeof propsVisible !== 'undefined';
const isAnchorCustom = typeof propsAnchor !== '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 theme = useTheme();
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(ref, () => {
return {
setVisible: (visible: boolean) => {
!visibleControlledByParent && setVisible(visible);
},
};
});
return (
<ClickAwayListener
onClickAway={() => {
setVisible(false);
}}
>
<Container>
{isAnchorCustom ? null : (
<div
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
onClick={() => {
if (
!hasClickTrigger ||
visibleControlledByParent
) {
return;
}
setVisible(!visible);
}}
onPointerEnter={onPointerEnterHandler}
onPointerLeave={onPointerLeaveHandler}
style={anchorStyle}
className={anchorClassName}
>
{children}
</div>
)}
<MuiPopper
open={
visibleControlledByParent ? propsVisible : visible
}
sx={{ zIndex: zIndex || theme.affine.zIndex.popover }}
anchorEl={isAnchorCustom ? propsAnchor : anchorEl}
placement={placement}
container={container}
keepMounted={keepMounted}
transition
modifiers={[
{
name: 'offset',
options: {
offset,
},
},
{
name: 'arrow',
enabled: showArrow,
options: {
element: arrowRef,
},
},
]}
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<div
onPointerEnter={onPointerEnterHandler}
onPointerLeave={onPointerLeaveHandler}
style={popoverStyle}
className={popoverClassName}
>
{showArrow && (
<PopperArrow
placement={placement}
ref={setArrowRef}
/>
)}
{content}
</div>
</Grow>
)}
</MuiPopper>
</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',
});
+2
View File
@@ -0,0 +1,2 @@
export * from './Popper';
export * from './interface';
@@ -0,0 +1,70 @@
import type { CSSProperties, ReactNode } from 'react';
import type { MuiPopperPlacementType as PopperPlacementType } from '../mui';
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;
// Position of Popover
placement?: PopperPlacementType;
// The popover will pop up based on the anchor position
// And if this parameter is passed, children will not be rendered
anchor?: VirtualElement | (() => VirtualElement);
// Whether the default is implicit
defaultVisible?: boolean;
// Used to manually control the visibility of the Popover
visible?: boolean;
// A HTML element or function that returns one. The container will have the portal children appended to it.
// By default, it uses the body of the top-level document object, so it's simply document.body most of the time.
container?: HTMLElement;
// Always keep the children in the DOM. This prop can be useful in SEO situation or when you want to maximize the responsiveness of the Popper
keepMounted?: 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;
};