feat: modify sidebar floating logic and header responsive style (#3550)

This commit is contained in:
JimmFly
2023-08-05 08:15:17 +08:00
committed by GitHub
parent 97de0ef5b4
commit 3a92c4f798
6 changed files with 189 additions and 142 deletions

View File

@@ -8,6 +8,7 @@ import { isDesktop } from '@affine/env/constant';
import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons'; import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store'; import type { Page } from '@blocksuite/store';
import { headerItemsAtom } from '@toeverything/infra/atom'; import { headerItemsAtom } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react'; import type { FC, HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
import { import {
@@ -38,8 +39,6 @@ export type BaseHeaderProps<
export enum HeaderRightItemName { export enum HeaderRightItemName {
EditorOptionMenu = 'editorOptionMenu', EditorOptionMenu = 'editorOptionMenu',
// some windows only items
WindowsAppControls = 'windowsAppControls',
} }
type HeaderItem = { type HeaderItem = {
@@ -63,59 +62,54 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
); );
}, },
}, },
[HeaderRightItemName.WindowsAppControls]: {
Component: () => {
const handleMinimizeApp = useCallback(() => {
window.apis?.ui.handleMinimizeApp().catch(err => {
console.error(err);
});
}, []);
const handleMaximizeApp = useCallback(() => {
window.apis?.ui.handleMaximizeApp().catch(err => {
console.error(err);
});
}, []);
const handleCloseApp = useCallback(() => {
window.apis?.ui.handleCloseApp().catch(err => {
console.error(err);
});
}, []);
return (
<div
data-platform-target="win32"
className={styles.windowAppControlsWrapper}
>
<button
data-type="minimize"
className={styles.windowAppControl}
onClick={handleMinimizeApp}
>
<MinusIcon />
</button>
<button
data-type="maximize"
className={styles.windowAppControl}
onClick={handleMaximizeApp}
>
<RoundedRectangleIcon />
</button>
<button
data-type="close"
className={styles.windowAppControl}
onClick={handleCloseApp}
>
<CloseIcon />
</button>
</div>
);
},
availableWhen: () => {
return isDesktop && globalThis.platform === 'win32';
},
},
}; };
export type HeaderProps = BaseHeaderProps; export type HeaderProps = BaseHeaderProps;
const WindowsAppControls = () => {
const handleMinimizeApp = useCallback(() => {
window.apis?.ui.handleMinimizeApp().catch(err => {
console.error(err);
});
}, []);
const handleMaximizeApp = useCallback(() => {
window.apis?.ui.handleMaximizeApp().catch(err => {
console.error(err);
});
}, []);
const handleCloseApp = useCallback(() => {
window.apis?.ui.handleCloseApp().catch(err => {
console.error(err);
});
}, []);
return (
<div
data-platform-target="win32"
className={styles.windowAppControlsWrapper}
>
<button
data-type="minimize"
className={styles.windowAppControl}
onClick={handleMinimizeApp}
>
<MinusIcon />
</button>
<button
data-type="maximize"
className={styles.windowAppControl}
onClick={handleMaximizeApp}
>
<RoundedRectangleIcon />
</button>
<button
data-type="close"
className={styles.windowAppControl}
onClick={handleCloseApp}
>
<CloseIcon />
</button>
</div>
);
};
const PluginHeader = () => { const PluginHeader = () => {
const rootRef = useRef<HTMLDivElement>(null); const rootRef = useRef<HTMLDivElement>(null);
@@ -165,7 +159,7 @@ export const Header = forwardRef<
const appSidebarFloating = useAtomValue(appSidebarFloatingAtom); const appSidebarFloating = useAtomValue(appSidebarFloatingAtom);
const mode = useAtomValue(currentModeAtom); const mode = useAtomValue(currentModeAtom);
const isWindowsDesktop = globalThis.platform === 'win32' && isDesktop;
return ( return (
<div <div
className={styles.headerContainer} className={styles.headerContainer}
@@ -196,14 +190,25 @@ export const Header = forwardRef<
data-has-warning={showWarning} data-has-warning={showWarning}
data-testid="editor-header-items" data-testid="editor-header-items"
data-is-edgeless={mode === 'edgeless'} data-is-edgeless={mode === 'edgeless'}
data-is-page-list={props.currentPage === null}
> >
<div className={styles.headerLeftSide}> <div className={styles.headerLeftSide}>
{!open && <SidebarSwitch />} <div>{!open && <SidebarSwitch />}</div>
{props.leftSlot} <div
className={clsx(styles.headerLeftSideItem, {
[styles.headerLeftSideOpen]: open,
})}
>
{props.leftSlot}
</div>
</div> </div>
{props.children} {props.children}
<div className={styles.headerRightSide}> <div
className={clsx(styles.headerRightSide, {
[styles.headerRightSideWindow]: isWindowsDesktop,
})}
>
<PluginHeader /> <PluginHeader />
{useMemo(() => { {useMemo(() => {
return Object.entries(HeaderRightItems).map( return Object.entries(HeaderRightItems).map(
@@ -227,6 +232,7 @@ export const Header = forwardRef<
); );
}, [props])} }, [props])}
</div> </div>
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div> </div>
</div> </div>
); );

View File

@@ -65,7 +65,7 @@ export const BlockSuiteEditorHeader: FC<
}} }}
/> />
</div> </div>
<div> <div className={styles.pageTitle}>
{isEditable ? ( {isEditable ? (
<div> <div>
<input <input
@@ -88,11 +88,7 @@ export const BlockSuiteEditorHeader: FC<
</Button> </Button>
</div> </div>
) : ( ) : (
<span <span data-testid="title-edit-button" onClick={handleClick}>
data-testid="title-edit-button"
onClick={handleClick}
style={{ cursor: 'pointer' }}
>
{title || 'Untitled'} {title || 'Untitled'}
</span> </span>
)} )}

