fix: app sidebar ui issues (#3783)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Peng Xiao 2023-08-18 02:36:17 +08:00 committed by GitHub
parent 7a31089c4b
commit 068c697be9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 137 additions and 82 deletions

View File

@ -6,7 +6,6 @@ export const StyledSelectorContainer = styled('div')(() => {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '0 6px', padding: '0 6px',
margin: '0 -6px',
borderRadius: '8px', borderRadius: '8px',
color: 'var(--affine-text-primary-color)', color: 'var(--affine-text-primary-color)',
':hover': { ':hover': {

View File

@ -211,7 +211,7 @@ const CollectionRenderer = ({
} }
> >
<div data-testid="collection-options" className={styles.more}> <div data-testid="collection-options" className={styles.more}>
<MoreHorizontalIcon></MoreHorizontalIcon> <MoreHorizontalIcon />
</div> </div>
</Menu> </Menu>
} }
@ -228,8 +228,8 @@ const CollectionRenderer = ({
<div>{collection.name}</div> <div>{collection.name}</div>
</div> </div>
</MenuItem> </MenuItem>
<Collapsible.Content> <Collapsible.Content className={styles.collapsibleContent}>
<div style={{ marginLeft: 8 }}> <div style={{ marginLeft: 20, marginTop: -4 }}>
{pagesToRender.map(page => { {pagesToRender.map(page => {
return ( return (
<Page <Page

View File

@ -182,20 +182,18 @@ export const Page = ({
> >
{page.title || t['Untitled']()} {page.title || t['Untitled']()}
</MenuItem> </MenuItem>
<Collapsible.Content> <Collapsible.Content className={styles.collapsibleContent}>
<div style={{ marginLeft: 8 }}> {referencesToRender.map(id => {
{referencesToRender.map(id => { return (
return ( <ReferencePage
<ReferencePage key={id}
key={id} workspace={workspace}
workspace={workspace} pageId={id}
pageId={id} metaMapping={allPageMeta}
metaMapping={allPageMeta} parentIds={new Set([pageId])}
parentIds={new Set([pageId])} />
/> );
); })}
})}
</div>
</Collapsible.Content> </Collapsible.Content>
</Collapsible.Root> </Collapsible.Root>
); );

View File

@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css'; import { globalStyle, keyframes, style } from '@vanilla-extract/css';
export const wrapper = style({ export const wrapper = style({
userSelect: 'none', userSelect: 'none',
@ -29,8 +29,9 @@ export const more = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: 4, borderRadius: 2,
padding: 4, fontSize: 16,
color: 'var(--affine-icon-color)',
':hover': { ':hover': {
backgroundColor: 'var(--affine-hover-color)', backgroundColor: 'var(--affine-hover-color)',
}, },
@ -52,3 +53,34 @@ export const menuDividerStyle = style({
height: '1px', height: '1px',
background: 'var(--affine-border-color)', background: 'var(--affine-border-color)',
}); });
const slideDown = keyframes({
'0%': {
height: '0px',
},
'100%': {
height: 'var(--radix-collapsible-content-height)',
},
});
const slideUp = keyframes({
'0%': {
height: 'var(--radix-collapsible-content-height)',
},
'100%': {
height: '0px',
},
});
export const collapsibleContent = style({
overflow: 'hidden',
marginTop: '4px',
selectors: {
'&[data-state="open"]': {
animation: `${slideDown} 0.2s ease-out`,
},
'&[data-state="closed"]': {
animation: `${slideUp} 0.2s ease-out`,
},
},
});

View File

@ -11,11 +11,13 @@ export const label = style({
export const favItemWrapper = style({ export const favItemWrapper = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '4px',
selectors: { selectors: {
'&[data-nested="true"]': { '&[data-nested="true"]': {
marginLeft: '12px', marginLeft: '20px',
width: 'calc(100% - 12px)', width: 'calc(100% - 20px)',
},
'&:not(:first-of-type)': {
marginTop: '4px',
}, },
}, },
}); });
@ -40,6 +42,7 @@ const slideUp = keyframes({
export const collapsibleContent = style({ export const collapsibleContent = style({
overflow: 'hidden', overflow: 'hidden',
marginTop: '4px',
selectors: { selectors: {
'&[data-state="open"]': { '&[data-state="open"]': {
animation: `${slideDown} 0.2s ease-out`, animation: `${slideDown} 0.2s ease-out`,
@ -53,5 +56,4 @@ export const collapsibleContent = style({
export const collapsibleContentInner = style({ export const collapsibleContentInner = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '4px',
}); });

View File

@ -51,14 +51,14 @@ export type RootAppSidebarProps = {
}; };
const RouteMenuLinkItem = React.forwardRef< const RouteMenuLinkItem = React.forwardRef<
HTMLDivElement, HTMLButtonElement,
{ {
currentPath: string; // todo: pass through useRouter? currentPath: string; // todo: pass through useRouter?
path: string; path: string;
icon: ReactElement; icon: ReactElement;
children?: ReactElement; children?: ReactElement;
isDraggedOver?: boolean; isDraggedOver?: boolean;
} & React.HTMLAttributes<HTMLDivElement> } & React.HTMLAttributes<HTMLButtonElement>
>(({ currentPath, path, icon, children, isDraggedOver, ...props }, ref) => { >(({ currentPath, path, icon, children, isDraggedOver, ...props }, ref) => {
// Force active style when a page is dragged over // Force active style when a page is dragged over
const active = isDraggedOver || currentPath === path; const active = isDraggedOver || currentPath === path;
@ -196,6 +196,8 @@ export const RootAppSidebar = ({
</CategoryDivider> </CategoryDivider>
<CollectionsList workspace={blockSuiteWorkspace} /> <CollectionsList workspace={blockSuiteWorkspace} />
<CategoryDivider label={t['others']()} /> <CategoryDivider label={t['others']()} />
{/* fixme: remove the following spacer */}
<div style={{ height: '4px' }} />
<RouteMenuLinkItem <RouteMenuLinkItem
ref={trashDroppable.setNodeRef} ref={trashDroppable.setNodeRef}
isDraggedOver={trashDroppable.isOver} isDraggedOver={trashDroppable.isOver}
@ -211,7 +213,7 @@ export const RootAppSidebar = ({
</SidebarScrollableContainer> </SidebarScrollableContainer>
<SidebarContainer> <SidebarContainer>
{isDesktop && <AppUpdaterButton />} {isDesktop && <AppUpdaterButton />}
<div /> <div style={{ height: '4px' }} />
<AddPageButton onClick={onClickNewPage} /> <AddPageButton onClick={onClickNewPage} />
</SidebarContainer> </SidebarContainer>
</AppSidebar> </AppSidebar>

View File

@ -34,7 +34,7 @@ async function createWindow() {
: isWindows() : isWindows()
? 'hidden' ? 'hidden'
: 'default', : 'default',
trafficLightPosition: { x: 24, y: 18 }, trafficLightPosition: { x: 20, y: 18 },
x: mainWindowState.x, x: mainWindowState.x,
y: mainWindowState.y, y: mainWindowState.y,
width: mainWindowState.width, width: mainWindowState.width,

View File

@ -3,13 +3,16 @@ import { style } from '@vanilla-extract/css';
export const root = style({ export const root = style({
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
minHeight: '16px', minHeight: '16px',
width: 'calc(100% + 6px)',
userSelect: 'none', userSelect: 'none',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
marginBottom: '4px',
padding: '0 8px',
selectors: { selectors: {
'&:not(:first-of-type)': { '&:not(:first-of-type)': {
marginTop: '10px', marginTop: '16px',
}, },
}, },
}); });

View File

@ -52,7 +52,6 @@ export const navStyle = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
zIndex: parseInt(baseTheme.zIndexModal), zIndex: parseInt(baseTheme.zIndexModal),
borderRight: '1px solid transparent',
}); });
export const navHeaderStyle = style({ export const navHeaderStyle = style({
@ -76,6 +75,7 @@ export const navBodyStyle = style({
height: 'calc(100% - 52px)', height: 'calc(100% - 52px)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
rowGap: '4px',
}); });
export const sidebarFloatMaskStyle = style({ export const sidebarFloatMaskStyle = style({

View File

@ -1,15 +1,23 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const linkItemRoot = style({
color: 'inherit',
display: 'contents',
});
export const root = style({ export const root = style({
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
borderRadius: '4px', borderRadius: '4px',
textAlign: 'left',
color: 'inherit',
width: '100%', width: '100%',
minHeight: '30px', minHeight: '30px',
userSelect: 'none', userSelect: 'none',
cursor: 'pointer', cursor: 'pointer',
padding: '0 12px', padding: '0 12px',
fontSize: 'var(--affine-font-sm)', fontSize: 'var(--affine-font-sm)',
margin: '2px 0', marginTop: '4px',
selectors: { selectors: {
'&:hover': { '&:hover': {
background: 'var(--affine-hover-color)', background: 'var(--affine-hover-color)',
@ -29,10 +37,8 @@ export const root = style({
// 'linear-gradient(0deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.04)), rgba(0, 0, 0, 0.04)', // 'linear-gradient(0deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.04)), rgba(0, 0, 0, 0.04)',
// }, // },
'&[data-collapsible="true"]': { '&[data-collapsible="true"]': {
width: 'calc(100% + 8px)',
transform: 'translateX(-8px)',
paddingLeft: '4px', paddingLeft: '4px',
paddingRight: '12px', paddingRight: '4px',
}, },
'&[data-type="collection-list-item"][data-collapsible="false"][data-active="true"],&[data-type="favorite-list-item"][data-collapsible="false"][data-active="true"], &[data-type="favorite-list-item"][data-collapsible="false"]:hover, &[data-type="collection-list-item"][data-collapsible="false"]:hover': '&[data-type="collection-list-item"][data-collapsible="false"][data-active="true"],&[data-type="favorite-list-item"][data-collapsible="false"][data-active="true"], &[data-type="favorite-list-item"][data-collapsible="false"]:hover, &[data-type="collection-list-item"][data-collapsible="false"]:hover':
{ {
@ -41,6 +47,9 @@ export const root = style({
paddingLeft: '20px', paddingLeft: '20px',
paddingRight: '12px', paddingRight: '12px',
}, },
[`${linkItemRoot}:first-of-type &`]: {
marginTop: '0px',
},
}, },
}); });
@ -53,6 +62,12 @@ export const content = style({
export const postfix = style({ export const postfix = style({
justifySelf: 'flex-end', justifySelf: 'flex-end',
display: 'none',
selectors: {
[`${root}:hover &`]: {
display: 'flex',
},
},
}); });
export const icon = style({ export const icon = style({
@ -68,10 +83,15 @@ export const collapsedIconContainer = style({
justifyContent: 'center', justifyContent: 'center',
borderRadius: '2px', borderRadius: '2px',
transition: 'transform 0.2s', transition: 'transform 0.2s',
color: 'inherit',
selectors: { selectors: {
'&[data-collapsed="true"]': { '&[data-collapsed="true"]': {
transform: 'rotate(-90deg)', transform: 'rotate(-90deg)',
}, },
'&[data-disabled="true"]': {
opacity: 0.3,
pointerEvents: 'none',
},
'&:hover': { '&:hover': {
background: 'var(--affine-hover-color)', background: 'var(--affine-hover-color)',
}, },
@ -103,8 +123,3 @@ export const collapsedIcon = style({
export const spacer = style({ export const spacer = style({
flex: 1, flex: 1,
}); });
export const linkItemRoot = style({
color: 'inherit',
display: 'contents',
});

View File

@ -6,11 +6,13 @@ import { Link } from 'react-router-dom';
import * as styles from './index.css'; import * as styles from './index.css';
export interface MenuItemProps extends React.HTMLAttributes<HTMLDivElement> { export interface MenuItemProps extends React.HTMLAttributes<HTMLButtonElement> {
icon?: React.ReactElement; icon?: React.ReactElement;
active?: boolean; active?: boolean;
disabled?: boolean; disabled?: boolean;
collapsed?: boolean; // true, false, undefined. undefined means no collapse // true, false, undefined. undefined means no collapse
collapsed?: boolean;
// if onCollapsedChange is given, but collapsed is undefined, then we will render the collapse button as disabled
onCollapsedChange?: (collapsed: boolean) => void; onCollapsedChange?: (collapsed: boolean) => void;
postfix?: React.ReactElement; postfix?: React.ReactElement;
} }
@ -23,7 +25,7 @@ const stopPropagation: React.MouseEventHandler = e => {
e.stopPropagation(); e.stopPropagation();
}; };
export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>( export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
( (
{ {
onClick, onClick,
@ -38,14 +40,9 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
}, },
ref ref
) => { ) => {
const collapsible = collapsed !== undefined; const collapsible = onCollapsedChange !== undefined;
if (collapsible && !onCollapsedChange) {
throw new Error(
'onCollapsedChange is required when collapsed is defined'
);
}
return ( return (
<div <button
ref={ref} ref={ref}
{...props} {...props}
onClick={onClick} onClick={onClick}
@ -58,6 +55,7 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
<div className={styles.iconsContainer} data-collapsible={collapsible}> <div className={styles.iconsContainer} data-collapsible={collapsible}>
{collapsible && ( {collapsible && (
<div <div
data-disabled={collapsed === undefined ? true : undefined}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); // for links e.preventDefault(); // for links
@ -68,7 +66,7 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
> >
<ArrowDownSmallIcon <ArrowDownSmallIcon
className={styles.collapsedIcon} className={styles.collapsedIcon}
data-collapsed={collapsed} data-collapsed={collapsed !== false}
/> />
</div> </div>
)} )}
@ -84,21 +82,22 @@ export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
{postfix} {postfix}
</div> </div>
) : null} ) : null}
</div> </button>
); );
} }
); );
MenuItem.displayName = 'MenuItem'; MenuItem.displayName = 'MenuItem';
export const MenuLinkItem = React.forwardRef<HTMLDivElement, MenuLinkItemProps>( export const MenuLinkItem = React.forwardRef<
({ to, ...props }, ref) => { HTMLButtonElement,
return ( MenuLinkItemProps
<Link to={to} className={styles.linkItemRoot}> >(({ to, ...props }, ref) => {
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */} return (
{/* Thus ref is passed to MenuItem instead of Link */} <Link to={to} className={styles.linkItemRoot}>
<MenuItem ref={ref} {...props}></MenuItem> {/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}
</Link> {/* Thus ref is passed to MenuItem instead of Link */}
); <MenuItem ref={ref} {...props}></MenuItem>
} </Link>
); );
});
MenuLinkItem.displayName = 'MenuLinkItem'; MenuLinkItem.displayName = 'MenuLinkItem';

View File

@ -12,7 +12,7 @@ export const root = style({
userSelect: 'none', userSelect: 'none',
cursor: 'pointer', cursor: 'pointer',
padding: '0 12px', padding: '0 12px',
margin: '12px 0', margin: '20px 0',
position: 'relative', position: 'relative',
}); });

View File

@ -1,6 +1,7 @@
import { assertExists } from '@blocksuite/global/utils';
import { useAtom, useSetAtom } from 'jotai'; import { useAtom, useSetAtom } from 'jotai';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useCallback, useLayoutEffect, useState } from 'react'; import { useCallback } from 'react';
import { import {
appSidebarOpenAtom, appSidebarOpenAtom,
@ -18,16 +19,10 @@ export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => {
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom); const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
const [isResizing, setIsResizing] = useAtom(appSidebarResizingAtom); const [isResizing, setIsResizing] = useAtom(appSidebarResizingAtom);
const [anchorLeft, setAnchorLeft] = useState(0);
useLayoutEffect(() => {
if (!props.targetElement) return;
const { left } = props.targetElement.getBoundingClientRect();
setAnchorLeft(left);
}, [props.targetElement]);
const onResizeStart = useCallback(() => { const onResizeStart = useCallback(() => {
let resized = false; let resized = false;
assertExists(props.targetElement);
const { left: anchorLeft } = props.targetElement.getBoundingClientRect();
function onMouseMove(e: MouseEvent) { function onMouseMove(e: MouseEvent) {
e.preventDefault(); e.preventDefault();
@ -51,13 +46,7 @@ export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => {
}, },
{ once: true } { once: true }
); );
}, [ }, [props.targetElement, setIsResizing, setSidebarOpen, setWidth]);
anchorLeft,
props.targetElement,
setIsResizing,
setSidebarOpen,
setWidth,
]);
return ( return (
<div <div

View File

@ -4,7 +4,6 @@ export const baseContainer = style({
padding: '4px 16px', padding: '4px 16px',
display: 'flex', display: 'flex',
flexFlow: 'column nowrap', flexFlow: 'column nowrap',
rowGap: '4px',
}); });
export const scrollableContainerRoot = style({ export const scrollableContainerRoot = style({
@ -45,6 +44,7 @@ export const scrollableContainer = style([
baseContainer, baseContainer,
{ {
height: '100%', height: '100%',
padding: '4px 8px',
}, },
]); ]);
@ -69,6 +69,7 @@ export const scrollbarThumb = style({
position: 'relative', position: 'relative',
background: 'var(--affine-black-30)', background: 'var(--affine-black-30)',
borderRadius: '4px', borderRadius: '4px',
overflow: 'hidden',
selectors: { selectors: {
'&::before': { '&::before': {
content: '""', content: '""',

View File

@ -14,6 +14,7 @@ export const avatarImageStyle = style({
height: '100%', height: '100%',
objectFit: 'cover', objectFit: 'cover',
objectPosition: 'center', objectPosition: 'center',
display: 'block',
}); });
const bottomAnimation = keyframes({ const bottomAnimation = keyframes({

View File

@ -252,3 +252,11 @@ affine-block-hub {
padding: 0; padding: 0;
} }
} }
button,
input,
select,
textarea,
[role='button'] {
-webkit-app-region: no-drag;
}

View File

@ -70,6 +70,7 @@ export const scrollbarThumb = style({
position: 'relative', position: 'relative',
background: 'var(--affine-divider-color)', background: 'var(--affine-divider-color)',
width: '50%', width: '50%',
overflow: 'hidden',
borderRadius: '4px', borderRadius: '4px',
':hover': { ':hover': {
background: 'var(--affine-icon-color)', background: 'var(--affine-icon-color)',

View File

@ -53,12 +53,14 @@ test('Show collections items in sidebar', async ({ page }) => {
await first.getByTestId('fav-collapsed-button').click(); await first.getByTestId('fav-collapsed-button').click();
const collectionPage = collections.getByTestId('collection-page').nth(0); const collectionPage = collections.getByTestId('collection-page').nth(0);
expect(await collectionPage.textContent()).toBe('test page'); expect(await collectionPage.textContent()).toBe('test page');
await collectionPage.hover();
await collectionPage.getByTestId('collection-page-options').click(); await collectionPage.getByTestId('collection-page-options').click();
const deletePage = page const deletePage = page
.getByTestId('collection-page-option') .getByTestId('collection-page-option')
.getByText('Delete'); .getByText('Delete');
await deletePage.click(); await deletePage.click();
expect(await collections.getByTestId('collection-page').count()).toBe(0); expect(await collections.getByTestId('collection-page').count()).toBe(0);
await first.hover();
await first.getByTestId('collection-options').click(); await first.getByTestId('collection-options').click();
const deleteCollection = page const deleteCollection = page
.getByTestId('collection-option') .getByTestId('collection-option')
@ -76,6 +78,7 @@ test('pin and unpin collection', async ({ page }) => {
await page.waitForTimeout(50); await page.waitForTimeout(50);
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
await first.hover();
await first.getByTestId('collection-options').click(); await first.getByTestId('collection-options').click();
const deleteCollection = page const deleteCollection = page
.getByTestId('collection-option') .getByTestId('collection-option')
@ -99,6 +102,7 @@ test('edit collection', async ({ page }) => {
const items = collections.getByTestId('collection-item'); const items = collections.getByTestId('collection-item');
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
await first.hover();
await first.getByTestId('collection-options').click(); await first.getByTestId('collection-options').click();
const editCollection = page const editCollection = page
.getByTestId('collection-option') .getByTestId('collection-option')
@ -117,6 +121,7 @@ test('edit collection and change filter date', async ({ page }) => {
const items = collections.getByTestId('collection-item'); const items = collections.getByTestId('collection-item');
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
await first.hover();
await first.getByTestId('collection-options').click(); await first.getByTestId('collection-options').click();
const editCollection = page const editCollection = page
.getByTestId('collection-option') .getByTestId('collection-option')

View File

@ -122,7 +122,7 @@ test("Deleted page's reference will not be shown in sidebar", async ({
'[data-testid="fav-collapsed-button"]' '[data-testid="fav-collapsed-button"]'
); );
await expect(collapseButton).not.toBeVisible(); expect(collapseButton).toHaveAttribute('data-disabled', 'true');
const currentWorkspace = await workspace.current(); const currentWorkspace = await workspace.current();
expect(currentWorkspace.flavour).toContain('local'); expect(currentWorkspace.flavour).toContain('local');