mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat: responsive ui above 640px (#1741)
This commit is contained in:
@@ -2,7 +2,6 @@ import { displayFlex, textEllipsis } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
export const StyledSelectorContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
marginTop: '4px',
|
||||
height: '58px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -10,7 +9,6 @@ export const StyledSelectorContainer = styled('div')(({ theme }) => {
|
||||
marginBottom: '16px',
|
||||
borderRadius: '8px',
|
||||
color: theme.colors.textColor,
|
||||
position: 'relative',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: theme.colors.hoverBackground,
|
||||
|
||||
@@ -8,10 +8,15 @@ import {
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
import { useMediaQuery, useTheme } from '@mui/material';
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
||||
import {
|
||||
useSidebarResizing,
|
||||
useSidebarStatus,
|
||||
useSidebarWidth,
|
||||
} from '../../../hooks/affine/use-sidebar-status';
|
||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
||||
@@ -23,8 +28,12 @@ import {
|
||||
StyledLink,
|
||||
StyledNewPageButton,
|
||||
StyledSidebarSwitchWrapper,
|
||||
StyledSlidebarWrapper,
|
||||
StyledSliderBar,
|
||||
StyledSliderBarInnerWrapper,
|
||||
StyledSliderBarWrapper,
|
||||
StyledSliderModalBackground,
|
||||
StyledSliderResizer,
|
||||
StyledSliderResizerInner,
|
||||
} from './style';
|
||||
import { WorkspaceSelector } from './WorkspaceSelector';
|
||||
|
||||
@@ -65,114 +74,171 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
}) => {
|
||||
const currentWorkspaceId = currentWorkspace?.id || null;
|
||||
const { t } = useTranslation();
|
||||
const [sidebarOpen] = useSidebarStatus();
|
||||
const [sidebarOpen, setSidebarOpen] = useSidebarStatus();
|
||||
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
|
||||
const onClickNewPage = useCallback(async () => {
|
||||
const page = await createPage();
|
||||
openPage(page.id);
|
||||
}, [createPage, openPage]);
|
||||
const theme = useTheme();
|
||||
const floatingSlider = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [sliderWidth, setSliderWidth] = useSidebarWidth();
|
||||
const [isResizing, setIsResizing] = useSidebarResizing();
|
||||
const show = isPublicWorkspace ? false : sidebarOpen;
|
||||
const actualWidth = show
|
||||
? floatingSlider
|
||||
? 'calc(10vw + 400px)'
|
||||
: sliderWidth
|
||||
: 0;
|
||||
const onResizeStart = useCallback(() => {
|
||||
let resized = false;
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
const newWidth = Math.min(480, Math.max(e.clientX, 256));
|
||||
setSliderWidth(newWidth);
|
||||
setIsResizing(true);
|
||||
resized = true;
|
||||
}
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener(
|
||||
'mouseup',
|
||||
() => {
|
||||
// if not resized, toggle sidebar
|
||||
if (!resized) {
|
||||
setSidebarOpen(o => !o);
|
||||
}
|
||||
setIsResizing(false);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}, [setIsResizing, setSidebarOpen, setSliderWidth]);
|
||||
return (
|
||||
<>
|
||||
<StyledSliderBar show={isPublicWorkspace ? false : sidebarOpen}>
|
||||
<StyledSidebarSwitchWrapper>
|
||||
<SidebarSwitch
|
||||
visible={sidebarOpen}
|
||||
tooltipContent={t('Collapse sidebar')}
|
||||
testid="sliderBar-arrowButton-collapse"
|
||||
/>
|
||||
</StyledSidebarSwitchWrapper>
|
||||
|
||||
<StyledSlidebarWrapper data-testid="sliderBar">
|
||||
<WorkspaceSelector
|
||||
currentWorkspace={currentWorkspace}
|
||||
onClick={onOpenWorkspaceListModal}
|
||||
/>
|
||||
<ChangeLog />
|
||||
<StyledListItem
|
||||
data-testid="slider-bar-quick-search-button"
|
||||
onClick={useCallback(() => {
|
||||
onOpenQuickSearchModal();
|
||||
}, [onOpenQuickSearchModal])}
|
||||
>
|
||||
<SearchIcon />
|
||||
{t('Quick search')}
|
||||
</StyledListItem>
|
||||
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.setting(currentWorkspaceId))
|
||||
}
|
||||
data-testid="slider-bar-workspace-setting-button"
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname:
|
||||
currentWorkspaceId && paths.setting(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<SettingsIcon />
|
||||
{t('Workspace Settings')}
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.all(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<FolderIcon />
|
||||
<span data-testid="all-pages">{t('All pages')}</span>
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
<Favorite
|
||||
currentPath={currentPath}
|
||||
paths={paths}
|
||||
currentPageId={currentPageId}
|
||||
openPage={openPage}
|
||||
currentWorkspace={currentWorkspace}
|
||||
/>
|
||||
{config.enableSubpage && !!currentWorkspace && (
|
||||
<Pivots
|
||||
currentWorkspace={currentWorkspace}
|
||||
openPage={openPage}
|
||||
allMetas={pageMeta}
|
||||
/>
|
||||
)}
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.trash(currentWorkspaceId))
|
||||
}
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
}}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<DeleteTemporarilyIcon /> {t('Trash')}
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
</StyledSlidebarWrapper>
|
||||
|
||||
<StyledNewPageButton
|
||||
data-testid="new-page-button"
|
||||
onClick={onClickNewPage}
|
||||
<StyledSliderBarWrapper data-testid="sliderBar-root">
|
||||
<StyledSliderBar
|
||||
resizing={isResizing}
|
||||
floating={floatingSlider}
|
||||
style={{ width: actualWidth }}
|
||||
show={show}
|
||||
>
|
||||
<PlusIcon /> {t('New Page')}
|
||||
</StyledNewPageButton>
|
||||
</StyledSliderBar>
|
||||
<StyledSidebarSwitchWrapper>
|
||||
<SidebarSwitch
|
||||
visible={sidebarOpen}
|
||||
tooltipContent={t('Collapse sidebar')}
|
||||
testid="sliderBar-arrowButton-collapse"
|
||||
/>
|
||||
</StyledSidebarSwitchWrapper>
|
||||
|
||||
<StyledSliderBarInnerWrapper data-testid="sliderBar-inner">
|
||||
<WorkspaceSelector
|
||||
currentWorkspace={currentWorkspace}
|
||||
onClick={onOpenWorkspaceListModal}
|
||||
/>
|
||||
<ChangeLog />
|
||||
<StyledListItem
|
||||
data-testid="slider-bar-quick-search-button"
|
||||
onClick={useCallback(() => {
|
||||
onOpenQuickSearchModal();
|
||||
}, [onOpenQuickSearchModal])}
|
||||
>
|
||||
<SearchIcon />
|
||||
{t('Quick search')}
|
||||
</StyledListItem>
|
||||
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.setting(currentWorkspaceId))
|
||||
}
|
||||
data-testid="slider-bar-workspace-setting-button"
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname:
|
||||
currentWorkspaceId && paths.setting(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<SettingsIcon />
|
||||
{t('Workspace Settings')}
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.all(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<FolderIcon />
|
||||
<span data-testid="all-pages">{t('All pages')}</span>
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
|
||||
<Favorite
|
||||
currentPath={currentPath}
|
||||
paths={paths}
|
||||
currentPageId={currentPageId}
|
||||
openPage={openPage}
|
||||
currentWorkspace={currentWorkspace}
|
||||
/>
|
||||
{config.enableSubpage && !!currentWorkspace && (
|
||||
<Pivots
|
||||
currentWorkspace={currentWorkspace}
|
||||
openPage={openPage}
|
||||
allMetas={pageMeta}
|
||||
/>
|
||||
)}
|
||||
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.trash(currentWorkspaceId))
|
||||
}
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
}}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname:
|
||||
currentWorkspaceId && paths.trash(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<DeleteTemporarilyIcon /> {t('Trash')}
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
</StyledSliderBarInnerWrapper>
|
||||
|
||||
<StyledNewPageButton
|
||||
data-testid="new-page-button"
|
||||
onClick={onClickNewPage}
|
||||
>
|
||||
<PlusIcon /> {t('New Page')}
|
||||
</StyledNewPageButton>
|
||||
</StyledSliderBar>
|
||||
{!floatingSlider && sidebarOpen && (
|
||||
<StyledSliderResizer
|
||||
data-testid="sliderBar-resizer"
|
||||
isResizing={isResizing}
|
||||
onMouseDown={onResizeStart}
|
||||
>
|
||||
<StyledSliderResizerInner isResizing={isResizing} />
|
||||
</StyledSliderResizer>
|
||||
)}
|
||||
</StyledSliderBarWrapper>
|
||||
<StyledSliderModalBackground
|
||||
data-testid="sliderBar-modalBackground"
|
||||
active={floatingSlider && sidebarOpen}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
import { displayFlex, styled } from '@affine/component';
|
||||
import Link from 'next/link';
|
||||
export const StyledSliderBar = styled('div')<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
width: show ? '256px' : '0',
|
||||
whiteSpace: 'nowrap',
|
||||
height: '100vh',
|
||||
minHeight: '450px',
|
||||
background: theme.colors.hubBackground,
|
||||
boxShadow: theme.shadow.popover,
|
||||
transition: 'width .15s, padding .15s',
|
||||
position: 'relative',
|
||||
zIndex: theme.zIndex.modal,
|
||||
padding: show ? '0 4px' : '0',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
position: 'absolute',
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledSliderBarWrapper = styled('div')(() => {
|
||||
return {
|
||||
height: '100%',
|
||||
width: 'auto',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSliderBar = styled('div')<{
|
||||
resizing: boolean;
|
||||
show: boolean;
|
||||
floating: boolean;
|
||||
}>(({ theme, show, floating, resizing }) => {
|
||||
return {
|
||||
whiteSpace: 'nowrap',
|
||||
height: '100%',
|
||||
background: theme.colors.hubBackground,
|
||||
zIndex: theme.zIndex.modal,
|
||||
transition: !resizing ? 'width .15s, padding .15s' : '',
|
||||
padding: show ? '0 4px' : '0',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
position: floating ? 'absolute' : 'relative',
|
||||
maxWidth: floating ? undefined : 'calc(100vw - 698px)',
|
||||
borderRight: '1px solid',
|
||||
borderColor: theme.colors.borderColor,
|
||||
};
|
||||
});
|
||||
export const StyledSidebarSwitchWrapper = styled('div')(() => {
|
||||
return {
|
||||
height: '48px',
|
||||
flexShrink: 0,
|
||||
padding: '0 16px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledSlidebarWrapper = styled('div')(() => {
|
||||
export const StyledSliderBarInnerWrapper = styled('div')(() => {
|
||||
return {
|
||||
flexGrow: 1,
|
||||
overflowX: 'hidden',
|
||||
@@ -70,3 +79,52 @@ export const StyledNewPageButton = styled('button')(({ theme }) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledSliderModalBackground = styled('div')<{ active: boolean }>(
|
||||
({ theme, active }) => {
|
||||
return {
|
||||
transition: 'opacity .15s',
|
||||
pointerEvents: active ? 'auto' : 'none',
|
||||
opacity: active ? 1 : 0,
|
||||
display: active ? 'block' : 'none',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: theme.zIndex.modal - 1,
|
||||
background: theme.colors.modalBackground,
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledSliderResizer = styled('div')<{ isResizing: boolean }>(
|
||||
({ theme }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '12px',
|
||||
transform: 'translateX(50%)',
|
||||
cursor: 'col-resize',
|
||||
zIndex: theme.zIndex.modal + 1,
|
||||
userSelect: 'none',
|
||||
':hover > *': {
|
||||
background: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledSliderResizerInner = styled('div')<{ isResizing: boolean }>(
|
||||
({ isResizing }) => {
|
||||
return {
|
||||
transition: 'background .15s .1s',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: '50%',
|
||||
bottom: 0,
|
||||
transform: 'translateX(0.5px)',
|
||||
width: '2px',
|
||||
background: isResizing ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user