View File

@@ -1,5 +1,7 @@
import type { ComplexStyleRule } from '@vanilla-extract/css'; import type { ComplexStyleRule } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css'; import { createContainer, style } from '@vanilla-extract/css';
export const headerVanillaContainer = createContainer();
export const headerContainer = style({ export const headerContainer = style({
height: 'auto', height: 'auto',
@@ -27,35 +29,45 @@ export const headerContainer = style({
} as ComplexStyleRule); } as ComplexStyleRule);
export const header = style({ export const header = style({
containerName: headerVanillaContainer,
containerType: 'inline-size',
flexShrink: 0, flexShrink: 0,
height: '52px', minHeight: '52px',
width: '100%', width: '100%',
padding: '0 20px', padding: '8px 20px',
display: 'flex', display: 'grid',
justifyContent: 'space-between', gridTemplateColumns: '1fr auto 1fr',
alignItems: 'center', alignItems: 'center',
background: 'var(--affine-background-primary-color)', background: 'var(--affine-background-primary-color)',
zIndex: 99, zIndex: 99,
position: 'relative', position: 'relative',
selectors: { selectors: {
'&[data-is-edgeless="true"]': { '&[data-is-page-list="true"], &[data-is-edgeless="true"]': {
borderBottom: `1px solid var(--affine-border-color)`, borderBottom: `1px solid var(--affine-border-color)`,
}, },
}, },
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
alignItems: 'start',
},
},
}); });
export const titleContainer = style({ export const titleContainer = style({
width: '100%', width: '100%',
height: '100%', height: '100%',
margin: 'auto',
position: 'absolute',
inset: 'auto auto auto 50%',
transform: 'translate(-50%, 0px)',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
alignContent: 'unset', alignContent: 'unset',
fontSize: 'var(--affine-font-base)', fontSize: 'var(--affine-font-base)',
['WebkitAppRegion' as string]: 'no-drag',
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
alignItems: 'start',
paddingTop: '2px',
},
},
}); });
export const title = style({ export const title = style({
@@ -75,9 +87,26 @@ export const title = style({
}, },
}, },
} as ComplexStyleRule); } as ComplexStyleRule);
export const pageTitle = style({
maxWidth: '600px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
transition: 'width .15s',
cursor: 'pointer',
'@container': {
[`${headerVanillaContainer} (max-width: 1920px)`]: {
maxWidth: '800px',
},
[`${headerVanillaContainer} (max-width: 1300px)`]: {
maxWidth: '400px',
},
[`${headerVanillaContainer} (max-width: 768px)`]: {
maxWidth: '220px',
},
},
});
export const titleWrapper = style({ export const titleWrapper = style({
height: '100%',
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
@@ -86,10 +115,28 @@ export const titleWrapper = style({
export const headerLeftSide = style({ export const headerLeftSide = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
width: '150px', transition: 'all .15s',
'@media': { '@container': {
'(max-width: 900px)': { [`${headerVanillaContainer} (max-width: 900px)`]: {
width: 'auto', flexDirection: 'column',
alignItems: 'flex-start',
height: '68px',
},
},
});
export const headerLeftSideItem = style({
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
position: 'absolute',
left: '0',
bottom: '8px',
},
},
});
export const headerLeftSideOpen = style({
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
marginLeft: '20px',
}, },
}, },
}); });
@@ -99,7 +146,21 @@ export const headerRightSide = style({
alignItems: 'center', alignItems: 'center',
gap: '12px', gap: '12px',
zIndex: 1, zIndex: 1,
marginLeft: '20px',
justifyContent: 'flex-end', justifyContent: 'flex-end',
transition: 'all .15s',
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
position: 'absolute',
height: 'auto',
right: '0',
bottom: '8px',
marginRight: '18px',
},
},
});
export const headerRightSideWindow = style({
marginRight: '140px',
}); });
export const browserWarning = style({ export const browserWarning = style({
@@ -131,22 +192,12 @@ export const closeButton = style({
}); });
export const switchWrapper = style({ export const switchWrapper = style({
position: 'absolute',
right: '100%',
top: 0,
bottom: 0,
margin: 'auto',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}); });
export const searchArrowWrapper = style({ export const searchArrowWrapper = style({
position: 'absolute',
left: 'calc(100% + 4px)',
top: 0,
bottom: 0,
margin: 'auto',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@@ -164,16 +215,13 @@ export const allPageListTitleWrapper = style({
color: 'var(--affine-text-primary-color)', color: 'var(--affine-text-primary-color)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
'::after': { width: '100%',
content: '""', height: '100%',
display: 'block', '@container': {
width: '100%', [`${headerVanillaContainer} (max-width: 900px)`]: {
height: '1px', alignItems: 'flex-start',
background: 'var(--affine-border-color)', marginTop: '8px',
position: 'absolute', },
bottom: 0,
left: 0,
margin: '0 1px',
}, },
}); });
export const pageListTitleIcon = style({ export const pageListTitleIcon = style({
@@ -220,30 +268,36 @@ export const windowAppControlsWrapper = style({
gap: '2px', gap: '2px',
transform: 'translateX(8px)', transform: 'translateX(8px)',
height: '100%', height: '100%',
position: 'absolute',
right: '14px',
}); });
export const windowAppControl = style({ export const windowAppControl = style({
WebkitAppRegion: 'no-drag', WebkitAppRegion: 'no-drag',
cursor: 'pointer', cursor: 'pointer',
display: 'inline-flex', display: 'inline-flex',
width: '42px', width: '51px',
height: 'calc(100% - 10px)',
paddingTop: '10px',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: '0', borderRadius: '0',
selectors: { selectors: {
'&[data-type="close"]': { '&[data-type="close"]': {
width: '56px', width: '56px',
paddingRight: '14px', paddingRight: '5px',
marginRight: '-14px', marginRight: '-12px',
}, },
'&[data-type="close"]:hover': { '&[data-type="close"]:hover': {
background: 'var(--affine-error-color)', background: 'var(--affine-windows-close-button)',
color: '#FFFFFF', color: 'var(--affine-pure-white)',
}, },
'&:hover': { '&:hover': {
background: 'var(--affine-background-tertiary-color)', background: 'var(--affine-hover-color)',
},
},
'@container': {
[`${headerVanillaContainer} (max-width: 900px)`]: {
height: '50px',
paddingTop: '0',
}, },
}, },
} as ComplexStyleRule); } as ComplexStyleRule);

View File

@@ -17,23 +17,18 @@ export const navWrapperStyle = style({
paddingBottom: '8px', paddingBottom: '8px',
backgroundColor: 'transparent', backgroundColor: 'transparent',
'@media': { '@media': {
[`(max-width: ${floatingMaxWidth}px)`]: {
position: 'absolute',
width: `calc(${navWidthVar})`,
zIndex: 4,
backgroundColor: 'var(--affine-background-primary-color)',
selectors: {
'&[data-open="false"]': {
marginLeft: `calc((10vw + ${navWidthVar}) * -1)`,
},
},
},
print: { print: {
display: 'none', display: 'none',
zIndex: -1, zIndex: -1,
}, },
}, },
selectors: { selectors: {
'&[data-is-floating="true"]': {
position: 'absolute',
width: `calc(${navWidthVar})`,
zIndex: 4,
backgroundColor: 'var(--affine-background-primary-color)',
},
'&[data-open="false"]': { '&[data-open="false"]': {
marginLeft: `calc(${navWidthVar} * -1)`, marginLeft: `calc(${navWidthVar} * -1)`,
}, },
@@ -93,17 +88,15 @@ export const sidebarFloatMaskStyle = style({
right: '100%', right: '100%',
bottom: 0, bottom: 0,
background: 'var(--affine-background-modal-color)', background: 'var(--affine-background-modal-color)',
'@media': { selectors: {
[`(max-width: ${floatingMaxWidth}px)`]: { '&[data-open="true"][data-is-floating="true"]': {
selectors: { opacity: 1,
'&[data-open="true"]': { pointerEvents: 'auto',
opacity: 1, right: '0',
pointerEvents: 'auto', zIndex: 3,
right: '0',
zIndex: 3,
},
},
}, },
},
'@media': {
print: { print: {
display: 'none', display: 'none',
}, },

View File

@@ -59,18 +59,22 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
useEffect(() => { useEffect(() => {
function onResize() { function onResize() {
const { matches } = window.matchMedia( const isFloatingMaxWidth = window.matchMedia(
`(max-width: ${floatingMaxWidth}px)` `(max-width: ${floatingMaxWidth}px)`
); ).matches;
const isOverflowWidth = window.matchMedia(
`(max-width: ${appSidebarWidth / 0.4}px)`
).matches;
const isFloating = isFloatingMaxWidth || isOverflowWidth;
if ( if (
open === undefined && open === undefined &&
localStorage.getItem(APP_SIDEBAR_OPEN) === null localStorage.getItem(APP_SIDEBAR_OPEN) === null
) { ) {
// give the initial value, // give the initial value,
// so that the sidebar can be closed on mobile by default // so that the sidebar can be closed on mobile by default
setOpen(!matches); setOpen(!isFloating);
} }
setAppSidebarFloating(matches && !!open); setAppSidebarFloating(isFloating && !!open);
} }
onResize(); onResize();
@@ -78,7 +82,7 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
return () => { return () => {
window.removeEventListener('resize', onResize); window.removeEventListener('resize', onResize);
}; };
}, [open, setAppSidebarFloating, setOpen]); }, [appSidebarWidth, open, setAppSidebarFloating, setOpen]);
// disable animation to avoid UI flash // disable animation to avoid UI flash
const enableAnimation = useEnableAnimation(); const enableAnimation = useEnableAnimation();
@@ -99,6 +103,7 @@ export function AppSidebar(props: AppSidebarProps): ReactElement {
})} })}
data-open={open} data-open={open}
data-is-macos-electron={isMacosDesktop} data-is-macos-electron={isMacosDesktop}
data-is-floating={appSidebarFloating}
data-enable-animation={enableAnimation && !isResizing} data-enable-animation={enableAnimation && !isResizing}
> >
<nav className={navStyle} ref={navRef} data-testid="app-sidebar"> <nav className={navStyle} ref={navRef} data-testid="app-sidebar">

View File

@@ -21,7 +21,7 @@ export const viewButton = style({
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
background: 'var(--affine-white)', background: 'var(--affine-white)',
['WebkitAppRegion' as string]: 'no-drag', ['WebkitAppRegion' as string]: 'no-drag',
maxWidth: '200px', maxWidth: '150px',
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
border: '1px solid var(--affine-border-color)', border: '1px solid var(--affine-border-color)',
transition: 'margin-left 0.2s ease-in-out', transition: 'margin-left 0.2s ease-in-out',
@@ -30,15 +30,6 @@ export const viewButton = style({
background: 'var(--affine-hover-color)', background: 'var(--affine-hover-color)',
}, },
marginRight: '20px', marginRight: '20px',
'@media': {
'(max-width: 1200px)': {
maxWidth: '100px',
},
'(max-width: 900px)': {
maxWidth: '150px',
marginRight: '10px',
},
},
}); });
globalStyle(`${viewButton} > span`, { globalStyle(`${viewButton} > span`, {
display: 'block', display: 'block',
@@ -74,6 +65,8 @@ export const deleteOption = style({
export const filterButton = style({ export const filterButton = style({
borderRadius: '8px', borderRadius: '8px',
height: '100%', height: '100%',
width: '100%',
marginRight: '20px',
padding: '4px 8px', padding: '4px 8px',
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
background: 'var(--affine-white)', background: 'var(--affine-white)',