From 73d7709e547715154510564fd888506ca1159d37 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Mon, 17 Oct 2022 18:01:09 +0800 Subject: [PATCH 1/4] feat: add tooltip & popover --- packages/app/package.json | 2 + .../src/components/popper/PopoverArrow.tsx | 93 ++++++++ packages/app/src/components/popper/Popper.tsx | 190 ++++++++++++++++ packages/app/src/components/popper/index.ts | 2 + .../app/src/components/popper/interface.ts | 66 ++++++ .../app/src/components/tooltip/Container.tsx | 36 +++ .../app/src/components/tooltip/Tooltip.tsx | 62 +++++ packages/app/src/components/tooltip/index.tsx | 1 + .../app/src/components/tooltip/interface.ts | 9 + pnpm-lock.yaml | 213 +++++++++++++++++- 10 files changed, 671 insertions(+), 3 deletions(-) create mode 100644 packages/app/src/components/popper/PopoverArrow.tsx create mode 100644 packages/app/src/components/popper/Popper.tsx create mode 100644 packages/app/src/components/popper/index.ts create mode 100644 packages/app/src/components/popper/interface.ts create mode 100644 packages/app/src/components/tooltip/Container.tsx create mode 100644 packages/app/src/components/tooltip/Tooltip.tsx create mode 100644 packages/app/src/components/tooltip/index.tsx create mode 100644 packages/app/src/components/tooltip/interface.ts diff --git a/packages/app/package.json b/packages/app/package.json index 0470ef952a..168fcfab55 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -17,6 +17,8 @@ "@emotion/react": "^11.10.4", "@emotion/server": "^11.10.0", "@emotion/styled": "^11.10.4", + "@mui/base": "^5.0.0-alpha.87", + "@mui/material": "^5.8.6", "css-spring": "^4.1.0", "lit": "^2.3.1", "next": "12.3.1", diff --git a/packages/app/src/components/popper/PopoverArrow.tsx b/packages/app/src/components/popper/PopoverArrow.tsx new file mode 100644 index 0000000000..4a99f50826 --- /dev/null +++ b/packages/app/src/components/popper/PopoverArrow.tsx @@ -0,0 +1,93 @@ +import { forwardRef } from 'react'; +import { styled } from '@/styles'; +import { PopperArrowProps } from './interface'; + +// eslint-disable-next-line react/display-name +export const PopperArrow = forwardRef( + ({ placement }, ref) => { + return ; + } +); + +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), + }; +}); diff --git a/packages/app/src/components/popper/Popper.tsx b/packages/app/src/components/popper/Popper.tsx new file mode 100644 index 0000000000..68ea64197b --- /dev/null +++ b/packages/app/src/components/popper/Popper.tsx @@ -0,0 +1,190 @@ +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(null); + const [visible, setVisible] = useState(defaultVisible); + // @ts-ignore + const [arrowRef, setArrowRef] = useState(null); + const popperRef = useRef(); + const pointerLeaveTimer = useRef(); + const pointerEnterTimer = useRef(); + + 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 + // @ts-ignore + return ( + { + if (visibleControlledByParent) { + onClickAway?.(); + } else { + setVisible(false); + } + }} + > + + {isAnchorCustom ? null : ( +
setAnchorEl(dom)} + onClick={e => { + if (!hasClickTrigger || visibleControlledByParent) { + onClick?.(e); + return; + } + setVisible(!visible); + }} + onPointerEnter={onPointerEnterHandler} + onPointerLeave={onPointerLeaveHandler} + style={anchorStyle} + className={anchorClassName} + > + {children} +
+ )} + + {({ TransitionProps }) => ( + +
+ {showArrow && ( + // @ts-ignore + + )} + {content} +
+
+ )} +
+
+
+ ); +}; + +// 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, + }; +}); diff --git a/packages/app/src/components/popper/index.ts b/packages/app/src/components/popper/index.ts new file mode 100644 index 0000000000..88fac67073 --- /dev/null +++ b/packages/app/src/components/popper/index.ts @@ -0,0 +1,2 @@ +export * from './interface'; +export * from './Popper'; diff --git a/packages/app/src/components/popper/interface.ts b/packages/app/src/components/popper/interface.ts new file mode 100644 index 0000000000..bbb676c4c5 --- /dev/null +++ b/packages/app/src/components/popper/interface.ts @@ -0,0 +1,66 @@ +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; + + onClickAway?: () => void; +} & Omit; diff --git a/packages/app/src/components/tooltip/Container.tsx b/packages/app/src/components/tooltip/Container.tsx new file mode 100644 index 0000000000..4c8615008c --- /dev/null +++ b/packages/app/src/components/tooltip/Container.tsx @@ -0,0 +1,36 @@ +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 = { + 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 +>(({ theme, direction, style }) => { + return ''; + // const shadow = theme.affine.shadows.shadow1; + // const white = theme.affine.palette.white; + // + // const borderRadius = + // border_radius_map[direction] || border_radius_map['left-top']; + // return { + // boxShadow: shadow, + // borderRadius: borderRadius, + // padding: '8px 4px', + // backgroundColor: white, + // ...style, + // }; +}); diff --git a/packages/app/src/components/tooltip/Tooltip.tsx b/packages/app/src/components/tooltip/Tooltip.tsx new file mode 100644 index 0000000000..0bbe560f9c --- /dev/null +++ b/packages/app/src/components/tooltip/Tooltip.tsx @@ -0,0 +1,62 @@ +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 = useTheme(); + return {}; + // return { + // backgroundColor: theme.affine.palette.icons, + // color: theme.affine.palette.white, + // ...theme.affine.typography.tooltip, + // padding: '4px 8px', + // }; +}; + +export const Tooltip = ( + props: PropsWithChildren> +) => { + const { content, placement = 'top-start' } = props; + const style = useTooltipStyle(); + // If there is no content, hide forever + const visibleProp = content ? {} : { visible: false }; + return ( + + {content} + + } + /> + ); +}; diff --git a/packages/app/src/components/tooltip/index.tsx b/packages/app/src/components/tooltip/index.tsx new file mode 100644 index 0000000000..7594a8f06c --- /dev/null +++ b/packages/app/src/components/tooltip/index.tsx @@ -0,0 +1 @@ +export * from './Tooltip'; diff --git a/packages/app/src/components/tooltip/interface.ts b/packages/app/src/components/tooltip/interface.ts new file mode 100644 index 0000000000..73e08c5ade --- /dev/null +++ b/packages/app/src/components/tooltip/interface.ts @@ -0,0 +1,9 @@ +export type TooltipProps = { + showArrow?: boolean; +}; +export type PopoverDirection = + | 'none' + | 'left-top' + | 'left-bottom' + | 'right-top' + | 'right-bottom'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fada691b7..f799352243 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,8 @@ importers: '@emotion/react': ^11.10.4 '@emotion/server': ^11.10.0 '@emotion/styled': ^11.10.4 + '@mui/base': ^5.0.0-alpha.87 + '@mui/material': ^5.8.6 '@types/node': 18.7.18 '@types/react': 18.0.20 '@types/react-dom': 18.0.6 @@ -49,6 +51,8 @@ importers: '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 '@emotion/server': 11.10.0_@emotion+css@11.10.0 '@emotion/styled': 11.10.4_yiaqs725o7pcd7rteavrnhgj4y + '@mui/base': 5.0.0-alpha.101_7ey2zzynotv32rpkwno45fsx4e + '@mui/material': 5.10.9_af5ln35zuaotaffazii6n6bke4 css-spring: 4.1.0 lit: 2.4.0 next: 12.3.1_biqbaboplfbrettd7655fr4n2y @@ -375,6 +379,164 @@ packages: resolution: {integrity: sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw==} dev: false + /@mui/base/5.0.0-alpha.101_7ey2zzynotv32rpkwno45fsx4e: + resolution: {integrity: sha512-a54BcXvArGOKUZ2zyS/7B9GNhAGgfomEQSkfEZ88Nc9jKvXA+Mppenfz5o4JCAnD8c4VlePmz9rKOYvvum1bZw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@emotion/is-prop-valid': 1.2.0 + '@mui/types': 7.2.0_@types+react@18.0.20 + '@mui/utils': 5.10.9_react@18.2.0 + '@popperjs/core': 2.11.6 + '@types/react': 18.0.20 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 18.2.0 + dev: false + + /@mui/core-downloads-tracker/5.10.9: + resolution: {integrity: sha512-rqoFu4qww6KJBbXYhyRd9YXjwBHa3ylnBPSWbGf1bdfG0AYMKmVzg8zxkWvxAWOp97kvx3M2kNPb0xMIDZiogQ==} + dev: false + + /@mui/material/5.10.9_af5ln35zuaotaffazii6n6bke4: + resolution: {integrity: sha512-sdOzlgpCmyw48je+E7o9UGGJpgBaF+60FlTRpVpcd/z+LUhnuzzuis891yPI5dPPXLBDL/bO4SsGg51lgNeLBw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 + '@emotion/styled': 11.10.4_yiaqs725o7pcd7rteavrnhgj4y + '@mui/base': 5.0.0-alpha.101_7ey2zzynotv32rpkwno45fsx4e + '@mui/core-downloads-tracker': 5.10.9 + '@mui/system': 5.10.9_4mv32nu4vciambuqqzuu4gtvj4 + '@mui/types': 7.2.0_@types+react@18.0.20 + '@mui/utils': 5.10.9_react@18.2.0 + '@types/react': 18.0.20 + '@types/react-transition-group': 4.4.5 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 18.2.0 + react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y + dev: false + + /@mui/private-theming/5.10.9_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-BN7/CnsVPVyBaQpDTij4uV2xGYHHHhOgpdxeYLlIu+TqnsVM7wUeF+37kXvHovxM6xmL5qoaVUD98gDC0IZnHg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@mui/utils': 5.10.9_react@18.2.0 + '@types/react': 18.0.20 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine/5.10.8_hfzxdiydbrbhhfpkwuv3jhvwmq: + resolution: {integrity: sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@emotion/cache': 11.10.3 + '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 + '@emotion/styled': 11.10.4_yiaqs725o7pcd7rteavrnhgj4y + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system/5.10.9_4mv32nu4vciambuqqzuu4gtvj4: + resolution: {integrity: sha512-B6fFC0sK06hNmqY7fAUfwShQv594+u/DT1YEFHPtK4laouTu7V4vSGQWi1WJT9Bjs9Db5D1bRDJ+Yy+tc3QOYA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.19.0 + '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 + '@emotion/styled': 11.10.4_yiaqs725o7pcd7rteavrnhgj4y + '@mui/private-theming': 5.10.9_w5j4k42lgipnm43s3brx6h3c34 + '@mui/styled-engine': 5.10.8_hfzxdiydbrbhhfpkwuv3jhvwmq + '@mui/types': 7.2.0_@types+react@18.0.20 + '@mui/utils': 5.10.9_react@18.2.0 + '@types/react': 18.0.20 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types/7.2.0_@types+react@18.0.20: + resolution: {integrity: sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==} + peerDependencies: + '@types/react': '*' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + dev: false + + /@mui/utils/5.10.9_react@18.2.0: + resolution: {integrity: sha512-2tdHWrq3+WCy+G6TIIaFx3cg7PorXZ71P375ExuX61od1NOAJP1mK90VxQ8N4aqnj2vmO3AQDkV4oV2Ktvt4bA==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.19.0 + '@types/prop-types': 15.7.5 + '@types/react-is': 17.0.3 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + /@next/env/12.3.1: resolution: {integrity: sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==} dev: false @@ -523,6 +685,10 @@ packages: fastq: 1.13.0 dev: true + /@popperjs/core/2.11.6: + resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + dev: false + /@rushstack/eslint-patch/1.2.0: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} dev: true @@ -554,6 +720,18 @@ packages: '@types/react': 18.0.20 dev: true + /@types/react-is/17.0.3: + resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} + dependencies: + '@types/react': 18.0.20 + dev: false + + /@types/react-transition-group/4.4.5: + resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} + dependencies: + '@types/react': 18.0.20 + dev: false + /@types/react/18.0.20: resolution: {integrity: sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==} dependencies: @@ -850,6 +1028,11 @@ packages: engines: {node: '>=0.8'} dev: false + /clsx/1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -997,6 +1180,13 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers/5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.19.0 + csstype: 3.1.1 + dev: false + /domino/2.1.6: resolution: {integrity: sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==} dev: false @@ -1180,7 +1370,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.38.0_76twfck5d7crjqrmw4yltga7zm + '@typescript-eslint/parser': 5.38.0_eslint@8.22.0 debug: 3.2.7 eslint: 8.22.0 eslint-import-resolver-node: 0.3.6 @@ -1199,7 +1389,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.38.0_76twfck5d7crjqrmw4yltga7zm + '@typescript-eslint/parser': 5.38.0_eslint@8.22.0 array-includes: 3.1.5 array.prototype.flat: 1.3.0 debug: 2.6.9 @@ -2204,7 +2394,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} @@ -2257,6 +2446,24 @@ packages: /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-is/18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false + + /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.19.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /react/18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} From 249b5ab0f1dc158cb57a7a9f34a5c9131e142b23 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Mon, 17 Oct 2022 18:54:14 +0800 Subject: [PATCH 2/4] feat: add modal --- packages/app/src/components/modal/index.tsx | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/app/src/components/modal/index.tsx diff --git a/packages/app/src/components/modal/index.tsx b/packages/app/src/components/modal/index.tsx new file mode 100644 index 0000000000..b734727f30 --- /dev/null +++ b/packages/app/src/components/modal/index.tsx @@ -0,0 +1,41 @@ +import ModalUnstyled from '@mui/base/ModalUnstyled'; +import { styled } from '@/styles'; +import Fade from '@mui/material/Fade'; + +import { ClickAwayListener } from '@mui/material'; + +const StyledModal = styled(ModalUnstyled)` + position: fixed; + z-index: 1300; + right: 0; + bottom: 0; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; +`; + +type TransitionsModalProps = { + open: boolean; + onClose: () => void; + children: JSX.Element; +}; + +export const Modal = (props: TransitionsModalProps) => { + return ( + + + + {props.children} + + + + ); +}; From d7c6f7e8d58202973c266307408a83f26171af42 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Tue, 18 Oct 2022 00:28:39 +0800 Subject: [PATCH 3/4] feat: modify style --- packages/app/src/components/Header/icons.tsx | 113 ++---------------- packages/app/src/components/Header/index.tsx | 90 +++++--------- packages/app/src/components/Header/styles.ts | 17 +++ .../src/components/animate-radio/icons.tsx | 38 ------ .../components/editor-mode-switch/icons.tsx | 85 +++++++++++++ .../index.tsx | 110 ++++++++--------- .../style.ts | 34 +++--- .../type.ts | 10 +- .../app/src/components/editor-provider.tsx | 12 +- packages/app/src/components/faq/icons.tsx | 45 +++++++ packages/app/src/components/faq/index.tsx | 59 +++++++++ packages/app/src/components/faq/style.ts | 54 +++++++++ packages/app/src/components/popover/index.tsx | 5 +- packages/app/src/components/popper/Popper.tsx | 1 - .../app/src/components/tooltip/Container.tsx | 21 ++-- .../app/src/components/tooltip/Tooltip.tsx | 16 +-- packages/app/src/pages/affine.tsx | 2 +- packages/app/src/pages/index.tsx | 3 +- packages/app/src/styles/theme.ts | 6 + packages/app/src/styles/types.ts | 5 + 20 files changed, 413 insertions(+), 313 deletions(-) delete mode 100644 packages/app/src/components/animate-radio/icons.tsx create mode 100644 packages/app/src/components/editor-mode-switch/icons.tsx rename packages/app/src/components/{animate-radio => editor-mode-switch}/index.tsx (54%) rename packages/app/src/components/{animate-radio => editor-mode-switch}/style.ts (82%) rename packages/app/src/components/{animate-radio => editor-mode-switch}/type.ts (58%) create mode 100644 packages/app/src/components/faq/icons.tsx create mode 100644 packages/app/src/components/faq/index.tsx create mode 100644 packages/app/src/components/faq/style.ts diff --git a/packages/app/src/components/Header/icons.tsx b/packages/app/src/components/Header/icons.tsx index b0f5374169..deea320cd8 100644 --- a/packages/app/src/components/Header/icons.tsx +++ b/packages/app/src/components/Header/icons.tsx @@ -1,24 +1,17 @@ import type { DOMAttributes, CSSProperties } from 'react'; - type IconProps = { - color?: string; style?: CSSProperties; } & DOMAttributes; - -export const LogoIcon = ({ - color, - style: propsStyle = {}, - ...props -}: IconProps) => { - const style = { fill: color, ...propsStyle }; +export const LogoIcon = ({ style = {}, ...props }: IconProps) => { return ( { - const style = { fill: color, ...propsStyle }; - +export const MoonIcon = ({ style = {}, ...props }: IconProps) => { return ( - - - - ); -}; - -export const MoonIcon = ({ - color, - style: propsStyle = {}, - ...props -}: IconProps) => { - const style = { fill: color, ...propsStyle }; - - return ( - { - const style = { fill: color, ...propsStyle }; +export const SunIcon = ({ style = {}, ...props }: IconProps) => { return ( - - - - - ); -}; - -export const SunIcon = ({ - color, - style: propsStyle = {}, - ...props -}: IconProps) => { - const style = { fill: color, ...propsStyle }; - - return ( - { - const style = { fill: color, ...propsStyle, transform: 'rotate(90deg)' }; - +export const MoreIcon = ({ style = {}, ...props }: IconProps) => { return ( @@ -162,26 +78,19 @@ export const MoreIcon = ({ ); }; -export const ExportIcon = ({ - color, - style: propsStyle = {}, - ...props -}: IconProps) => { - const style = { fill: color, ...propsStyle }; +export const ExportIcon = ({ style = {}, ...props }: IconProps) => { return ( ); diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx index a082915dd5..bf1fad41e2 100644 --- a/packages/app/src/components/Header/index.tsx +++ b/packages/app/src/components/Header/index.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from 'react'; import { LogoIcon, - PaperIcon, - EdgelessIcon, SunIcon, MoonIcon, MoreIcon, @@ -13,56 +11,14 @@ import { StyledTitle, StyledTitleWrapper, StyledLogo, - StyledModeSwitch, StyledHeaderRightSide, StyledMoreMenuItem, + IconButton, } from './styles'; import { Popover } from '@/components/popover'; import { useTheme } from '@/styles'; import { useEditor } from '@/components/editor-provider'; -import { AnimateRadio } from '@/components/animate-radio'; - -const PaperItem = ({ active }: { active?: boolean }) => { - const { - theme: { - colors: { highlight, disabled }, - }, - } = useTheme(); - - return ; -}; - -const EdgelessItem = ({ active }: { active?: boolean }) => { - const { - theme: { - colors: { highlight, disabled }, - }, - } = useTheme(); - - return ; -}; -const EditorModeSwitch = ({ isHover }: { isHover: boolean }) => { - const handleModeSwitch = (mode: 'page' | 'edgeless') => { - const event = new CustomEvent('affine.switch-mode', { detail: mode }); - window.dispatchEvent(event); - }; - return ( - } - labelRight="Edgeless" - iconRight={} - style={{ - marginRight: '12px', - }} - initialValue="left" - onChange={value => { - handleModeSwitch(value === 'left' ? 'page' : 'edgeless'); - }} - /> - ); -}; +import EditorModeSwitch from '@/components/editor-mode-switch'; const DarkModeSwitch = () => { const { changeMode, mode } = useTheme(); @@ -71,16 +27,14 @@ const DarkModeSwitch = () => { <> {mode === 'dark' ? ( { changeMode('light'); }} > ) : ( { changeMode('dark'); }} @@ -130,26 +84,36 @@ export const Header = () => { }, [editor]); return ( - { - setIsHover(true); - }} - onMouseLeave={() => { - setIsHover(false); - }} - > + - {}} /> + {}} /> - - + { + setIsHover(true); + }} + onMouseLeave={() => { + setIsHover(false); + }} + > + {title} - }> - + } + style={{ marginLeft: '20px' }} + > + + + diff --git a/packages/app/src/components/Header/styles.ts b/packages/app/src/components/Header/styles.ts index e2175e7aa7..e690dcbd09 100644 --- a/packages/app/src/components/Header/styles.ts +++ b/packages/app/src/components/Header/styles.ts @@ -71,3 +71,20 @@ export const StyledMoreMenuItem = styled('div')({ }, }, }); + +export const IconButton = styled('div')(({ theme }) => { + return { + width: '32px', + height: '32px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + color: theme.colors.disabled, + background: 'transparent', + borderRadius: '5px', + ':hover': { + color: theme.colors.highlight, + background: '#F1F3FF', + }, + }; +}); diff --git a/packages/app/src/components/animate-radio/icons.tsx b/packages/app/src/components/animate-radio/icons.tsx deleted file mode 100644 index cc93ced81f..0000000000 --- a/packages/app/src/components/animate-radio/icons.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { CSSProperties, DOMAttributes } from 'react'; - -type IconProps = { - color?: string; - style?: CSSProperties; -} & DOMAttributes; - -export const ArrowIcon = ({ - color, - style: propsStyle = {}, - direction = 'right', - ...props -}: IconProps & { direction?: 'left' | 'right' | 'middle' }) => { - const style = { - fill: color, - transform: `rotate(${direction === 'left' ? '0' : '180deg'})`, - opacity: direction === 'middle' ? 0 : 1, - ...propsStyle, - }; - return ( - - - - ); -}; diff --git a/packages/app/src/components/editor-mode-switch/icons.tsx b/packages/app/src/components/editor-mode-switch/icons.tsx new file mode 100644 index 0000000000..e59abf234f --- /dev/null +++ b/packages/app/src/components/editor-mode-switch/icons.tsx @@ -0,0 +1,85 @@ +import { CSSProperties, DOMAttributes } from 'react'; + +type IconProps = { + style?: CSSProperties; +} & DOMAttributes; + +export const ArrowIcon = ({ + style: propsStyle = {}, + direction = 'right', + ...props +}: IconProps & { direction?: 'left' | 'right' | 'middle' }) => { + const style = { + transform: `rotate(${direction === 'left' ? '0' : '180deg'})`, + opacity: direction === 'middle' ? 0 : 1, + ...propsStyle, + }; + return ( + + + + ); +}; + +export const PaperIcon = ({ style = {}, ...props }: IconProps) => { + return ( + + + + + + ); +}; + +export const EdgelessIcon = ({ style = {}, ...props }: IconProps) => { + return ( + + + + + ); +}; diff --git a/packages/app/src/components/animate-radio/index.tsx b/packages/app/src/components/editor-mode-switch/index.tsx similarity index 54% rename from packages/app/src/components/animate-radio/index.tsx rename to packages/app/src/components/editor-mode-switch/index.tsx index 4919581f80..9558abc756 100644 --- a/packages/app/src/components/animate-radio/index.tsx +++ b/packages/app/src/components/editor-mode-switch/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, cloneElement } from 'react'; +import React, { useState, useEffect, cloneElement } from 'react'; import { StyledAnimateRadioContainer, StyledRadioMiddle, @@ -7,12 +7,34 @@ import { StyledLabel, StyledIcon, } from './style'; -import { ArrowIcon } from './icons'; import type { RadioItemStatus, AnimateRadioProps, AnimateRadioItemProps, } from './type'; +import { useTheme } from '@/styles'; +import { EdgelessIcon, PaperIcon } from './icons'; +import { useEditor } from '@/components/editor-provider'; + +const PaperItem = ({ active }: { active?: boolean }) => { + const { + theme: { + colors: { highlight, disabled }, + }, + } = useTheme(); + + return ; +}; + +const EdgelessItem = ({ active }: { active?: boolean }) => { + const { + theme: { + colors: { highlight, disabled }, + }, + } = useTheme(); + + return ; +}; const AnimateRadioItem = ({ active, @@ -24,7 +46,7 @@ const AnimateRadioItem = ({ }: AnimateRadioItemProps) => { return ( - + {cloneElement(icon, { active, })} @@ -44,37 +66,32 @@ const RadioMiddle = ({ }) => { return ( ); }; -export const AnimateRadio = ({ - labelLeft, - labelRight, - iconLeft, - iconRight, +export const EditorModeSwitch = ({ isHover, style = {}, - onChange, - initialValue = 'left', }: AnimateRadioProps) => { - const [active, setActive] = useState(initialValue); + const { mode, setMode } = useEditor(); const modifyRadioItemStatus = (): RadioItemStatus => { return { - left: !isHover && active === 'right' ? 'shrink' : 'normal', - right: !isHover && active === 'left' ? 'shrink' : 'normal', + left: isHover + ? mode === 'page' + ? 'stretch' + : 'normal' + : mode === 'page' + ? 'shrink' + : 'hidden', + right: isHover + ? mode === 'edgeless' + ? 'stretch' + : 'normal' + : mode === 'edgeless' + ? 'shrink' + : 'hidden', }; }; const [radioItemStatus, setRadioItemStatus] = useState( @@ -84,19 +101,18 @@ export const AnimateRadio = ({ useEffect(() => { setRadioItemStatus(modifyRadioItemStatus()); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isHover, active]); + }, [isHover, mode]); return ( } + active={mode === 'page'} status={radioItemStatus.left} onClick={() => { - setActive('left'); - onChange?.('left'); + setMode('page'); }} onMouseEnter={() => { setRadioItemStatus({ @@ -105,31 +121,18 @@ export const AnimateRadio = ({ }); }} onMouseLeave={() => { - setRadioItemStatus({ - ...radioItemStatus, - left: 'normal', - }); + setRadioItemStatus(modifyRadioItemStatus()); }} /> - + } + active={mode === 'edgeless'} status={radioItemStatus.right} onClick={() => { - setActive('right'); - onChange?.('right'); + setMode('edgeless'); }} onMouseEnter={() => { setRadioItemStatus({ @@ -138,14 +141,11 @@ export const AnimateRadio = ({ }); }} onMouseLeave={() => { - setRadioItemStatus({ - ...radioItemStatus, - right: 'normal', - }); + setRadioItemStatus(modifyRadioItemStatus()); }} /> ); }; -export default AnimateRadio; +export default EditorModeSwitch; diff --git a/packages/app/src/components/animate-radio/style.ts b/packages/app/src/components/editor-mode-switch/style.ts similarity index 82% rename from packages/app/src/components/animate-radio/style.ts rename to packages/app/src/components/editor-mode-switch/style.ts index 7bcb2fb36e..aea2af5738 100644 --- a/packages/app/src/components/animate-radio/style.ts +++ b/packages/app/src/components/editor-mode-switch/style.ts @@ -8,11 +8,11 @@ const ANIMATE_DURATION = 300; export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>( ({ shrink }) => { const animateScaleStretch = keyframes`${toString( - spring({ width: '66px' }, { width: '132px' }, { preset: 'gentle' }) + spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) )}`; const animateScaleShrink = keyframes( `${toString( - spring({ width: '132px' }, { width: '66px' }, { preset: 'gentle' }) + spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) )}` ); const shrinkStyle = shrink @@ -40,7 +40,7 @@ export const StyledRadioMiddle = styled('div')<{ hidden: boolean; }>(({ hidden }) => { return { - width: '6px', + width: '1px', height: '100%', position: 'relative', opacity: hidden ? '0' : '1', @@ -53,9 +53,6 @@ export const StyledMiddleLine = styled('div')<{ hidden: boolean }>( width: '1px', height: '16px', background: '#D0D7E3', - position: 'absolute', - left: '0', - right: '0', top: '0', bottom: '0', margin: 'auto', @@ -69,13 +66,13 @@ export const StyledRadioItem = styled('div')<{ active: boolean; }>(({ status, active, theme }) => { const animateScaleStretch = keyframes`${toString( - spring({ width: '66px' }, { width: '116px' }) + spring({ width: '44px' }, { width: '112px' }) )}`; const animateScaleOrigin = keyframes( - `${toString(spring({ width: '116px' }, { width: '66px' }))}` + `${toString(spring({ width: '112px' }, { width: '44px' }))}` ); const animateScaleShrink = keyframes( - `${toString(spring({ width: '66px' }, { width: '0px' }))}` + `${toString(spring({ width: '0px' }, { width: '36px' }))}` ); const dynamicStyle = status === 'stretch' @@ -86,14 +83,16 @@ export const StyledRadioItem = styled('div')<{ : status === 'shrink' ? { animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`, - opacity: '0', } - : { animation: `${animateScaleOrigin} ${ANIMATE_DURATION}ms forwards` }; + : status === 'normal' + ? { animation: `${animateScaleOrigin} ${ANIMATE_DURATION}ms forwards` } + : {}; const { colors: { highlight, disabled }, } = theme; return { + width: '0', height: '100%', display: 'flex', cursor: 'pointer', @@ -135,19 +134,14 @@ export const StyledIcon = styled('div')<{ shrink: boolean; isLeft: boolean; }>(({ shrink, isLeft }) => { - const shrinkStyle = shrink - ? { - width: '24px', - margin: isLeft ? '0 12px' : '0 5px', - } - : { - width: '66px', - }; + const dynamicStyle = shrink + ? { width: '36px' } + : { width: isLeft ? '44px' : '34px' }; return { display: 'flex', justifyContent: 'center', alignItems: 'center', flexShrink: '0', - ...shrinkStyle, + ...dynamicStyle, }; }); diff --git a/packages/app/src/components/animate-radio/type.ts b/packages/app/src/components/editor-mode-switch/type.ts similarity index 58% rename from packages/app/src/components/animate-radio/type.ts rename to packages/app/src/components/editor-mode-switch/type.ts index 1a2f1277d0..d9bf0b5245 100644 --- a/packages/app/src/components/animate-radio/type.ts +++ b/packages/app/src/components/editor-mode-switch/type.ts @@ -1,20 +1,14 @@ import { CSSProperties, DOMAttributes, ReactElement } from 'react'; -export type ItemStatus = 'normal' | 'stretch' | 'shrink'; +export type ItemStatus = 'normal' | 'stretch' | 'shrink' | 'hidden'; export type RadioItemStatus = { left: ItemStatus; right: ItemStatus; }; export type AnimateRadioProps = { - labelLeft: string; - labelRight: string; - iconLeft: ReactElement; - iconRight: ReactElement; isHover: boolean; - initialValue?: 'left' | 'right'; - style?: CSSProperties; - onChange?: (value: 'left' | 'right') => void; + style: CSSProperties; }; export type AnimateRadioItemProps = { active: boolean; diff --git a/packages/app/src/components/editor-provider.tsx b/packages/app/src/components/editor-provider.tsx index de6d95d845..030b912d11 100644 --- a/packages/app/src/components/editor-provider.tsx +++ b/packages/app/src/components/editor-provider.tsx @@ -5,13 +5,17 @@ import type { PropsWithChildren } from 'react'; type EditorContextValue = { editor: EditorContainer | null; + mode: EditorContainer['mode']; setEditor: (editor: EditorContainer) => void; + setMode: (mode: EditorContainer['mode']) => void; }; type EditorContextProps = PropsWithChildren<{}>; export const EditorContext = createContext({ editor: null, + mode: 'page', setEditor: () => {}, + setMode: () => {}, }); export const useEditor = () => useContext(EditorContext); @@ -20,9 +24,15 @@ export const EditorProvider = ({ children, }: PropsWithChildren) => { const [editor, setEditor] = useState(null); + const [mode, setMode] = useState('page'); + + useEffect(() => { + const event = new CustomEvent('affine.switch-mode', { detail: mode }); + window.dispatchEvent(event); + }, [mode]); return ( - + {children} ); diff --git a/packages/app/src/components/faq/icons.tsx b/packages/app/src/components/faq/icons.tsx new file mode 100644 index 0000000000..9a4eba69ae --- /dev/null +++ b/packages/app/src/components/faq/icons.tsx @@ -0,0 +1,45 @@ +export const HelpIcon = () => { + return ( + + + + ); +}; + +export const ContactIcon = () => { + return ( + + + + ); +}; + +export const KeyboardIcon = () => { + return ( + + + + ); +}; diff --git a/packages/app/src/components/faq/index.tsx b/packages/app/src/components/faq/index.tsx new file mode 100644 index 0000000000..2eec3dbf3d --- /dev/null +++ b/packages/app/src/components/faq/index.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { StyledFAQ, StyledIconWrapper, StyledFAQWrapper } from './style'; +import { ContactIcon, HelpIcon, KeyboardIcon } from './icons'; +import Grow from '@mui/material/Grow'; +import { Tooltip } from '../tooltip'; +import { Modal } from '@/components/modal'; +const Contact = () => { + const [openModal, setOpenModal] = React.useState(false); + return ( + <> + { + setOpenModal(false); + }} + > +
modal content
+ + + { + setOpenModal(true); + }} + > + + + + + ); +}; +export const FAQ = () => { + const [showContent, setShowContent] = React.useState(false); + return ( + { + setShowContent(true); + }} + onMouseLeave={() => { + setShowContent(false); + }} + > + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/app/src/components/faq/style.ts b/packages/app/src/components/faq/style.ts new file mode 100644 index 0000000000..a5710e2fb9 --- /dev/null +++ b/packages/app/src/components/faq/style.ts @@ -0,0 +1,54 @@ +import { styled } from '@/styles'; + +export const StyledFAQ = styled('div')(({ theme }) => { + return { + width: '32px', + height: '32px', + backgroundColor: '#fff', + color: theme.colors.disabled, + position: 'fixed', + right: '30px', + bottom: '30px', + borderRadius: '50%', + zIndex: 1000, + ':hover': { + backgroundColor: '#F1F3FF', + color: theme.colors.highlight, + }, + }; +}); + +export const StyledIconWrapper = styled('div')(({ theme }) => { + return { + color: theme.colors.disabled, + marginBottom: '24px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer', + backgroundColor: 'transparent', + borderRadius: '50%', + width: '32px', + height: '32px', + ':hover': { + color: theme.colors.highlight, + backgroundColor: '#F1F3FF', + }, + }; +}); + +export const StyledFAQWrapper = styled('div')(({ theme }) => { + return { + position: 'absolute', + bottom: '100%', + left: '0', + width: '100%', + color: theme.colors.disabled, + ':hover': { + '> svg': { + color: theme.colors.highlight, + }, + color: theme.colors.highlight, + }, + }; +}); diff --git a/packages/app/src/components/popover/index.tsx b/packages/app/src/components/popover/index.tsx index 7541a4b4f1..3c43eb0d5a 100644 --- a/packages/app/src/components/popover/index.tsx +++ b/packages/app/src/components/popover/index.tsx @@ -1,9 +1,10 @@ import { useState } from 'react'; -import type { PropsWithChildren } from 'react'; +import type { CSSProperties, PropsWithChildren } from 'react'; import { styled } from '@/styles'; type PopoverProps = { popoverContent?: React.ReactNode; + style?: CSSProperties; }; const StyledPopoverContainer = styled('div')({ @@ -34,6 +35,7 @@ const StyledPopover = styled('div')<{ show: boolean }>(({ show }) => { export const Popover = ({ children, popoverContent, + style = {}, }: PropsWithChildren) => { const [show, setShow] = useState(false); return ( @@ -47,6 +49,7 @@ export const Popover = ({ onMouseLeave={() => { setShow(false); }} + style={style} > {children} diff --git a/packages/app/src/components/popper/Popper.tsx b/packages/app/src/components/popper/Popper.tsx index 68ea64197b..c889f80971 100644 --- a/packages/app/src/components/popper/Popper.tsx +++ b/packages/app/src/components/popper/Popper.tsx @@ -95,7 +95,6 @@ export const Popper = ({ }; }); - // @ts-ignore // @ts-ignore return ( = { export const PopoverContainer = styled('div')< Pick ->(({ theme, direction, style }) => { - return ''; - // const shadow = theme.affine.shadows.shadow1; - // const white = theme.affine.palette.white; - // - // const borderRadius = - // border_radius_map[direction] || border_radius_map['left-top']; - // return { - // boxShadow: shadow, - // borderRadius: borderRadius, - // padding: '8px 4px', - // backgroundColor: white, - // ...style, - // }; +>(({ direction, style }) => { + const borderRadius = + border_radius_map[direction] || border_radius_map['left-top']; + return { + borderRadius: borderRadius, + ...style, + }; }); diff --git a/packages/app/src/components/tooltip/Tooltip.tsx b/packages/app/src/components/tooltip/Tooltip.tsx index 0bbe560f9c..bea79371c7 100644 --- a/packages/app/src/components/tooltip/Tooltip.tsx +++ b/packages/app/src/components/tooltip/Tooltip.tsx @@ -26,14 +26,14 @@ export const placementToContainerDirection: Record< }; const useTooltipStyle = (): CSSProperties => { - const theme = useTheme(); - return {}; - // return { - // backgroundColor: theme.affine.palette.icons, - // color: theme.affine.palette.white, - // ...theme.affine.typography.tooltip, - // padding: '4px 8px', - // }; + const { theme } = useTheme(); + return { + boxShadow: '1px 1px 4px rgba(0, 0, 0, 0.14)', + padding: '4px 12px', + backgroundColor: theme.colors.highlight, + color: '#fff', + fontSize: theme.font.xs, + }; }; export const Tooltip = ( diff --git a/packages/app/src/pages/affine.tsx b/packages/app/src/pages/affine.tsx index 10a8cc78e2..d202ac30a4 100644 --- a/packages/app/src/pages/affine.tsx +++ b/packages/app/src/pages/affine.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import { useRef, useState, useEffect } from 'react'; import { styled } from '@/styles'; -import { PaperIcon, EdgelessIcon } from '../components/Header/icons'; +import { PaperIcon, EdgelessIcon } from '@/components/Header/icons'; export const StyledHeader = styled('div')({ height: '60px', width: '100vw', diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index 632b2ef9f5..aa8e9ae95b 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -2,7 +2,7 @@ import type { NextPage } from 'next'; import dynamic from 'next/dynamic'; import { styled, useTheme } from '@/styles'; import { Header } from '@/components/Header'; - +import { FAQ } from '@/components/faq'; import '@/components/simple-counter'; const StyledEditorContainer = styled('div')(({ theme }) => { @@ -56,6 +56,7 @@ const Home: NextPage = () => { {/*>*/} {/* auto*/} {/**/} + ); }; diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts index f419b8d646..5b00dd2583 100644 --- a/packages/app/src/styles/theme.ts +++ b/packages/app/src/styles/theme.ts @@ -8,9 +8,15 @@ export const lightTheme: AffineTheme = { disabled: '#9096A5', background: '#fff', }, + font: { + xs: '12px', + sm: '16px', + base: '18px', + }, }; export const darkTheme: AffineTheme = { + ...lightTheme, colors: { primary: '#fff', highlight: '#7389FD', diff --git a/packages/app/src/styles/types.ts b/packages/app/src/styles/types.ts index 81b9fa4cb4..7c042957c5 100644 --- a/packages/app/src/styles/types.ts +++ b/packages/app/src/styles/types.ts @@ -18,6 +18,11 @@ export interface AffineTheme { disabled: string; background: string; }; + font: { + xs: string; // tiny + sm: string; // small + base: string; + }; } declare module '@emotion/react' { From 02d85c893b20c5a499f9c16d70fcf6fc1dc898b3 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Tue, 18 Oct 2022 00:32:08 +0800 Subject: [PATCH 4/4] fix: lint error --- packages/app/src/components/Header/icons.tsx | 1 - packages/app/src/components/Header/index.tsx | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/app/src/components/Header/icons.tsx b/packages/app/src/components/Header/icons.tsx index deea320cd8..9e27fc7875 100644 --- a/packages/app/src/components/Header/icons.tsx +++ b/packages/app/src/components/Header/icons.tsx @@ -42,7 +42,6 @@ export const MoonIcon = ({ style = {}, ...props }: IconProps) => { ); }; - export const SunIcon = ({ style = {}, ...props }: IconProps) => { return (