feat(core): add tooltips to sidebar resize handle (#8717)

close PD-1865
![CleanShot 2024-11-06 at 12 52 35@2x](https://github.com/user-attachments/assets/b7353448-f8c5-4cfb-bfed-d1acff2923c4)
This commit is contained in:
JimmFly
2024-11-06 06:19:38 +00:00
parent 584d095895
commit 59264b9996
7 changed files with 89 additions and 32 deletions

View File

@@ -3,6 +3,7 @@ import clsx from 'clsx';
import { forwardRef, useCallback, useLayoutEffect, useRef } from 'react'; import { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
import { useTransition } from 'react-transition-state'; import { useTransition } from 'react-transition-state';
import { Tooltip, type TooltipProps } from '../../ui/tooltip';
import * as styles from './resize-panel.css'; import * as styles from './resize-panel.css';
export interface ResizeHandleProps export interface ResizeHandleProps
@@ -17,6 +18,10 @@ export interface ResizeHandleProps
onOpen: (open: boolean) => void; onOpen: (open: boolean) => void;
onResizing: (resizing: boolean) => void; onResizing: (resizing: boolean) => void;
onWidthChange: (width: number) => void; onWidthChange: (width: number) => void;
tooltip?: TooltipProps['content'];
tooltipShortcut?: TooltipProps['shortcut'];
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
tooltipShortcutClassName?: string;
} }
export interface ResizePanelProps export interface ResizePanelProps
@@ -29,6 +34,12 @@ export interface ResizePanelProps
resizeHandlePos: 'left' | 'right'; resizeHandlePos: 'left' | 'right';
resizeHandleOffset?: number; resizeHandleOffset?: number;
resizeHandleVerticalPadding?: number; resizeHandleVerticalPadding?: number;
resizeHandleTooltip?: TooltipProps['content'];
resizeHandleTooltipShortcut?: TooltipProps['shortcut'];
resizeHandleTooltipShortcutClassName?: string;
resizeHandleTooltipOptions?: Partial<
Omit<TooltipProps, 'content' | 'shortcut'>
>;
enableAnimation?: boolean; enableAnimation?: boolean;
width: number; width: number;
unmountOnExit?: boolean; unmountOnExit?: boolean;
@@ -49,6 +60,10 @@ const ResizeHandle = ({
onOpen, onOpen,
onResizing, onResizing,
onWidthChange, onWidthChange,
tooltip,
tooltipShortcut,
tooltipOptions,
tooltipShortcutClassName,
...rest ...rest
}: ResizeHandleProps) => { }: ResizeHandleProps) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@@ -101,24 +116,31 @@ const ResizeHandle = ({
); );
return ( return (
<div <Tooltip
{...rest} content={tooltip}
data-testid="resize-handle" shortcut={tooltipShortcut}
ref={ref} shortcutClassName={tooltipShortcutClassName}
style={assignInlineVars({ {...tooltipOptions}
[styles.resizeHandleOffsetVar]: `${resizeHandleOffset ?? 0}px`,
[styles.resizeHandleVerticalPadding]: `${
resizeHandleVerticalPadding ?? 0
}px`,
})}
className={clsx(styles.resizeHandleContainer, className)}
data-handle-position={resizeHandlePos}
data-resizing={resizing}
data-open={open}
onMouseDown={onResizeStart}
> >
<div className={styles.resizerInner} /> <div
</div> {...rest}
data-testid="resize-handle"
ref={ref}
style={assignInlineVars({
[styles.resizeHandleOffsetVar]: `${resizeHandleOffset ?? 0}px`,
[styles.resizeHandleVerticalPadding]: `${
resizeHandleVerticalPadding ?? 0
}px`,
})}
className={clsx(styles.resizeHandleContainer, className)}
data-handle-position={resizeHandlePos}
data-resizing={resizing}
data-open={open}
onMouseDown={onResizeStart}
>
<div className={styles.resizerInner} />
</div>
</Tooltip>
); );
}; };
@@ -143,6 +165,10 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
resizeHandlePos, resizeHandlePos,
resizeHandleOffset, resizeHandleOffset,
resizeHandleVerticalPadding, resizeHandleVerticalPadding,
resizeHandleTooltip,
resizeHandleTooltipShortcut,
resizeHandleTooltipShortcutClassName,
resizeHandleTooltipOptions,
...rest ...rest
}, },
ref ref
@@ -176,6 +202,10 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
resizeHandlePos={resizeHandlePos} resizeHandlePos={resizeHandlePos}
resizeHandleOffset={resizeHandleOffset} resizeHandleOffset={resizeHandleOffset}
resizeHandleVerticalPadding={resizeHandleVerticalPadding} resizeHandleVerticalPadding={resizeHandleVerticalPadding}
tooltip={resizeHandleTooltip}
tooltipOptions={resizeHandleTooltipOptions}
tooltipShortcut={resizeHandleTooltipShortcut}
tooltipShortcutClassName={resizeHandleTooltipShortcutClassName}
maxWidth={maxWidth} maxWidth={maxWidth}
minWidth={minWidth} minWidth={minWidth}
onOpen={onOpen} onOpen={onOpen}

