diff --git a/apps/web/src/components/affine/sidebar-switch/icons.tsx b/apps/web/src/components/affine/sidebar-switch/icons.tsx new file mode 100644 index 0000000000..622e30d3de --- /dev/null +++ b/apps/web/src/components/affine/sidebar-switch/icons.tsx @@ -0,0 +1,26 @@ +export const SidebarSwitchIcon = () => { + return ( + + + + + ); +}; diff --git a/apps/web/src/components/affine/sidebar-switch/index.tsx b/apps/web/src/components/affine/sidebar-switch/index.tsx new file mode 100644 index 0000000000..4cdc7bb487 --- /dev/null +++ b/apps/web/src/components/affine/sidebar-switch/index.tsx @@ -0,0 +1,49 @@ +import { Tooltip } from '@affine/component'; +import { useTranslation } from '@affine/i18n'; +import React, { useCallback, useState } from 'react'; + +import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status'; +import { SidebarSwitchIcon } from './icons'; +import { StyledSidebarSwitch } from './style'; +type SidebarSwitchProps = { + visible?: boolean; + tooltipContent?: string; + testid?: string; +}; +export const SidebarSwitch = ({ + visible = true, + tooltipContent, + testid = '', +}: SidebarSwitchProps) => { + const [open, setOpen] = useSidebarStatus(); + const [tooltipVisible, setTooltipVisible] = useState(false); + const { t } = useTranslation(); + tooltipContent = + tooltipContent || (open ? t('Collapse sidebar') : t('Expand sidebar')); + + return ( + + { + setOpen(!open); + setTooltipVisible(false); + }, [open, setOpen])} + onMouseEnter={useCallback(() => { + setTooltipVisible(true); + }, [])} + onMouseLeave={useCallback(() => { + setTooltipVisible(false); + }, [])} + > + + + + ); +}; diff --git a/apps/web/src/components/affine/sidebar-switch/style.ts b/apps/web/src/components/affine/sidebar-switch/style.ts new file mode 100644 index 0000000000..b70d32390d --- /dev/null +++ b/apps/web/src/components/affine/sidebar-switch/style.ts @@ -0,0 +1,23 @@ +import { styled } from '@affine/component'; + +export const StyledSidebarSwitch = styled('button')<{ visible: boolean }>( + ({ theme, visible }) => { + return { + display: 'inline-flex', + justifyContent: 'center', + alignItems: 'center', + color: theme.colors.innerHoverBackground, + width: '32px', + height: '32px', + borderRadius: '8px', + opacity: visible ? 1 : 0, + transition: 'all 0.2s ease-in-out', + ...(visible ? {} : { cursor: 'not-allowed', pointerEvents: 'none' }), + + ':hover': { + background: '#F1F1F4', + color: theme.colors.iconColor, + }, + }; + } +); diff --git a/apps/web/src/components/blocksuite/header/editor-mode-switch/Icons.tsx b/apps/web/src/components/blocksuite/header/editor-mode-switch/Icons.tsx deleted file mode 100644 index e59abf234f..0000000000 --- a/apps/web/src/components/blocksuite/header/editor-mode-switch/Icons.tsx +++ /dev/null @@ -1,85 +0,0 @@ -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/apps/web/src/components/blocksuite/header/editor-mode-switch/index.tsx b/apps/web/src/components/blocksuite/header/editor-mode-switch/index.tsx index 7697c80e83..ad16c913b0 100644 --- a/apps/web/src/components/blocksuite/header/editor-mode-switch/index.tsx +++ b/apps/web/src/components/blocksuite/header/editor-mode-switch/index.tsx @@ -1,80 +1,26 @@ -import { useTranslation } from '@affine/i18n'; +import { EdgelessIcon, PaperIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; -import { useTheme } from '@mui/material'; -import React, { cloneElement, CSSProperties, useEffect, useState } from 'react'; +import { CSSProperties } from 'react'; import { usePageMeta, usePageMetaHelper, } from '../../../../hooks/use-page-meta'; -// todo(himself65): remove `useTheme` hook import { BlockSuiteWorkspace } from '../../../../shared'; -import { EdgelessIcon, PaperIcon } from './Icons'; -import { - StyledAnimateRadioContainer, - StyledIcon, - StyledLabel, - StyledMiddleLine, - StyledRadioItem, -} from './style'; -import type { AnimateRadioItemProps, RadioItemStatus } from './type'; -const PaperItem = ({ active }: { active?: boolean }) => { - const { - colors: { iconColor, primaryColor }, - } = useTheme(); - - return ; -}; - -const EdgelessItem = ({ active }: { active?: boolean }) => { - const { - colors: { iconColor, primaryColor }, - } = useTheme(); - - return ; -}; - -const AnimateRadioItem = ({ - active, - status, - icon: propsIcon, - label, - isLeft, - ...props -}: AnimateRadioItemProps) => { - const icon = ( - - {cloneElement(propsIcon, { - active, - })} - - ); - return ( - - {isLeft ? icon : null} - - {label} - - {isLeft ? null : icon} - - ); -}; +import { StyledEditorModeSwitch, StyledSwitchItem } from './style'; export type EditorModeSwitchProps = { // todo(himself65): combine these two properties blockSuiteWorkspace: BlockSuiteWorkspace; pageId: string; - isHover: boolean; - style: CSSProperties; + style?: CSSProperties; }; -export const EditorModeSwitch: React.FC = ({ - isHover, - style = {}, +export const EditorModeSwitch = ({ + style, blockSuiteWorkspace, pageId, -}) => { - const theme = useTheme(); +}: EditorModeSwitchProps) => { const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); const pageMeta = usePageMeta(blockSuiteWorkspace).find( meta => meta.id === pageId @@ -82,85 +28,32 @@ export const EditorModeSwitch: React.FC = ({ assertExists(pageMeta); const { trash, mode = 'page' } = pageMeta; - const modifyRadioItemStatus = (): RadioItemStatus => { - return { - left: isHover - ? mode === 'page' - ? 'stretch' - : 'normal' - : mode === 'page' - ? 'shrink' - : 'hidden', - right: isHover - ? mode === 'edgeless' - ? 'stretch' - : 'normal' - : mode === 'edgeless' - ? 'shrink' - : 'hidden', - }; - }; - const [radioItemStatus, setRadioItemStatus] = useState( - modifyRadioItemStatus - ); - - useEffect(() => { - setRadioItemStatus(modifyRadioItemStatus()); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isHover, mode]); - const { t } = useTranslation(); return ( - - } + { setPageMeta(pageId, { mode: 'page' }); }} - onMouseEnter={() => { - setRadioItemStatus({ - right: 'normal', - left: 'stretch', - }); - }} - onMouseLeave={() => { - setRadioItemStatus(modifyRadioItemStatus()); - }} - /> - + { setPageMeta(pageId, { mode: 'edgeless' }); }} - onMouseEnter={() => { - setRadioItemStatus({ - left: 'normal', - right: 'stretch', - }); - }} - onMouseLeave={() => { - setRadioItemStatus(modifyRadioItemStatus()); - }} - /> - + > + + + ); }; - -export default EditorModeSwitch; diff --git a/apps/web/src/components/blocksuite/header/editor-mode-switch/style.ts b/apps/web/src/components/blocksuite/header/editor-mode-switch/style.ts index 4067a4558b..b3682dbcbc 100644 --- a/apps/web/src/components/blocksuite/header/editor-mode-switch/style.ts +++ b/apps/web/src/components/blocksuite/header/editor-mode-switch/style.ts @@ -1,179 +1,58 @@ -import { css, displayFlex, keyframes, styled } from '@affine/component'; -// @ts-ignore -import spring, { toString } from 'css-spring'; - -// @ts-ignore -import type { ItemStatus } from './type'; - -const ANIMATE_DURATION = 500; - -export const StyledAnimateRadioContainer = styled('div')<{ - shrink: boolean; - disabled: boolean; -}>(({ shrink, theme, disabled }) => { - const animateScaleStretch = toString( - spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) - ); - const animateScaleShrink = toString( - spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) - ); - const shrinkStyle: any = shrink - ? { - animation: css` - ${keyframes`${animateScaleShrink}`} ${ANIMATE_DURATION}ms forwards - `, - background: 'transparent', - } - : { - animation: css` - ${keyframes`${animateScaleStretch}`} ${ANIMATE_DURATION}ms forwards - `, - }; - return css` - height: 36px; - border-radius: 18px; - background: ${disabled ? 'transparent' : theme.colors.hoverBackground} - position: relative; - display: flex; - transition: background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms; - border: 1px solid transparent; - ${ - disabled - ? css` - pointer-events: none; - ` - : css` - animation: ${shrinkStyle.animation}; - background: ${shrinkStyle.background}; - ` - } - - //...(disabled ? { pointerEvents: 'none' } : shrinkStyle), - :hover { - border: ${disabled ? '' : `1px solid ${theme.colors.primaryColor}`} - } - `; -}); - -export const StyledMiddleLine = styled('div')<{ - hidden: boolean; - dark: boolean; -}>(({ hidden, dark }) => { - return { - width: '1px', - height: '16px', - background: dark ? '#4d4c53' : '#D0D7E3', - top: '0', - bottom: '0', - margin: 'auto', - opacity: hidden ? '0' : '1', - }; -}); - -export const StyledRadioItem = styled('div')<{ - status: ItemStatus; - active: boolean; -}>(({ status, active, theme }) => { - const animateScaleStretch = toString( - spring({ width: '44px' }, { width: '112px' }) - ); - const animateScaleOrigin = toString( - spring({ width: '112px' }, { width: '44px' }) - ); - const animateScaleShrink = toString( - spring({ width: '0px' }, { width: '36px' }) - ); - const dynamicStyle = - status === 'stretch' - ? { - animation: css` - ${keyframes`${animateScaleStretch}`} ${ANIMATE_DURATION}ms forwards - `, - flexShrink: '0', - } - : status === 'shrink' - ? { - animation: css` - ${keyframes`${animateScaleShrink}`} ${ANIMATE_DURATION}ms forwards - `, - } - : status === 'normal' - ? { - animation: css` - ${keyframes`${animateScaleOrigin}`} ${ANIMATE_DURATION}ms forwards - `, - } - : {}; +import { displayFlex, styled } from '@affine/component'; +export const StyledEditorModeSwitch = styled('div')<{ + switchLeft: boolean; + showAlone?: boolean; +}>(({ theme, switchLeft, showAlone }) => { const { - colors: { iconColor, primaryColor }, + palette: { mode }, } = theme; - return css` - width: 0; - height: 100%; - display: flex; - cursor: pointer; - overflow: hidden; - color: ${active ? primaryColor : iconColor}; - animation: ${dynamicStyle.animation}; - flex-shrink: ${dynamicStyle.flexShrink}; - `; -}); - -export const StyledLabel = styled('div')<{ - shrink: boolean; - isLeft: boolean; -}>(({ shrink, isLeft }) => { - const animateScaleStretch = toString( - spring( - { width: '0px' }, - { width: isLeft ? '65px' : '75px' }, - { preset: 'gentle' } - ) - ); - const animateScaleShrink = toString( - spring( - { width: isLeft ? '65px' : '75px' }, - { width: '0px' }, - { preset: 'gentle' } - ) - ); - const shrinkStyle = shrink - ? { - animation: css` - ${keyframes`${animateScaleShrink}`} ${ANIMATE_DURATION}ms forwards - `, - } - : { - animation: css` - ${keyframes`${animateScaleStretch}`} ${ANIMATE_DURATION}ms forwards - `, - }; - - return css` - display: flex; - align-items: center; - justify-content: ${isLeft ? 'flex-start' : 'flex-end'}; - font-size: 16px; - flex-shrink: 0; - transition: transform ${ANIMATE_DURATION}ms; - font-weight: normal; - overflow: hidden; - white-space: nowrap; - animation: ${shrinkStyle.animation}; - `; -}); - -export const StyledIcon = styled('div')<{ - shrink: boolean; - isLeft: boolean; -}>(({ shrink, isLeft }) => { - const dynamicStyle = shrink - ? { width: '36px' } - : { width: isLeft ? '44px' : '34px' }; return { - ...displayFlex('center', 'center'), - flexShrink: '0', - ...dynamicStyle, + width: showAlone ? '40px' : '78px', + height: '32px', + background: showAlone + ? 'transparent' + : mode === 'dark' + ? '#242424' + : '#F9F9FB', + borderRadius: '12px', + ...displayFlex('space-between', 'center'), + padding: '0 8px', + position: 'relative', + + '::after': { + content: '""', + display: showAlone ? 'none' : 'block', + width: '24px', + height: '24px', + background: theme.colors.pageBackground, + boxShadow: + mode === 'dark' + ? '0px 0px 6px rgba(22, 22, 22, 0.6)' + : '0px 0px 6px #E2E2E2', + borderRadius: '8px', + zIndex: 1, + position: 'absolute', + transform: `translateX(${switchLeft ? '0' : '38px'})`, + transition: 'all .15s', + }, + }; +}); + +export const StyledSwitchItem = styled('button')<{ + active: boolean; + hide?: boolean; +}>(({ theme, active, hide = false }) => { + return { + width: '24px', + height: '24px', + borderRadius: '8px', + color: active ? theme.colors.primaryColor : theme.colors.iconColor, + display: hide ? 'none' : 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + zIndex: 2, + fontSize: '20px', }; }); diff --git a/apps/web/src/components/blocksuite/header/editor-mode-switch/type.ts b/apps/web/src/components/blocksuite/header/editor-mode-switch/type.ts deleted file mode 100644 index 0909c6b1ba..0000000000 --- a/apps/web/src/components/blocksuite/header/editor-mode-switch/type.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { DOMAttributes, ReactElement } from 'react'; - -export type ItemStatus = 'normal' | 'stretch' | 'shrink' | 'hidden'; - -export type RadioItemStatus = { - left: ItemStatus; - right: ItemStatus; -}; -export type AnimateRadioItemProps = { - active: boolean; - status: ItemStatus; - label: string; - icon: ReactElement; - isLeft: boolean; -} & DOMAttributes; diff --git a/apps/web/src/components/blocksuite/header/header-right-items/EditorOptionMenu.tsx b/apps/web/src/components/blocksuite/header/header-right-items/EditorOptionMenu.tsx index 052e13568a..833823a697 100644 --- a/apps/web/src/components/blocksuite/header/header-right-items/EditorOptionMenu.tsx +++ b/apps/web/src/components/blocksuite/header/header-right-items/EditorOptionMenu.tsx @@ -12,6 +12,7 @@ import { FavoriteIcon, MoreVerticalIcon, } from '@blocksuite/icons'; +import { EdgelessIcon, PaperIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; import { useTheme } from '@mui/material'; import { useState } from 'react'; @@ -22,7 +23,6 @@ import { usePageMeta, usePageMetaHelper, } from '../../../../hooks/use-page-meta'; -import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/Icons'; export const EditorOptionMenu = () => { const { t } = useTranslation(); @@ -52,6 +52,7 @@ export const EditorOptionMenu = () => { favorite ? t('Removed from Favorites') : t('Added to Favorites') ); }} + iconSize={[20, 20]} icon={ favorite ? ( @@ -64,6 +65,7 @@ export const EditorOptionMenu = () => { : } + iconSize={[20, 20]} data-testid="editor-option-menu-edgeless" onClick={() => { setPageMeta(pageId, { @@ -84,6 +86,7 @@ export const EditorOptionMenu = () => { globalThis.editor.contentParser.onExportHtml(); }} icon={} + iconSize={[20, 20]} > {t('Export to HTML')} @@ -93,13 +96,14 @@ export const EditorOptionMenu = () => { globalThis.editor.contentParser.onExportMarkdown(); }} icon={} + iconSize={[20, 20]} > {t('Export to Markdown')} } > - } isDir={true}> + } iconSize={[20, 20]} isDir={true}> {t('Export')} @@ -109,6 +113,7 @@ export const EditorOptionMenu = () => { setOpen(true); }} icon={} + iconSize={[20, 20]} > {t('Delete')} @@ -124,7 +129,7 @@ export const EditorOptionMenu = () => { disablePortal={true} trigger="click" > - + diff --git a/apps/web/src/components/blocksuite/header/header-right-items/SyncUser.tsx b/apps/web/src/components/blocksuite/header/header-right-items/SyncUser.tsx index 04e9dd6552..aaec733412 100644 --- a/apps/web/src/components/blocksuite/header/header-right-items/SyncUser.tsx +++ b/apps/web/src/components/blocksuite/header/header-right-items/SyncUser.tsx @@ -24,7 +24,7 @@ const IconWrapper = styled('div')(({ theme }) => { width: '32px', height: '32px', marginRight: '12px', - fontSize: '20px', + fontSize: '24px', color: theme.colors.iconColor, ...displayFlex('center', 'center'), }; @@ -102,7 +102,6 @@ export const SyncUser = () => { setOpen(true); }} style={{ marginRight: '12px' }} - iconSize={[20, 20]} > diff --git a/apps/web/src/components/blocksuite/header/header-right-items/theme-mode-switch/style.ts b/apps/web/src/components/blocksuite/header/header-right-items/theme-mode-switch/style.ts index 70119c010f..35253beb72 100644 --- a/apps/web/src/components/blocksuite/header/header-right-items/theme-mode-switch/style.ts +++ b/apps/web/src/components/blocksuite/header/header-right-items/theme-mode-switch/style.ts @@ -4,13 +4,17 @@ import spring, { toString } from 'css-spring'; const ANIMATE_DURATION = 400; -export const StyledThemeModeSwitch = styled('div')({ - width: '32px', - height: '32px', - borderRadius: '6px', - overflow: 'hidden', - backgroundColor: 'transparent', - position: 'relative', +export const StyledThemeModeSwitch = styled('div')(({ theme }) => { + return { + width: '32px', + height: '32px', + borderRadius: '6px', + overflow: 'hidden', + backgroundColor: 'transparent', + position: 'relative', + color: theme.colors.iconColor, + fontSize: '24px', + }; }); export const StyledSwitchItem = styled('div')<{ active: boolean; @@ -63,7 +67,6 @@ export const StyledSwitchItem = styled('div')<{ background-color: ${activeStyle.backgroundColor}; animation: ${activeStyle.animation}; animation-direction: ${activeStyle.animationDirection}; - font-size: 20px; //svg { // width: 24px; // height: 24px; diff --git a/apps/web/src/components/blocksuite/header/header.tsx b/apps/web/src/components/blocksuite/header/header.tsx index f83fad9680..97f66c8538 100644 --- a/apps/web/src/components/blocksuite/header/header.tsx +++ b/apps/web/src/components/blocksuite/header/header.tsx @@ -1,6 +1,9 @@ +import { useTranslation } from '@affine/i18n'; import { CloseIcon } from '@blocksuite/icons'; import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react'; +import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status'; +import { SidebarSwitch } from '../../affine/sidebar-switch'; import { EditorOptionMenu } from './header-right-items/EditorOptionMenu'; import SyncUser from './header-right-items/SyncUser'; import ThemeModeSwitch from './header-right-items/theme-mode-switch'; @@ -56,6 +59,9 @@ export const Header: React.FC = ({ useEffect(() => { setShowWarning(shouldShowWarning()); }, []); + const [open] = useSidebarStatus(); + const { t } = useTranslation(); + return ( = ({ data-testid="editor-header-items" data-tauri-drag-region > + + {children} {useMemo( diff --git a/apps/web/src/components/blocksuite/header/index.tsx b/apps/web/src/components/blocksuite/header/index.tsx index 0ee4f3806d..cbe699926f 100644 --- a/apps/web/src/components/blocksuite/header/index.tsx +++ b/apps/web/src/components/blocksuite/header/index.tsx @@ -1,7 +1,7 @@ import { Content } from '@affine/component'; import { assertExists } from '@blocksuite/store'; import { useSetAtom } from 'jotai'; -import React, { useState } from 'react'; +import React from 'react'; import { openQuickSearchModalAtom } from '../../../atoms'; import { usePageMeta } from '../../../hooks/use-page-meta'; @@ -42,7 +42,6 @@ export const BlockSuiteEditorHeader: React.FC = ({ ); assertExists(pageMeta); const title = pageMeta.title; - const [isHover, setIsHover] = useState(false); const { trash: isTrash } = pageMeta; return (
= ({ > {children} {title && !isPublic && ( - { - if (isTrash) return; - - setIsHover(true); - }} - onMouseLeave={() => { - if (isTrash) return; - - setIsHover(false); - }} - > + ( export const StyledHeader = styled('div')<{ hasWarning: boolean }>( ({ theme }) => { return { - height: '60px', + height: '64px', width: '100%', padding: '0 28px', ...displayFlex('flex-end', 'center'), diff --git a/apps/web/src/components/pure/workspace-avatar/index.tsx b/apps/web/src/components/pure/workspace-avatar/index.tsx index bb0e38e41b..c2ef888598 100644 --- a/apps/web/src/components/pure/workspace-avatar/index.tsx +++ b/apps/web/src/components/pure/workspace-avatar/index.tsx @@ -54,10 +54,10 @@ export const Avatar: React.FC = React.memo( fontSize: Math.ceil(0.5 * size) + 'px', background: stringToColour(name || 'AFFiNE'), borderRadius: '50%', - textAlign: 'center', - lineHeight: size + 'px', - display: 'inline-block', - verticalAlign: 'middle', + display: 'inline-flex', + lineHeight: '1', + justifyContent: 'center', + alignItems: 'center', }} > {(name || 'AFFiNE').substring(0, 1)} diff --git a/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts b/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts index 5ec6881e2a..8faff4070d 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts +++ b/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts @@ -2,12 +2,13 @@ import { MuiAvatar, textEllipsis } from '@affine/component'; import { styled } from '@affine/component'; export const SelectorWrapper = styled('div')(({ theme }) => { return { - height: '52px', + height: '64px', display: 'flex', alignItems: 'center', - padding: '0 12px', + padding: '0 44px 0 12px', borderRadius: '5px', color: theme.colors.textColor, + position: 'relative', ':hover': { cursor: 'pointer', background: theme.colors.hoverBackground, @@ -25,7 +26,6 @@ export const WorkspaceName = styled('span')(({ theme }) => { marginLeft: '12px', fontSize: theme.font.h6, fontWeight: 500, - marginTop: '4px', flexGrow: 1, ...textEllipsis(1), }; diff --git a/apps/web/src/components/pure/workspace-slider-bar/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/index.tsx index ffa0afdc49..85f672a9ad 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/index.tsx @@ -1,5 +1,4 @@ import { MuiCollapse } from '@affine/component'; -import { Tooltip } from '@affine/component'; import { IconButton } from '@affine/component'; import { useTranslation } from '@affine/i18n'; import { @@ -16,14 +15,15 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useCallback, useMemo, useState } from 'react'; +import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status'; import { usePageMeta } from '../../../hooks/use-page-meta'; import { RemWorkspace } from '../../../shared'; -import { Arrow } from './icons'; +import { SidebarSwitch } from '../../affine/sidebar-switch'; import { - StyledArrowButton, StyledLink, StyledListItem, StyledNewPageButton, + StyledSidebarWrapper, StyledSliderBar, StyledSliderBarWrapper, StyledSubListItem, @@ -83,8 +83,6 @@ export type WorkSpaceSliderBarProps = { currentPageId: string | null; openPage: (pageId: string) => void; createPage: () => Promise; - show: boolean; - setShow: (show: boolean) => void; currentPath: string; paths: { all: (workspaceId: string) => string; @@ -100,17 +98,17 @@ export const WorkSpaceSliderBar: React.FC = ({ currentPageId, openPage, createPage, - show, - setShow, currentPath, paths, onOpenQuickSearchModal, onOpenWorkspaceListModal, }) => { const currentWorkspaceId = currentWorkspace?.id || null; - const [showSubFavorite, setShowSubFavorite] = useState(true); - const [showTip, setShowTip] = useState(false); + const [showSubFavorite, setOpenSubFavorite] = useState(true); const { t } = useTranslation(); + const [open] = useSidebarStatus(); + + const [sidebarOpen] = useSidebarStatus(); const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null); const onClickNewPage = useCallback(async () => { const pageId = await createPage(); @@ -120,33 +118,14 @@ export const WorkSpaceSliderBar: React.FC = ({ }, [createPage, openPage]); return ( <> - - - { - setShow(!show); - setShowTip(false); - }, [setShow, show])} - onMouseEnter={useCallback(() => { - setShowTip(true); - }, [])} - onMouseLeave={useCallback(() => { - setShowTip(false); - }, [])} - > - - - - + + + + = ({ { - setShowSubFavorite(!showSubFavorite); + setOpenSubFavorite(!showSubFavorite); }, [showSubFavorite])} > = ({ {/* { - setShowWorkspaceSetting(false); + setOpenWorkspaceSetting(false); }} /> */} {/* TODO: will finish the feature next version */} diff --git a/apps/web/src/components/pure/workspace-slider-bar/style.ts b/apps/web/src/components/pure/workspace-slider-bar/style.ts index 4729f042a0..e962d08605 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/style.ts +++ b/apps/web/src/components/pure/workspace-slider-bar/style.ts @@ -12,11 +12,19 @@ export const StyledSliderBar = styled('div')<{ show: boolean }>( transition: 'width .15s, padding .15s', position: 'relative', zIndex: theme.zIndex.modal, - padding: show ? '24px 12px' : '24px 0', + padding: show ? '0 12px' : '0', flexShrink: 0, }; } ); +export const StyledSidebarWrapper = styled('div')(() => { + return { + position: 'absolute', + right: '12px', + top: '16px', + zIndex: 1, + }; +}); export const StyledSliderBarWrapper = styled('div')(() => { return { height: '100%', @@ -26,31 +34,6 @@ export const StyledSliderBarWrapper = styled('div')(() => { }; }); -export const StyledArrowButton = styled('button')<{ isShow: boolean }>( - ({ theme, isShow }) => { - return { - width: '32px', - height: '32px', - ...displayFlex('center', 'center'), - color: theme.colors.primaryColor, - backgroundColor: theme.colors.hoverBackground, - borderRadius: '50%', - transition: 'all .15s', - position: 'absolute', - top: '34px', - right: '-20px', - zIndex: theme.zIndex.modal, - svg: { - transform: isShow ? 'rotate(180deg)' : 'unset', - }, - ':hover': { - color: '#fff', - backgroundColor: theme.colors.primaryColor, - }, - }; - } -); - export const StyledListItem = styled('div')<{ active?: boolean; disabled?: boolean; diff --git a/apps/web/src/hooks/affine/use-sidebar-status.ts b/apps/web/src/hooks/affine/use-sidebar-status.ts new file mode 100644 index 0000000000..f2dd9d050f --- /dev/null +++ b/apps/web/src/hooks/affine/use-sidebar-status.ts @@ -0,0 +1,8 @@ +import { useAtom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; + +const sideBarOpenAtom = atomWithStorage('sidebarOpen', true); + +export function useSidebarStatus() { + return useAtom(sideBarOpenAtom); +} diff --git a/apps/web/src/layouts/index.tsx b/apps/web/src/layouts/index.tsx index 2648ce1f1b..fdf65b900a 100644 --- a/apps/web/src/layouts/index.tsx +++ b/apps/web/src/layouts/index.tsx @@ -3,7 +3,6 @@ import { setUpLanguage, useTranslation } from '@affine/i18n'; import { assertExists, nanoid } from '@blocksuite/store'; import { NoSsr } from '@mui/material'; import { useAtom, useAtomValue, useSetAtom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; import dynamic from 'next/dynamic'; import Head from 'next/head'; import { useRouter } from 'next/router'; @@ -35,8 +34,6 @@ const QuickSearchModal = dynamic( () => import('../components/pure/quick-search-modal') ); -const sideBarOpenAtom = atomWithStorage('sideBarOpen', true); - const logger = new DebugLogger('workspace-layout'); export const WorkspaceLayout: React.FC = function WorkspacesSuspense({ children }) { @@ -91,7 +88,6 @@ export const WorkspaceLayout: React.FC = export const WorkspaceLayoutInner: React.FC = ({ children, }) => { - const [show, setShow] = useAtom(sideBarOpenAtom); const [currentWorkspace] = useCurrentWorkspace(); const [currentPageId] = useCurrentPageId(); const workspaces = useWorkspaces(); @@ -183,8 +179,6 @@ export const WorkspaceLayoutInner: React.FC = ({ onOpenWorkspaceListModal={handleOpenWorkspaceListModal} openPage={handleOpenPage} createPage={handleCreatePage} - show={show} - setShow={setShow} currentPath={router.asPath} paths={isPublicWorkspace ? publicPathGenerator : pathGenerator} /> diff --git a/packages/component/src/ui/menu/MenuItem.tsx b/packages/component/src/ui/menu/MenuItem.tsx index 746a3331e1..97f53f30ad 100644 --- a/packages/component/src/ui/menu/MenuItem.tsx +++ b/packages/component/src/ui/menu/MenuItem.tsx @@ -10,17 +10,19 @@ import { StyledArrow, StyledMenuItem } from './styles'; export type IconMenuProps = PropsWithChildren<{ isDir?: boolean; icon?: ReactElement; + iconSize?: [number, number]; }> & HTMLAttributes; export const MenuItem = forwardRef( - ({ isDir = false, icon, children, ...props }, ref) => { + ({ isDir = false, icon, iconSize, children, ...props }, ref) => { + const [iconWidth, iconHeight] = iconSize || [16, 16]; return ( {icon && cloneElement(icon, { - width: 16, - height: 16, + width: iconWidth, + height: iconHeight, style: { marginRight: 14, ...icon.props?.style, diff --git a/tests/change-page-mode.spec.ts b/tests/change-page-mode.spec.ts index 2cfdb33a17..1a1295390b 100644 --- a/tests/change-page-mode.spec.ts +++ b/tests/change-page-mode.spec.ts @@ -7,31 +7,8 @@ loadPage(); test.describe('Change page mode(Page or Edgeless)', () => { test('Switch to edgeless by switch edgeless item', async ({ page }) => { - const switcher = page.locator('[data-testid=editor-mode-switcher]'); - const box = await switcher.boundingBox(); - expect(box?.x).not.toBeUndefined(); - - // mouse hover trigger animation for showing full switcher - // await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); - await page.mouse.move((box?.x ?? 0) + 10, (box?.y ?? 0) + 10); - - // await page.waitForTimeout(1000); - const edgelessButton = page.getByTestId('switch-edgeless-item'); // page.getByText('Edgeless').click() - await edgelessButton.click(); - - // // mouse move to edgeless button - // await page.mouse.move( - // (box?.x ?? 0) + (box?.width ?? 0) - 5, - // (box?.y ?? 0) + 5 - // ); - - // await page.waitForTimeout(1000); - - // // click switcher - // await page.mouse.click( - // (box?.x ?? 0) + (box?.width ?? 0) - 5, - // (box?.y ?? 0) + 5 - // ); + const btn = await page.getByTestId('switch-edgeless-mode-button'); + await btn.click(); const edgeless = page.locator('affine-edgeless-page'); expect(await edgeless.isVisible()).toBe(true); diff --git a/tests/layout.spec.ts b/tests/layout.spec.ts index 4a997693c7..d8984b0bac 100644 --- a/tests/layout.spec.ts +++ b/tests/layout.spec.ts @@ -7,17 +7,17 @@ loadPage(); test.describe('Layout ui', () => { test('Collapse Sidebar', async ({ page }) => { - await page.getByTestId('sliderBar-arrowButton').click(); + await page.getByTestId('sliderBar-arrowButton-collapse').click(); const sliderBarArea = page.getByTestId('sliderBar'); await expect(sliderBarArea).not.toBeVisible(); }); test('Expand Sidebar', async ({ page }) => { - await page.getByTestId('sliderBar-arrowButton').click(); + await page.getByTestId('sliderBar-arrowButton-collapse').click(); const sliderBarArea = page.getByTestId('sliderBar'); await expect(sliderBarArea).not.toBeVisible(); - await page.getByTestId('sliderBar-arrowButton').click(); + await page.getByTestId('sliderBar-arrowButton-expand').click(); await expect(sliderBarArea).toBeVisible(); }); });