feat(core): add tooltips to sidebar resize handle (#8717)
close PD-1865 
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -105,3 +105,8 @@ export const sidebarFloatMaskStyle = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const resizeHandleShortcutStyle = style({
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
marginBottom: '2px',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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>",
|
||||||
|
|||||||
@@ -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>",
|
||||||
|
|||||||
Reference in New Issue
Block a user