feat(core): linked doc visiblity setting and new sidebar layout (#12836)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added a setting to control the visibility of linked document
structures in the sidebar, enabled by default.
- Introduced a "dense" mode for workspace selectors and cards, providing
a more compact display.

- **Improvements**
- Refined sidebar and navigation panel layouts with updated padding,
spacing, and avatar/button sizing for a cleaner and more consistent
appearance.
- Enhanced sidebar appearance settings UI, including new localization
for the linked doc visibility option.
- Updated color theming and spacing in sidebar menu items and quick
search input for better usability.
- Enabled collapsible behavior control for navigation panel tree nodes,
improving user interaction flexibility.

- **Style**
- Adjusted various component styles for improved compactness and
alignment across the sidebar and navigation panels.
- Reduced sizes and padding of buttons and icons for a tidier interface.
- Updated CSS variables and dynamic sizing for workspace cards to
support dense mode.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-06-17 16:09:34 +08:00
committed by GitHub
parent ba718b955a
commit dfe4c22a75
23 changed files with 205 additions and 94 deletions

View File

@@ -16,6 +16,7 @@ export type AppSetting = {
autoCheckUpdate: boolean; autoCheckUpdate: boolean;
autoDownloadUpdate: boolean; autoDownloadUpdate: boolean;
enableTelemetry: boolean; enableTelemetry: boolean;
showLinkedDocInSidebar: boolean;
}; };
export const windowFrameStyleOptions: AppSetting['windowFrameStyle'][] = [ export const windowFrameStyleOptions: AppSetting['windowFrameStyle'][] = [
'frameless', 'frameless',
@@ -33,6 +34,7 @@ const appSettingBaseAtom = atomWithStorage<AppSetting>(
autoCheckUpdate: true, autoCheckUpdate: true,
autoDownloadUpdate: true, autoDownloadUpdate: true,
enableTelemetry: true, enableTelemetry: true,
showLinkedDocInSidebar: true,
}, },
undefined, undefined,
{ {

View File

@@ -5,6 +5,9 @@ export const workspaceAndUserWrapper = style({
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
gap: 8, gap: 8,
width: 'calc(100% + 12px)',
paddingRight: 6,
alignSelf: 'center',
}); });
export const quickSearchAndNewPage = style({ export const quickSearchAndNewPage = style({
display: 'flex', display: 'flex',

View File

@@ -167,6 +167,7 @@ export const RootAppSidebar = memo((): ReactElement => {
showSyncStatus showSyncStatus
open={workspaceSelectorOpen} open={workspaceSelectorOpen}
onOpenChange={onWorkspaceSelectorOpenChange} onOpenChange={onWorkspaceSelectorOpenChange}
dense
/> />
</div> </div>
<UserInfo /> <UserInfo />

View File

@@ -38,8 +38,14 @@ const menuContentOptions: MenuProps['contentOptions'] = {
const AuthorizedUserInfo = ({ account }: { account: AuthAccountInfo }) => { const AuthorizedUserInfo = ({ account }: { account: AuthAccountInfo }) => {
return ( return (
<Menu items={<OperationMenu />} contentOptions={menuContentOptions}> <Menu items={<OperationMenu />} contentOptions={menuContentOptions}>
<IconButton data-testid="sidebar-user-avatar" variant="plain" size="24"> <IconButton
<Avatar size={24} name={account.label} url={account.avatar} /> data-testid="sidebar-user-avatar"
variant="plain"
size="20"
style={{ padding: 0 }}
withoutHover
>
<Avatar size={20} name={account.label} url={account.avatar} />
</IconButton> </IconButton>
</Menu> </Menu>
); );
@@ -57,7 +63,7 @@ const UnauthorizedUserInfo = () => {
onClick={openSignInModal} onClick={openSignInModal}
data-testid="sidebar-user-avatar" data-testid="sidebar-user-avatar"
variant="plain" variant="plain"
size="24" size="20"
> >
<UnknownUserIcon /> <UnknownUserIcon />
</IconButton> </IconButton>

View File

@@ -32,6 +32,8 @@ interface WorkspaceSelectorProps {
disable?: boolean; disable?: boolean;
menuContentOptions?: MenuProps['contentOptions']; menuContentOptions?: MenuProps['contentOptions'];
className?: string; className?: string;
/** if true, will hide cloud/local, and scale the avatar */
dense?: boolean;
} }
export const WorkspaceSelector = ({ export const WorkspaceSelector = ({
@@ -46,6 +48,7 @@ export const WorkspaceSelector = ({
showSyncStatus, showSyncStatus,
className, className,
menuContentOptions, menuContentOptions,
dense,
}: WorkspaceSelectorProps) => { }: WorkspaceSelectorProps) => {
const { workspacesService, globalContextService } = useServices({ const { workspacesService, globalContextService } = useServices({
GlobalContextService, GlobalContextService,
@@ -133,6 +136,7 @@ export const WorkspaceSelector = ({
hideCollaborationIcon={true} hideCollaborationIcon={true}
hideTeamWorkspaceIcon={true} hideTeamWorkspaceIcon={true}
data-testid="current-workspace-card" data-testid="current-workspace-card"
dense={dense}
/> />
) : ( ) : (
<span></span> <span></span>

View File

@@ -174,9 +174,11 @@ const usePauseAnimation = (timeToResume = 5000) => {
const WorkspaceSyncInfo = ({ const WorkspaceSyncInfo = ({
workspaceMetadata, workspaceMetadata,
workspaceProfile, workspaceProfile,
dense,
}: { }: {
workspaceMetadata: WorkspaceMetadata; workspaceMetadata: WorkspaceMetadata;
workspaceProfile: WorkspaceProfileInfo; workspaceProfile: WorkspaceProfileInfo;
dense?: boolean;
}) => { }) => {
const syncStatus = useSyncEngineSyncProgress(workspaceMetadata); const syncStatus = useSyncEngineSyncProgress(workspaceMetadata);
const isCloud = workspaceMetadata.flavour !== 'local'; const isCloud = workspaceMetadata.flavour !== 'local';
@@ -209,15 +211,21 @@ const WorkspaceSyncInfo = ({
} }
return ( return (
<div className={styles.workspaceInfoSlider} data-active={delayActive}> <div
className={styles.workspaceInfoSlider}
data-active={delayActive}
data-dense={dense}
>
<div className={styles.workspaceInfoSlide}> <div className={styles.workspaceInfoSlide}>
<div className={styles.workspaceInfo} data-type="normal"> <div className={styles.workspaceInfo} data-type="normal">
<div className={styles.workspaceName} data-testid="workspace-name"> <div className={styles.workspaceName} data-testid="workspace-name">
{workspaceProfile.name} {workspaceProfile.name}
</div> </div>
<div className={styles.workspaceStatus}> {!dense ? (
{isCloud ? <CloudWorkspaceStatus /> : <LocalWorkspaceStatus />} <div className={styles.workspaceStatus}>
</div> {isCloud ? <CloudWorkspaceStatus /> : <LocalWorkspaceStatus />}
</div>
) : null}
</div> </div>
{/* when syncing/offline/... */} {/* when syncing/offline/... */}
@@ -250,6 +258,7 @@ export const WorkspaceCard = forwardRef<
hideTeamWorkspaceIcon?: boolean; hideTeamWorkspaceIcon?: boolean;
active?: boolean; active?: boolean;
infoClassName?: string; infoClassName?: string;
dense?: boolean;
onClickOpenSettings?: (workspaceMetadata: WorkspaceMetadata) => void; onClickOpenSettings?: (workspaceMetadata: WorkspaceMetadata) => void;
onClickEnableCloud?: (workspaceMetadata: WorkspaceMetadata) => void; onClickEnableCloud?: (workspaceMetadata: WorkspaceMetadata) => void;
} }
@@ -259,7 +268,6 @@ export const WorkspaceCard = forwardRef<
workspaceMetadata, workspaceMetadata,
showSyncStatus, showSyncStatus,
showArrowDownIcon, showArrowDownIcon,
avatarSize = 32,
onClickOpenSettings, onClickOpenSettings,
onClickEnableCloud, onClickEnableCloud,
className, className,
@@ -268,6 +276,8 @@ export const WorkspaceCard = forwardRef<
hideCollaborationIcon, hideCollaborationIcon,
hideTeamWorkspaceIcon, hideTeamWorkspaceIcon,
active, active,
dense,
avatarSize = dense ? 20 : 32,
...props ...props
}, },
ref ref
@@ -301,6 +311,7 @@ export const WorkspaceCard = forwardRef<
<div className={clsx(styles.infoContainer, infoClassName)}> <div className={clsx(styles.infoContainer, infoClassName)}>
{information ? ( {information ? (
<WorkspaceAvatar <WorkspaceAvatar
className={styles.avatar}
meta={workspaceMetadata} meta={workspaceMetadata}
rounded={3} rounded={3}
data-testid="workspace-avatar" data-testid="workspace-avatar"
@@ -317,6 +328,7 @@ export const WorkspaceCard = forwardRef<
<WorkspaceSyncInfo <WorkspaceSyncInfo
workspaceProfile={information} workspaceProfile={information}
workspaceMetadata={workspaceMetadata} workspaceMetadata={workspaceMetadata}
dense={dense}
/> />
) : ( ) : (
<span className={styles.workspaceName}>{information.name}</span> <span className={styles.workspaceName}>{information.name}</span>

View File

@@ -9,11 +9,10 @@ const wsSlideAnim = {
}; };
export const container = style({ export const container = style({
height: '50px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
padding: '0 6px', padding: '4px 6px',
borderRadius: 4, borderRadius: 4,
outline: 'none', outline: 'none',
width: '100%', width: '100%',
@@ -48,8 +47,16 @@ export const disable = style({
}); });
export const workspaceInfoSlider = style({ export const workspaceInfoSlider = style({
height: 42, vars: {
'--h': '42px',
},
height: 'var(--h)',
overflow: 'hidden', overflow: 'hidden',
selectors: {
'&[data-dense="true"]': {
vars: { '--h': '22px' },
},
},
}); });
export const workspaceInfoSlide = style({ export const workspaceInfoSlide = style({
display: 'flex', display: 'flex',
@@ -59,15 +66,18 @@ export const workspaceInfoSlide = style({
transition: `transform ${wsSlideAnim.duration} ${wsSlideAnim.ease} ${wsSlideAnim.delay}`, transition: `transform ${wsSlideAnim.duration} ${wsSlideAnim.ease} ${wsSlideAnim.delay}`,
selectors: { selectors: {
[`.${workspaceInfoSlider}[data-active="true"] &`]: { [`.${workspaceInfoSlider}[data-active="true"] &`]: {
transform: 'translateY(-42px)', transform: 'translateY(calc(var(--h) * -1))',
}, },
}, },
}); });
export const avatar = style({
border: `0.5px solid ${cssVarV2.layer.insideBorder.border}`,
});
export const workspaceInfo = style({ export const workspaceInfo = style({
width: '100%', width: '100%',
flexGrow: 1, flexGrow: 1,
overflow: 'hidden', overflow: 'hidden',
height: 42, height: 'var(--h)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',

View File

@@ -6,6 +6,7 @@ import {
Tooltip, Tooltip,
} from '@affine/component'; } from '@affine/component';
import { Guard } from '@affine/core/components/guard'; import { Guard } from '@affine/core/components/guard';
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
@@ -66,10 +67,12 @@ export const NavigationPanelDocNode = ({
FeatureFlagService, FeatureFlagService,
GuardService, GuardService,
}); });
const { appSettings } = useAppSettingHelper();
const active = const active =
useLiveData(globalContextService.globalContext.docId.$) === docId; useLiveData(globalContextService.globalContext.docId.$) === docId;
const [collapsed, setCollapsed] = useState(true); const [collapsed, setCollapsed] = useState(true);
const isCollapsed = appSettings.showLinkedDocInSidebar ? collapsed : true;
const docRecord = useLiveData(docsService.list.doc$(docId)); const docRecord = useLiveData(docsService.list.doc$(docId));
const DocIcon = useLiveData( const DocIcon = useLiveData(
@@ -94,10 +97,10 @@ export const NavigationPanelDocNode = ({
useMemo( useMemo(
() => () =>
LiveData.from( LiveData.from(
!collapsed ? docsSearchService.watchRefsFrom(docId) : NEVER, !isCollapsed ? docsSearchService.watchRefsFrom(docId) : NEVER,
null null
), ),
[docsSearchService, docId, collapsed] [docsSearchService, docId, isCollapsed]
) )
); );
const searching = children === null; const searching = children === null;
@@ -247,8 +250,9 @@ export const NavigationPanelDocNode = ({
onDrop={handleDropOnDoc} onDrop={handleDropOnDoc}
renameable renameable
extractEmojiAsIcon={enableEmojiIcon} extractEmojiAsIcon={enableEmojiIcon}
collapsed={collapsed} collapsed={isCollapsed}
setCollapsed={setCollapsed} setCollapsed={setCollapsed}
collapsible={appSettings.showLinkedDocInSidebar}
canDrop={handleCanDrop} canDrop={handleCanDrop}
to={`/${docId}`} to={`/${docId}`}
onClick={() => { onClick={() => {
@@ -257,7 +261,7 @@ export const NavigationPanelDocNode = ({
active={active} active={active}
postfix={ postfix={
referencesLoading && referencesLoading &&
!collapsed && ( !isCollapsed && (
<Tooltip <Tooltip
content={t['com.affine.rootAppSidebar.docs.references-loading']()} content={t['com.affine.rootAppSidebar.docs.references-loading']()}
> >
@@ -285,24 +289,26 @@ export const NavigationPanelDocNode = ({
dropEffect={handleDropEffectOnDoc} dropEffect={handleDropEffectOnDoc}
data-testid={`navigation-panel-doc-${docId}`} data-testid={`navigation-panel-doc-${docId}`}
> >
<Guard docId={docId} permission="Doc_Read"> {appSettings.showLinkedDocInSidebar ? (
{canRead => <Guard docId={docId} permission="Doc_Read">
canRead {canRead =>
? children?.map((child, index) => ( canRead
<NavigationPanelDocNode ? children?.map((child, index) => (
key={`${child.docId}-${index}`} <NavigationPanelDocNode
docId={child.docId} key={`${child.docId}-${index}`}
reorderable={false} docId={child.docId}
location={{ reorderable={false}
at: 'navigation-panel:doc:linked-docs', location={{
docId, at: 'navigation-panel:doc:linked-docs',
}} docId,
isLinked }}
/> isLinked
)) />
: null ))
} : null
</Guard> }
</Guard>
) : null}
</NavigationPanelTreeNode> </NavigationPanelTreeNode>
); );
}; };

View File

@@ -15,7 +15,7 @@ export const itemRoot = style({
minHeight: '30px', minHeight: '30px',
userSelect: 'none', userSelect: 'none',
cursor: 'pointer', cursor: 'pointer',
padding: '0 4px', padding: '0 6px',
fontSize: cssVar('fontSm'), fontSize: cssVar('fontSm'),
position: 'relative', position: 'relative',
marginTop: '0px', marginTop: '0px',
@@ -44,6 +44,14 @@ export const itemMain = style({
position: 'relative', position: 'relative',
gap: 12, gap: 12,
}); });
export const toggleIcon = style({
width: 20,
height: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
});
export const itemRenameAnchor = style({ export const itemRenameAnchor = style({
pointerEvents: 'none', pointerEvents: 'none',
position: 'absolute', position: 'absolute',
@@ -84,16 +92,26 @@ export const iconContainer = style({
height: 20, height: 20,
color: cssVarV2('icon/primary'), color: cssVarV2('icon/primary'),
fontSize: 20, fontSize: 20,
position: 'absolute',
selectors: {
[`${itemRoot}[data-collapsible="true"]:hover &`]: {
opacity: 0,
pointerEvents: 'none',
},
},
}); });
export const collapsedIconContainer = style({ export const collapsedIconContainer = style({
width: '16px', width: '20px',
height: '16px', height: '20px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: '2px', borderRadius: '2px',
transition: 'transform 0.2s', transition: 'transform 0.2s',
color: cssVarV2('icon/primary'), color: cssVarV2('icon/primary'),
position: 'absolute',
opacity: 0,
pointerEvents: 'none',
selectors: { selectors: {
'&[data-collapsed="true"]': { '&[data-collapsed="true"]': {
transform: 'rotate(-90deg)', transform: 'rotate(-90deg)',
@@ -105,10 +123,15 @@ export const collapsedIconContainer = style({
'&:hover': { '&:hover': {
background: cssVar('hoverColor'), background: cssVar('hoverColor'),
}, },
[`${itemRoot}[data-collapsible="true"]:hover &`]: {
opacity: 1,
pointerEvents: 'initial',
},
}, },
}); });
export const collapsedIcon = style({ export const collapsedIcon = style({
transition: 'transform 0.2s ease-in-out', transition: 'transform 0.2s ease-in-out',
fontSize: 16,
selectors: { selectors: {
'&[data-collapsed="true"]': { '&[data-collapsed="true"]': {
transform: 'rotate(-90deg)', transform: 'rotate(-90deg)',

View File

@@ -66,6 +66,7 @@ export interface BaseNavigationPanelTreeNodeProps {
extractEmojiAsIcon?: boolean; extractEmojiAsIcon?: boolean;
collapsed: boolean; collapsed: boolean;
setCollapsed: (collapsed: boolean) => void; setCollapsed: (collapsed: boolean) => void;
collapsible?: boolean;
disabled?: boolean; disabled?: boolean;
onClick?: () => void; onClick?: () => void;
to?: To; to?: To;
@@ -140,6 +141,7 @@ export const NavigationPanelTreeNode = ({
collapsed, collapsed,
extractEmojiAsIcon, extractEmojiAsIcon,
setCollapsed, setCollapsed,
collapsible = true,
canDrop, canDrop,
reorderable = true, reorderable = true,
operations = [], operations = [],
@@ -226,7 +228,7 @@ export const NavigationPanelTreeNode = ({
return; return;
} }
onDrop?.(data); onDrop?.(data);
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child' && collapsible) {
setCollapsed(false); setCollapsed(false);
} }
}, },
@@ -242,6 +244,7 @@ export const NavigationPanelTreeNode = ({
handleCanDrop, handleCanDrop,
cid, cid,
onDrop, onDrop,
collapsible,
setCollapsed, setCollapsed,
] ]
); );
@@ -253,6 +256,7 @@ export const NavigationPanelTreeNode = ({
treeInstruction?.type === 'make-child' && treeInstruction?.type === 'make-child' &&
!isSelfDraggedOver !isSelfDraggedOver
) { ) {
if (!collapsible) return;
// auto expand when dragged over // auto expand when dragged over
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setCollapsed(false); setCollapsed(false);
@@ -260,7 +264,13 @@ export const NavigationPanelTreeNode = ({
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
} }
return; return;
}, [draggedOver, isSelfDraggedOver, setCollapsed, treeInstruction?.type]); }, [
collapsible,
draggedOver,
isSelfDraggedOver,
setCollapsed,
treeInstruction?.type,
]);
useEffect(() => { useEffect(() => {
if (rootRef.current) { if (rootRef.current) {
@@ -346,9 +356,10 @@ export const NavigationPanelTreeNode = ({
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); // for links e.preventDefault(); // for links
if (!collapsible) return;
setCollapsed(!collapsed); setCollapsed(!collapsed);
}, },
[collapsed, setCollapsed] [collapsed, collapsible, setCollapsed]
); );
const handleRename = useCallback( const handleRename = useCallback(
@@ -365,11 +376,11 @@ export const NavigationPanelTreeNode = ({
} }
if (!clickForCollapse) { if (!clickForCollapse) {
onClick?.(); onClick?.();
} else { } else if (collapsible) {
setCollapsed(!collapsed); setCollapsed(!collapsed);
} }
}, },
[clickForCollapse, collapsed, onClick, setCollapsed] [clickForCollapse, collapsed, collapsible, onClick, setCollapsed]
); );
const content = ( const content = (
@@ -378,20 +389,20 @@ export const NavigationPanelTreeNode = ({
className={styles.itemRoot} className={styles.itemRoot}
data-active={active} data-active={active}
data-disabled={disabled} data-disabled={disabled}
data-collapsible={collapsible}
> >
<div <div className={styles.toggleIcon}>
data-disabled={disabled} <div
onClick={handleCollapsedChange} data-disabled={disabled}
data-testid="navigation-panel-collapsed-button" onClick={handleCollapsedChange}
className={styles.collapsedIconContainer} data-testid="navigation-panel-collapsed-button"
> className={styles.collapsedIconContainer}
<ArrowDownSmallIcon >
className={styles.collapsedIcon} <ArrowDownSmallIcon
data-collapsed={collapsed !== false} className={styles.collapsedIcon}
/> data-collapsed={collapsed !== false}
</div> />
</div>
<div className={styles.itemMain}>
<div className={styles.iconContainer}> <div className={styles.iconContainer}>
{emoji ?? {emoji ??
(Icon && ( (Icon && (
@@ -402,6 +413,9 @@ export const NavigationPanelTreeNode = ({
/> />
))} ))}
</div> </div>
</div>
<div className={styles.itemMain}>
<div className={styles.itemContent}>{name}</div> <div className={styles.itemContent}>{name}</div>
{postfix} {postfix}
<div <div

View File

@@ -139,10 +139,10 @@ export const AppearanceSettings = () => {
</SettingWrapper> </SettingWrapper>
) : null} ) : null}
{BUILD_CONFIG.isElectron ? ( <SettingWrapper
<SettingWrapper title={t['com.affine.appearanceSettings.sidebar.title']()}
title={t['com.affine.appearanceSettings.sidebar.title']()} >
> {BUILD_CONFIG.isElectron ? (
<SettingRow <SettingRow
name={t['com.affine.appearanceSettings.noisyBackground.title']()} name={t['com.affine.appearanceSettings.noisyBackground.title']()}
desc={t[ desc={t[
@@ -156,23 +156,38 @@ export const AppearanceSettings = () => {
} }
/> />
</SettingRow> </SettingRow>
{environment.isMacOs && ( ) : null}
<SettingRow {BUILD_CONFIG.isElectron && environment.isMacOs && (
name={t['com.affine.appearanceSettings.translucentUI.title']()} <SettingRow
desc={t[ name={t['com.affine.appearanceSettings.translucentUI.title']()}
'com.affine.appearanceSettings.translucentUI.description' desc={t[
]()} 'com.affine.appearanceSettings.translucentUI.description'
> ]()}
<Switch >
checked={appSettings.enableBlurBackground} <Switch
onChange={checked => checked={appSettings.enableBlurBackground}
updateSettings('enableBlurBackground', checked) onChange={checked =>
} updateSettings('enableBlurBackground', checked)
/> }
</SettingRow> />
)} </SettingRow>
</SettingWrapper> )}
) : null} <SettingRow
name={t[
'com.affine.appearanceSettings.showLinkedDocInSidebar.title'
]()}
desc={t[
'com.affine.appearanceSettings.showLinkedDocInSidebar.description'
]()}
>
<Switch
checked={appSettings.showLinkedDocInSidebar}
onChange={checked =>
updateSettings('showLinkedDocInSidebar', checked)
}
/>
</SettingRow>
</SettingWrapper>
{BUILD_CONFIG.isElectron ? <MenubarSetting /> : null} {BUILD_CONFIG.isElectron ? <MenubarSetting /> : null}
</> </>

View File

@@ -3,9 +3,9 @@ import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const root = style({ export const root = style({
width: 28, width: 20,
height: 28, height: 20,
borderRadius: 8, borderRadius: 4,
boxShadow: cssVar('buttonShadow'), boxShadow: cssVar('buttonShadow'),
borderWidth: 0, borderWidth: 0,
background: cssVarV2('button/siderbarPrimary/background'), background: cssVarV2('button/siderbarPrimary/background'),

View File

@@ -184,6 +184,7 @@ function AddPageWithoutAsk({ className, style }: AddPageButtonProps) {
data-testid="sidebar-new-page-button" data-testid="sidebar-new-page-button"
style={style} style={style}
className={clsx([styles.root, className])} className={clsx([styles.root, className])}
size={16}
onClick={onClickNewPage} onClick={onClickNewPage}
onAuxClick={onClickNewPage} onAuxClick={onClickNewPage}
> >

View File

@@ -67,7 +67,7 @@ export const navStyle = style({
export const navHeaderStyle = style({ export const navHeaderStyle = style({
flex: '0 0 auto', flex: '0 0 auto',
height: '52px', height: '52px',
padding: '0px 16px', padding: '0px 8px',
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',

View File

@@ -223,7 +223,7 @@ export function FallbackHeaderWithWorkspaceNavigator() {
return ( return (
<div className={styles.fallbackHeader}> <div className={styles.fallbackHeader}>
{currentWorkspace && navigate ? ( {currentWorkspace && navigate ? (
<WorkspaceNavigator showSyncStatus showEnableCloudButton /> <WorkspaceNavigator showSyncStatus showEnableCloudButton dense />
) : ( ) : (
<FallbackHeaderSkeleton /> <FallbackHeaderSkeleton />
)} )}

View File

@@ -14,20 +14,20 @@ export const root = style({
minHeight: '30px', minHeight: '30px',
userSelect: 'none', userSelect: 'none',
cursor: 'pointer', cursor: 'pointer',
padding: '0 2px 0 12px', padding: '0 2px 0 0',
fontSize: cssVar('fontSm'), fontSize: cssVar('fontSm'),
marginTop: '4px', marginTop: '4px',
position: 'relative', position: 'relative',
selectors: { selectors: {
'&:hover': { '&:hover': {
background: cssVar('hoverColor'), background: cssVarV2.layer.background.hoverOverlay,
}, },
'&[data-active="true"]': { '&[data-active="true"]': {
background: cssVar('hoverColor'), background: cssVarV2.layer.background.hoverOverlay,
}, },
'&[data-disabled="true"]': { '&[data-disabled="true"]': {
cursor: 'default', cursor: 'default',
color: cssVar('textSecondaryColor'), color: cssVarV2.text.disable,
pointerEvents: 'none', pointerEvents: 'none',
}, },
// this is not visible in dark mode // this is not visible in dark mode
@@ -43,7 +43,7 @@ export const root = style({
'&[data-collapsible="false"]:is([data-active="true"], :hover)': { '&[data-collapsible="false"]:is([data-active="true"], :hover)': {
width: 'calc(100% + 8px + 8px)', width: 'calc(100% + 8px + 8px)',
transform: 'translateX(-8px)', transform: 'translateX(-8px)',
paddingLeft: '20px', paddingLeft: '8px',
paddingRight: '10px', paddingRight: '10px',
}, },
[`${linkItemRoot}:first-of-type &`]: { [`${linkItemRoot}:first-of-type &`]: {
@@ -93,7 +93,7 @@ export const collapsedIconContainer = style({
pointerEvents: 'none', pointerEvents: 'none',
}, },
'&:hover': { '&:hover': {
background: cssVar('hoverColor'), background: cssVarV2.layer.background.hoverOverlay,
}, },
}, },
}); });
@@ -101,7 +101,7 @@ export const iconsContainer = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-start', justifyContent: 'flex-start',
width: '28px', width: '32px',
flexShrink: 0, flexShrink: 0,
selectors: { selectors: {
'&[data-collapsible="true"]': { '&[data-collapsible="true"]': {

View File

@@ -27,6 +27,10 @@ const stopPropagation: React.MouseEventHandler = e => {
e.stopPropagation(); e.stopPropagation();
}; };
/**
* This component is not a generic component.
* It is used for the app sidebar.
*/
export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>( export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
( (
{ {

View File

@@ -11,7 +11,7 @@ export const root = style({
height: '30px', height: '30px',
userSelect: 'none', userSelect: 'none',
cursor: 'pointer', cursor: 'pointer',
padding: '0 12px 0 20px', padding: '0 12px 0 8px',
position: 'relative', position: 'relative',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
overflow: 'hidden', overflow: 'hidden',
@@ -20,7 +20,7 @@ export const root = style({
}, },
}); });
export const icon = style({ export const icon = style({
marginRight: '8px', marginRight: '12px',
color: cssVarV2('icon/primary'), color: cssVarV2('icon/primary'),
fontSize: '20px', fontSize: '20px',
}); });

View File

@@ -1,7 +1,7 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { globalStyle, style } from '@vanilla-extract/css'; import { globalStyle, style } from '@vanilla-extract/css';
export const baseContainer = style({ export const baseContainer = style({
padding: '4px 16px', padding: '4px 14px',
display: 'flex', display: 'flex',
flexFlow: 'column nowrap', flexFlow: 'column nowrap',
':empty': { ':empty': {

View File

@@ -19,7 +19,7 @@ export const header = style({
alignItems: 'center', alignItems: 'center',
flexShrink: 0, flexShrink: 0,
background: cssVar('backgroundPrimaryColor'), background: cssVar('backgroundPrimaryColor'),
padding: '0 16px', padding: '0 16px 0px 8px',
contain: 'strict', contain: 'strict',
'@media': { '@media': {
print: { print: {

View File

@@ -14,7 +14,7 @@
"it-IT": 100, "it-IT": 100,
"it": 1, "it": 1,
"ja": 100, "ja": 100,
"ko": 54, "ko": 53,
"pl": 100, "pl": 100,
"pt-BR": 100, "pt-BR": 100,
"ru": 100, "ru": 100,

View File

@@ -1003,6 +1003,14 @@ export function useAFFiNEI18N(): {
* `Translucent UI on the sidebar` * `Translucent UI on the sidebar`
*/ */
["com.affine.appearanceSettings.translucentUI.title"](): string; ["com.affine.appearanceSettings.translucentUI.title"](): string;
/**
* `Show linked doc in sidebar`
*/
["com.affine.appearanceSettings.showLinkedDocInSidebar.title"](): string;
/**
* `Control whether to show the structure of linked docs in the sidebar.`
*/
["com.affine.appearanceSettings.showLinkedDocInSidebar.description"](): string;
/** /**
* `Your current email is {{email}}. We'll send a temporary verification link to this email.` * `Your current email is {{email}}. We'll send a temporary verification link to this email.`
*/ */

View File

@@ -240,6 +240,8 @@
"com.affine.appearanceSettings.title": "Appearance settings", "com.affine.appearanceSettings.title": "Appearance settings",
"com.affine.appearanceSettings.translucentUI.description": "Use transparency effect on the sidebar.", "com.affine.appearanceSettings.translucentUI.description": "Use transparency effect on the sidebar.",
"com.affine.appearanceSettings.translucentUI.title": "Translucent UI on the sidebar", "com.affine.appearanceSettings.translucentUI.title": "Translucent UI on the sidebar",
"com.affine.appearanceSettings.showLinkedDocInSidebar.title": "Show linked doc in sidebar",
"com.affine.appearanceSettings.showLinkedDocInSidebar.description": "Control whether to show the structure of linked docs in the sidebar.",
"com.affine.auth.change.email.message": "Your current email is {{email}}. We'll send a temporary verification link to this email.", "com.affine.auth.change.email.message": "Your current email is {{email}}. We'll send a temporary verification link to this email.",
"com.affine.auth.change.email.page.subtitle": "Please enter your new email address below. We will send a verification link to this email address to complete the process.", "com.affine.auth.change.email.page.subtitle": "Please enter your new email address below. We will send a verification link to this email address to complete the process.",
"com.affine.auth.change.email.page.success.subtitle": "Congratulations! You have successfully updated the email address associated with your AFFiNE Cloud account.", "com.affine.auth.change.email.page.success.subtitle": "Congratulations! You have successfully updated the email address associated with your AFFiNE Cloud account.",