mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-09-22 00:49:36 +03:00
Feat/sidebar&top bar (#1454)
This commit is contained in:
parent
31d2e522eb
commit
921061eeb6
26
apps/web/src/components/affine/sidebar-switch/icons.tsx
Normal file
26
apps/web/src/components/affine/sidebar-switch/icons.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
export const SidebarSwitchIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11 5.00009V19.0001M6 7.62509H8M6 10.2501H8M6 12.8751H8M6.2 19.0001H17.8C18.9201 19.0001 19.4802 19.0001 19.908 18.8094C20.2843 18.6416 20.5903 18.3739 20.782 18.0446C21 17.6702 21 17.1802 21 16.2001V7.80009C21 6.82 21 6.32995 20.782 5.95561C20.5903 5.62632 20.2843 5.35861 19.908 5.19083C19.4802 5.00009 18.9201 5.00009 17.8 5.00009H6.2C5.0799 5.00009 4.51984 5.00009 4.09202 5.19083C3.71569 5.35861 3.40973 5.62632 3.21799 5.95561C3 6.32995 3 6.82 3 7.80009V16.2001C3 17.1802 3 17.6702 3.21799 18.0446C3.40973 18.3739 3.71569 18.6416 4.09202 18.8094C4.51984 19.0001 5.07989 19.0001 6.2 19.0001Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11 5.00009V19.0001M6 7.62509H8M6 10.2501H8M6 12.8751H8M6.2 19.0001H17.8C18.9201 19.0001 19.4802 19.0001 19.908 18.8094C20.2843 18.6416 20.5903 18.3739 20.782 18.0446C21 17.6702 21 17.1802 21 16.2001V7.80009C21 6.82 21 6.32995 20.782 5.95561C20.5903 5.62632 20.2843 5.35861 19.908 5.19083C19.4802 5.00009 18.9201 5.00009 17.8 5.00009H6.2C5.0799 5.00009 4.51984 5.00009 4.09202 5.19083C3.71569 5.35861 3.40973 5.62632 3.21799 5.95561C3 6.32995 3 6.82 3 7.80009V16.2001C3 17.1802 3 17.6702 3.21799 18.0446C3.40973 18.3739 3.71569 18.6416 4.09202 18.8094C4.51984 19.0001 5.07989 19.0001 6.2 19.0001Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
49
apps/web/src/components/affine/sidebar-switch/index.tsx
Normal file
49
apps/web/src/components/affine/sidebar-switch/index.tsx
Normal file
@ -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 (
|
||||
<Tooltip
|
||||
content={tooltipContent}
|
||||
placement="right"
|
||||
zIndex={1000}
|
||||
visible={tooltipVisible}
|
||||
>
|
||||
<StyledSidebarSwitch
|
||||
visible={visible}
|
||||
data-testid={testid}
|
||||
onClick={useCallback(() => {
|
||||
setOpen(!open);
|
||||
setTooltipVisible(false);
|
||||
}, [open, setOpen])}
|
||||
onMouseEnter={useCallback(() => {
|
||||
setTooltipVisible(true);
|
||||
}, [])}
|
||||
onMouseLeave={useCallback(() => {
|
||||
setTooltipVisible(false);
|
||||
}, [])}
|
||||
>
|
||||
<SidebarSwitchIcon />
|
||||
</StyledSidebarSwitch>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
23
apps/web/src/components/affine/sidebar-switch/style.ts
Normal file
23
apps/web/src/components/affine/sidebar-switch/style.ts
Normal file
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
@ -1,85 +0,0 @@
|
||||
import { CSSProperties, DOMAttributes } from 'react';
|
||||
|
||||
type IconProps = {
|
||||
style?: CSSProperties;
|
||||
} & DOMAttributes<SVGElement>;
|
||||
|
||||
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 (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="6"
|
||||
height="16"
|
||||
viewBox="0 0 6 16"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
style={style}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0.602933 0.305738C0.986547 0.0865297 1.47523 0.219807 1.69444 0.603421L5.41093 7.10728C5.72715 7.66066 5.72715 8.34 5.41093 8.89338L1.69444 15.3972C1.47523 15.7809 0.986547 15.9141 0.602933 15.6949C0.219319 15.4757 0.0860414 14.987 0.305249 14.6034L4.02174 8.09956C4.05688 8.03807 4.05688 7.96259 4.02174 7.9011L0.305249 1.39724C0.0860414 1.01363 0.219319 0.524946 0.602933 0.305738Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PaperIcon = ({ style = {}, ...props }: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M17 9.8H7V8.2h10v1.6ZM12 12.8H7v-1.6h5v1.6Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path d="m14 19 7-7h-5a2 2 0 0 0-2 2v5Z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5 6.6h14c.22 0 .4.18.4.4v6.6L21 12V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h9l1.6-1.6H5a.4.4 0 0 1-.4-.4V7c0-.22.18-.4.4-.4Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const EdgelessIcon = ({ style = {}, ...props }: IconProps) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 17.4a5.4 5.4 0 1 0 0-10.8 5.4 5.4 0 0 0 0 10.8Zm7-5.4a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18.565 8a.8.8 0 0 1 .8-.8c.797 0 1.511.07 2.07.24.5.15 1.172.477 1.334 1.202v.004c.089.405-.026.776-.186 1.065a3.165 3.165 0 0 1-.652.782c-.52.471-1.265.947-2.15 1.407-1.783.927-4.28 1.869-7.077 2.62-2.796.752-5.409 1.184-7.381 1.266-.98.04-1.848-.003-2.516-.162-.333-.079-.662-.196-.937-.38-.282-.19-.547-.48-.639-.892v-.002c-.138-.63.202-1.173.518-1.532.343-.39.836-.768 1.413-1.129a.8.8 0 0 1 .848 1.357c-.515.322-.862.605-1.06.83a1.524 1.524 0 0 0-.078.096c.07.03.169.064.304.095.461.11 1.163.158 2.08.12 1.822-.075 4.314-.481 7.033-1.212 2.718-.73 5.1-1.635 6.753-2.494.832-.433 1.441-.835 1.814-1.173.127-.115.213-.21.268-.284a1.67 1.67 0 0 0-.153-.053c-.342-.104-.878-.171-1.606-.171a.8.8 0 0 1-.8-.8Zm2.692 1.097-.004-.004a.026.026 0 0 1 .004.004Zm-18.46 5 .001-.002v.002Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
@ -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 <PaperIcon style={{ color: active ? primaryColor : iconColor }} />;
|
||||
};
|
||||
|
||||
const EdgelessItem = ({ active }: { active?: boolean }) => {
|
||||
const {
|
||||
colors: { iconColor, primaryColor },
|
||||
} = useTheme();
|
||||
|
||||
return <EdgelessIcon style={{ color: active ? primaryColor : iconColor }} />;
|
||||
};
|
||||
|
||||
const AnimateRadioItem = ({
|
||||
active,
|
||||
status,
|
||||
icon: propsIcon,
|
||||
label,
|
||||
isLeft,
|
||||
...props
|
||||
}: AnimateRadioItemProps) => {
|
||||
const icon = (
|
||||
<StyledIcon shrink={status === 'shrink'} isLeft={isLeft}>
|
||||
{cloneElement(propsIcon, {
|
||||
active,
|
||||
})}
|
||||
</StyledIcon>
|
||||
);
|
||||
return (
|
||||
<StyledRadioItem title={label} active={active} status={status} {...props}>
|
||||
{isLeft ? icon : null}
|
||||
<StyledLabel shrink={status !== 'stretch'} isLeft={isLeft}>
|
||||
{label}
|
||||
</StyledLabel>
|
||||
{isLeft ? null : icon}
|
||||
</StyledRadioItem>
|
||||
);
|
||||
};
|
||||
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<EditorModeSwitchProps> = ({
|
||||
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<EditorModeSwitchProps> = ({
|
||||
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<RadioItemStatus>(
|
||||
modifyRadioItemStatus
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRadioItemStatus(modifyRadioItemStatus());
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isHover, mode]);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledAnimateRadioContainer
|
||||
data-testid="editor-mode-switcher"
|
||||
shrink={!isHover}
|
||||
<StyledEditorModeSwitch
|
||||
style={style}
|
||||
disabled={!!trash}
|
||||
switchLeft={mode === 'page'}
|
||||
showAlone={trash}
|
||||
>
|
||||
<AnimateRadioItem
|
||||
isLeft={true}
|
||||
label={t('Page')}
|
||||
icon={<PaperItem />}
|
||||
<StyledSwitchItem
|
||||
data-testid="switch-page-mode-button"
|
||||
active={mode === 'page'}
|
||||
status={radioItemStatus.left}
|
||||
hide={trash && mode !== 'page'}
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { mode: 'page' });
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setRadioItemStatus({
|
||||
right: 'normal',
|
||||
left: 'stretch',
|
||||
});
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setRadioItemStatus(modifyRadioItemStatus());
|
||||
}}
|
||||
/>
|
||||
<StyledMiddleLine
|
||||
hidden={!isHover}
|
||||
dark={theme.palette.mode === 'dark'}
|
||||
/>
|
||||
<AnimateRadioItem
|
||||
isLeft={false}
|
||||
label={t('Edgeless')}
|
||||
data-testid="switch-edgeless-item"
|
||||
icon={<EdgelessItem />}
|
||||
>
|
||||
<PaperIcon />
|
||||
</StyledSwitchItem>
|
||||
<StyledSwitchItem
|
||||
data-testid="switch-edgeless-mode-button"
|
||||
active={mode === 'edgeless'}
|
||||
status={radioItemStatus.right}
|
||||
hide={trash && mode !== 'edgeless'}
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, { mode: 'edgeless' });
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setRadioItemStatus({
|
||||
left: 'normal',
|
||||
right: 'stretch',
|
||||
});
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setRadioItemStatus(modifyRadioItemStatus());
|
||||
}}
|
||||
/>
|
||||
</StyledAnimateRadioContainer>
|
||||
>
|
||||
<EdgelessIcon />
|
||||
</StyledSwitchItem>
|
||||
</StyledEditorModeSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorModeSwitch;
|
||||
|
@ -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',
|
||||
};
|
||||
});
|
||||
|
@ -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<HTMLDivElement>;
|
@ -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 ? (
|
||||
<FavoritedIcon style={{ color: theme.colors.primaryColor }} />
|
||||
@ -64,6 +65,7 @@ export const EditorOptionMenu = () => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={mode === 'page' ? <EdgelessIcon /> : <PaperIcon />}
|
||||
iconSize={[20, 20]}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onClick={() => {
|
||||
setPageMeta(pageId, {
|
||||
@ -84,6 +86,7 @@ export const EditorOptionMenu = () => {
|
||||
globalThis.editor.contentParser.onExportHtml();
|
||||
}}
|
||||
icon={<ExportToHtmlIcon />}
|
||||
iconSize={[20, 20]}
|
||||
>
|
||||
{t('Export to HTML')}
|
||||
</MenuItem>
|
||||
@ -93,13 +96,14 @@ export const EditorOptionMenu = () => {
|
||||
globalThis.editor.contentParser.onExportMarkdown();
|
||||
}}
|
||||
icon={<ExportToMarkdownIcon />}
|
||||
iconSize={[20, 20]}
|
||||
>
|
||||
{t('Export to Markdown')}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuItem icon={<ExportIcon />} isDir={true}>
|
||||
<MenuItem icon={<ExportIcon />} iconSize={[20, 20]} isDir={true}>
|
||||
{t('Export')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
@ -109,6 +113,7 @@ export const EditorOptionMenu = () => {
|
||||
setOpen(true);
|
||||
}}
|
||||
icon={<DeleteTemporarilyIcon />}
|
||||
iconSize={[20, 20]}
|
||||
>
|
||||
{t('Delete')}
|
||||
</MenuItem>
|
||||
@ -124,7 +129,7 @@ export const EditorOptionMenu = () => {
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton data-testid="editor-option-menu" iconSize={[20, 20]}>
|
||||
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
|
@ -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]}
|
||||
>
|
||||
<LocalWorkspaceIcon />
|
||||
</IconButton>
|
||||
|
@ -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;
|
||||
|
@ -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<HeaderProps> = ({
|
||||
useEffect(() => {
|
||||
setShowWarning(shouldShowWarning());
|
||||
}, []);
|
||||
const [open] = useSidebarStatus();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledHeaderContainer hasWarning={showWarning}>
|
||||
<BrowserWarning
|
||||
@ -69,6 +75,12 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
data-testid="editor-header-items"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<SidebarSwitch
|
||||
visible={!open}
|
||||
tooltipContent={t('Expand sidebar')}
|
||||
testid="sliderBar-arrowButton-expand"
|
||||
/>
|
||||
|
||||
{children}
|
||||
<StyledHeaderRightSide>
|
||||
{useMemo(
|
||||
|
@ -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<BlockSuiteEditorHeaderProps> = ({
|
||||
);
|
||||
assertExists(pageMeta);
|
||||
const title = pageMeta.title;
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const { trash: isTrash } = pageMeta;
|
||||
return (
|
||||
<Header
|
||||
@ -57,25 +56,12 @@ export const BlockSuiteEditorHeader: React.FC<BlockSuiteEditorHeaderProps> = ({
|
||||
>
|
||||
{children}
|
||||
{title && !isPublic && (
|
||||
<StyledTitle
|
||||
data-tauri-drag-region
|
||||
onMouseEnter={() => {
|
||||
if (isTrash) return;
|
||||
|
||||
setIsHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (isTrash) return;
|
||||
|
||||
setIsHover(false);
|
||||
}}
|
||||
>
|
||||
<StyledTitle data-tauri-drag-region>
|
||||
<StyledTitleWrapper>
|
||||
<StyledSwitchWrapper>
|
||||
<EditorModeSwitch
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
pageId={pageId}
|
||||
isHover={isHover}
|
||||
style={{
|
||||
marginRight: '12px',
|
||||
}}
|
||||
|
@ -10,7 +10,7 @@ export const StyledHeaderContainer = styled('div')<{ hasWarning: boolean }>(
|
||||
export const StyledHeader = styled('div')<{ hasWarning: boolean }>(
|
||||
({ theme }) => {
|
||||
return {
|
||||
height: '60px',
|
||||
height: '64px',
|
||||
width: '100%',
|
||||
padding: '0 28px',
|
||||
...displayFlex('flex-end', 'center'),
|
||||
|
@ -54,10 +54,10 @@ export const Avatar: React.FC<AvatarProps> = React.memo<AvatarProps>(
|
||||
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)}
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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<string>;
|
||||
show: boolean;
|
||||
setShow: (show: boolean) => void;
|
||||
currentPath: string;
|
||||
paths: {
|
||||
all: (workspaceId: string) => string;
|
||||
@ -100,17 +98,17 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
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<WorkSpaceSliderBarProps> = ({
|
||||
}, [createPage, openPage]);
|
||||
return (
|
||||
<>
|
||||
<StyledSliderBar show={isPublicWorkspace ? false : show}>
|
||||
<Tooltip
|
||||
content={show ? t('Collapse sidebar') : t('Expand sidebar')}
|
||||
placement="right"
|
||||
visible={showTip}
|
||||
>
|
||||
<StyledArrowButton
|
||||
data-testid="sliderBar-arrowButton"
|
||||
isShow={show}
|
||||
style={{
|
||||
visibility: isPublicWorkspace ? 'hidden' : 'visible',
|
||||
}}
|
||||
onClick={useCallback(() => {
|
||||
setShow(!show);
|
||||
setShowTip(false);
|
||||
}, [setShow, show])}
|
||||
onMouseEnter={useCallback(() => {
|
||||
setShowTip(true);
|
||||
}, [])}
|
||||
onMouseLeave={useCallback(() => {
|
||||
setShowTip(false);
|
||||
}, [])}
|
||||
>
|
||||
<Arrow />
|
||||
</StyledArrowButton>
|
||||
</Tooltip>
|
||||
|
||||
<StyledSliderBar show={isPublicWorkspace ? false : sidebarOpen}>
|
||||
<StyledSidebarWrapper>
|
||||
<SidebarSwitch
|
||||
visible={open}
|
||||
tooltipContent={t('Collapse sidebar')}
|
||||
testid="sliderBar-arrowButton-collapse"
|
||||
/>
|
||||
</StyledSidebarWrapper>
|
||||
<StyledSliderBarWrapper data-testid="sliderBar">
|
||||
<WorkspaceSelector
|
||||
currentWorkspace={currentWorkspace}
|
||||
@ -196,7 +175,7 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
<IconButton
|
||||
darker={true}
|
||||
onClick={useCallback(() => {
|
||||
setShowSubFavorite(!showSubFavorite);
|
||||
setOpenSubFavorite(!showSubFavorite);
|
||||
}, [showSubFavorite])}
|
||||
>
|
||||
<ArrowDownSmallIcon
|
||||
@ -233,7 +212,7 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
{/* <WorkspaceSetting
|
||||
isShow={showWorkspaceSetting}
|
||||
onClose={() => {
|
||||
setShowWorkspaceSetting(false);
|
||||
setOpenWorkspaceSetting(false);
|
||||
}}
|
||||
/> */}
|
||||
{/* TODO: will finish the feature next version */}
|
||||
|
@ -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;
|
||||
|
8
apps/web/src/hooks/affine/use-sidebar-status.ts
Normal file
8
apps/web/src/hooks/affine/use-sidebar-status.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { useAtom } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
const sideBarOpenAtom = atomWithStorage('sidebarOpen', true);
|
||||
|
||||
export function useSidebarStatus() {
|
||||
return useAtom(sideBarOpenAtom);
|
||||
}
|
@ -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<React.PropsWithChildren> =
|
||||
function WorkspacesSuspense({ children }) {
|
||||
@ -91,7 +88,6 @@ export const WorkspaceLayout: React.FC<React.PropsWithChildren> =
|
||||
export const WorkspaceLayoutInner: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [show, setShow] = useAtom(sideBarOpenAtom);
|
||||
const [currentWorkspace] = useCurrentWorkspace();
|
||||
const [currentPageId] = useCurrentPageId();
|
||||
const workspaces = useWorkspaces();
|
||||
@ -183,8 +179,6 @@ export const WorkspaceLayoutInner: React.FC<React.PropsWithChildren> = ({
|
||||
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
||||
openPage={handleOpenPage}
|
||||
createPage={handleCreatePage}
|
||||
show={show}
|
||||
setShow={setShow}
|
||||
currentPath={router.asPath}
|
||||
paths={isPublicWorkspace ? publicPathGenerator : pathGenerator}
|
||||
/>
|
||||
|
@ -10,17 +10,19 @@ import { StyledArrow, StyledMenuItem } from './styles';
|
||||
export type IconMenuProps = PropsWithChildren<{
|
||||
isDir?: boolean;
|
||||
icon?: ReactElement;
|
||||
iconSize?: [number, number];
|
||||
}> &
|
||||
HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const MenuItem = forwardRef<HTMLButtonElement, IconMenuProps>(
|
||||
({ isDir = false, icon, children, ...props }, ref) => {
|
||||
({ isDir = false, icon, iconSize, children, ...props }, ref) => {
|
||||
const [iconWidth, iconHeight] = iconSize || [16, 16];
|
||||
return (
|
||||
<StyledMenuItem ref={ref} {...props}>
|
||||
{icon &&
|
||||
cloneElement(icon, {
|
||||
width: 16,
|
||||
height: 16,
|
||||
width: iconWidth,
|
||||
height: iconHeight,
|
||||
style: {
|
||||
marginRight: 14,
|
||||
...icon.props?.style,
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user