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:
@@ -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,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ export const RootAppSidebar = memo((): ReactElement => {
|
|||||||
showSyncStatus
|
showSyncStatus
|
||||||
open={workspaceSelectorOpen}
|
open={workspaceSelectorOpen}
|
||||||
onOpenChange={onWorkspaceSelectorOpenChange}
|
onOpenChange={onWorkspaceSelectorOpenChange}
|
||||||
|
dense
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<UserInfo />
|
<UserInfo />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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"]': {
|
||||||
|
|||||||
@@ -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>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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': {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
Reference in New Issue
Block a user