View File

@@ -40,15 +40,22 @@ export interface TooltipProps {
rootOptions?: Omit<RootProps, 'children'>; rootOptions?: Omit<RootProps, 'children'>;
portalOptions?: TooltipPortalProps; portalOptions?: TooltipPortalProps;
options?: Omit<TooltipContentProps, 'side' | 'align'>; options?: Omit<TooltipContentProps, 'side' | 'align'>;
shortcutClassName?: string;
} }
const TooltipShortcut = ({ shortcut }: { shortcut: string | string[] }) => { const TooltipShortcut = ({
shortcut,
className,
}: {
shortcut: string | string[];
className?: string;
}) => {
const commands = (Array.isArray(shortcut) ? shortcut : [shortcut]) const commands = (Array.isArray(shortcut) ? shortcut : [shortcut])
.map(cmd => cmd.trim()) .map(cmd => cmd.trim())
.map(cmd => getCommand(cmd)); .map(cmd => getCommand(cmd));
return ( return (
<div className={styles.shortcut}> <div className={clsx(styles.shortcut, className)}>
{commands.map((cmd, index) => ( {commands.map((cmd, index) => (
<div <div
key={`${index}-${cmd}`} key={`${index}-${cmd}`}
@@ -71,6 +78,7 @@ export const Tooltip = ({
options, options,
rootOptions, rootOptions,
portalOptions, portalOptions,
shortcutClassName,
}: TooltipProps) => { }: TooltipProps) => {
if (!content) { if (!content) {
return children; return children;
@@ -94,7 +102,10 @@ export const Tooltip = ({
{shortcut ? ( {shortcut ? (
<div className={styles.withShortcut}> <div className={styles.withShortcut}>
<div className={styles.withShortcutContent}>{content}</div> <div className={styles.withShortcutContent}>{content}</div>
<TooltipShortcut shortcut={shortcut} /> <TooltipShortcut
shortcut={shortcut}
className={shortcutClassName}
/>
</div> </div>
) : ( ) : (
content content

View File

@@ -105,3 +105,8 @@ export const sidebarFloatMaskStyle = style({
}, },
}, },
}); });
export const resizeHandleShortcutStyle = style({
alignItems: 'flex-end',
marginBottom: '2px',
});

View File

@@ -3,6 +3,7 @@ import { ResizePanel } from '@affine/component/resize-panel';
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper'; import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
import { WorkspaceNavigator } from '@affine/core/components/workspace-selector'; import { WorkspaceNavigator } from '@affine/core/components/workspace-selector';
import { useI18n } from '@affine/i18n';
import { import {
useLiveData, useLiveData,
useService, useService,
@@ -23,6 +24,7 @@ import {
navHeaderStyle, navHeaderStyle,
navStyle, navStyle,
navWrapperStyle, navWrapperStyle,
resizeHandleShortcutStyle,
sidebarFloatMaskStyle, sidebarFloatMaskStyle,
} from './index.css'; } from './index.css';
import { SidebarHeader } from './sidebar-header'; import { SidebarHeader } from './sidebar-header';
@@ -170,6 +172,13 @@ export function AppSidebar({ children }: PropsWithChildren) {
})} })}
resizeHandleOffset={0} resizeHandleOffset={0}
resizeHandleVerticalPadding={clientBorder ? 16 : 0} resizeHandleVerticalPadding={clientBorder ? 16 : 0}
resizeHandleTooltip={<ResizeHandleTooltipContent />}
resizeHandleTooltipOptions={{
side: 'right',
align: 'center',
}}
resizeHandleTooltipShortcut={['$mod', '/']}
resizeHandleTooltipShortcutClassName={resizeHandleShortcutStyle}
data-transparent data-transparent
data-open={sidebarState !== 'close'} data-open={sidebarState !== 'close'}
data-has-border={hasRightBorder} data-has-border={hasRightBorder}
@@ -198,6 +207,16 @@ export function AppSidebar({ children }: PropsWithChildren) {
); );
} }
const ResizeHandleTooltipContent = () => {
const t = useI18n();
return (
<div>
<div>{t['com.affine.rootAppSidebar.resize-handle.tooltip.drag']()}</div>
<div>{t['com.affine.rootAppSidebar.resize-handle.tooltip.click']()}</div>
</div>
);
};
export function FallbackHeader() { export function FallbackHeader() {
return ( return (
<div className={styles.fallbackHeader}> <div className={styles.fallbackHeader}>

View File

@@ -1,5 +1,4 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { SidebarIcon } from '@blocksuite/icons/rc'; import { SidebarIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -45,11 +44,6 @@ export const SidebarSwitch = ({
appSidebarService.toggleSidebar(); appSidebarService.toggleSidebar();
}, [appSidebarService, open]); }, [appSidebarService, open]);
const t = useI18n();
const tooltipContent = open
? t['com.affine.sidebarSwitch.collapse']()
: t['com.affine.sidebarSwitch.expand']();
return ( return (
<div <div
ref={switchRef} ref={switchRef}
@@ -59,12 +53,6 @@ export const SidebarSwitch = ({
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
> >
<IconButton <IconButton
tooltip={tooltipContent}
tooltipShortcut={['$mod', '/']}
tooltipOptions={{
side: open ? 'bottom' : 'right',
rootOptions: { delayDuration: 700 },
}}
className={className} className={className}
size="24" size="24"
style={{ style={{

View File

@@ -999,6 +999,8 @@
"com.affine.rootAppSidebar.tags.empty.new-tag-button": "New tag", "com.affine.rootAppSidebar.tags.empty.new-tag-button": "New tag",
"com.affine.rootAppSidebar.tags.new-tag": "New tag", "com.affine.rootAppSidebar.tags.new-tag": "New tag",
"com.affine.rootAppSidebar.tags.no-doc": "No docs", "com.affine.rootAppSidebar.tags.no-doc": "No docs",
"com.affine.rootAppSidebar.resize-handle.tooltip.drag": "Drag to resize",
"com.affine.rootAppSidebar.resize-handle.tooltip.click": "Click to collapse",
"com.affine.search-tags.placeholder": "Type here ...", "com.affine.search-tags.placeholder": "Type here ...",
"com.affine.selectPage.empty": "Empty", "com.affine.selectPage.empty": "Empty",
"com.affine.selectPage.empty.tips": "No doc titles contain <1>{{search}}</1>", "com.affine.selectPage.empty.tips": "No doc titles contain <1>{{search}}</1>",

View File

@@ -977,6 +977,8 @@
"com.affine.rootAppSidebar.tags.empty.new-tag-button": "新建标签", "com.affine.rootAppSidebar.tags.empty.new-tag-button": "新建标签",
"com.affine.rootAppSidebar.tags.new-tag": "新建标签", "com.affine.rootAppSidebar.tags.new-tag": "新建标签",
"com.affine.rootAppSidebar.tags.no-doc": "没有文档", "com.affine.rootAppSidebar.tags.no-doc": "没有文档",
"com.affine.rootAppSidebar.resize-handle.tooltip.drag": "拖动调整宽度",
"com.affine.rootAppSidebar.resize-handle.tooltip.click": "点击收起侧栏",
"com.affine.search-tags.placeholder": "在这输入...", "com.affine.search-tags.placeholder": "在这输入...",
"com.affine.selectPage.empty": "选择页面为空", "com.affine.selectPage.empty": "选择页面为空",
"com.affine.selectPage.empty.tips": "没有文档标题包含 <1>{{search}}</1>", "com.affine.selectPage.empty.tips": "没有文档标题包含 <1>{{search}}</1>",