Files
AFFiNE/packages/frontend/component/src/components/resize-panel/resize-panel.tsx
pengx17 a4841bbfa3 feat(core): drop doc onto split view (#9487)
fix AF-2068, AF-2069, AF-1175, AF-2061, AF-2079, AF-2034, AF-2080, AF-1960, AF-2081

1. replace `dnd-kit` with `@atlaskit/pragmatic-drag-and-drop`
2. allow creating split views by drag & drop the following
   a. WorkbenchLinks (route links), like journals, trash, all docs
   b. doc refs
   c. tags/collection
3. style adjustments to split view
4. remove split view's feature flag and make it GA for electron

https://github.com/user-attachments/assets/6a3e4a25-faa2-4215-8eb0-983f44db6e8c
2025-01-08 05:05:33 +00:00

234 lines
6.6 KiB
TypeScript

import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
import { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
import { useTransitionState } from 'react-transition-state';
import { useDropTarget } from '../../ui/dnd';
import { Tooltip, type TooltipProps } from '../../ui/tooltip';
import * as styles from './resize-panel.css';
export interface ResizeHandleProps
extends React.HtmlHTMLAttributes<HTMLDivElement> {
resizing: boolean;
open: boolean;
minWidth: number;
maxWidth: number;
resizeHandlePos: 'left' | 'right';
resizeHandleOffset?: number;
resizeHandleVerticalPadding?: number;
onOpen: (open: boolean) => void;
onResizing: (resizing: boolean) => void;
onWidthChange: (width: number) => void;
tooltip?: TooltipProps['content'];
tooltipShortcut?: TooltipProps['shortcut'];
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
tooltipShortcutClassName?: string;
dropTargetOptions?: Parameters<typeof useDropTarget>[0];
}
export interface ResizePanelProps
extends React.HtmlHTMLAttributes<HTMLDivElement> {
resizing: boolean;
open: boolean;
floating?: boolean;
minWidth: number;
maxWidth: number;
resizeHandlePos: 'left' | 'right';
resizeHandleOffset?: number;
resizeHandleVerticalPadding?: number;
resizeHandleTooltip?: TooltipProps['content'];
resizeHandleTooltipShortcut?: TooltipProps['shortcut'];
resizeHandleTooltipShortcutClassName?: string;
resizeHandleTooltipOptions?: Partial<
Omit<TooltipProps, 'content' | 'shortcut'>
>;
resizeHandleDropTargetOptions?: Parameters<typeof useDropTarget>[0];
enableAnimation?: boolean;
width: number;
unmountOnExit?: boolean;
onOpen: (open: boolean) => void;
onResizing: (resizing: boolean) => void;
onWidthChange: (width: number) => void;
}
const ResizeHandle = ({
className,
resizing,
minWidth,
maxWidth,
resizeHandlePos,
resizeHandleOffset,
resizeHandleVerticalPadding,
dropTargetOptions,
open,
onOpen,
onResizing,
onWidthChange,
tooltip,
tooltipShortcut,
tooltipOptions,
tooltipShortcutClassName,
...rest
}: ResizeHandleProps) => {
const ref = useRef<HTMLDivElement>(null);
const onResizeStart = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
let resized = false;
const panelContainer = ref.current?.parentElement;
if (!panelContainer) return;
// add cursor style to body
document.body.style.cursor = 'col-resize';
const { left: anchorLeft, right: anchorRight } =
panelContainer.getBoundingClientRect();
function onMouseMove(e: MouseEvent) {
e.preventDefault();
if (!panelContainer) return;
const newWidth = Math.min(
maxWidth,
Math.max(
resizeHandlePos === 'right'
? e.clientX - anchorLeft
: anchorRight - e.clientX,
minWidth
)
);
onWidthChange(newWidth);
onResizing(true);
resized = true;
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener(
'mouseup',
() => {
// if not resized, toggle sidebar
if (!resized) {
onOpen(false);
}
onResizing(false);
document.removeEventListener('mousemove', onMouseMove);
document.body.style.cursor = '';
},
{ once: true }
);
},
[maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]
);
const { dropTargetRef } = useDropTarget(dropTargetOptions, [
dropTargetOptions,
]);
return (
<Tooltip
content={tooltip}
shortcut={tooltipShortcut}
shortcutClassName={tooltipShortcutClassName}
{...tooltipOptions}
>
<div
{...rest}
data-testid="resize-handle"
ref={node => {
ref.current = node;
dropTargetRef.current = node;
}}
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>
);
};
const animationTimeout = 300;
export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
function ResizePanel(
{
children,
className,
resizing,
minWidth,
maxWidth,
width,
floating,
enableAnimation = true,
open,
unmountOnExit,
onOpen,
onResizing,
onWidthChange,
resizeHandlePos,
resizeHandleOffset,
resizeHandleVerticalPadding,
resizeHandleTooltip,
resizeHandleTooltipShortcut,
resizeHandleTooltipShortcutClassName,
resizeHandleTooltipOptions,
resizeHandleDropTargetOptions,
...rest
},
ref
) {
const safeWidth = Math.min(maxWidth, Math.max(minWidth, width));
const [{ status }, toggle] = useTransitionState({
timeout: animationTimeout,
});
useLayoutEffect(() => {
toggle(open);
}, [open, toggle]);
return (
<div
{...rest}
ref={ref}
style={assignInlineVars({
[styles.panelWidthVar]: `${safeWidth}px`,
[styles.animationTimeout]: `${animationTimeout}ms`,
})}
className={clsx(className, styles.root)}
data-open={open}
data-transition-state={status}
data-is-floating={floating}
data-handle-position={resizeHandlePos}
data-enable-animation={enableAnimation && !resizing}
>
<div className={styles.content}>
{!(status === 'exited' && unmountOnExit !== false) && children}
</div>
<ResizeHandle
resizeHandlePos={resizeHandlePos}
resizeHandleOffset={resizeHandleOffset}
resizeHandleVerticalPadding={resizeHandleVerticalPadding}
tooltip={resizeHandleTooltip}
tooltipOptions={resizeHandleTooltipOptions}
tooltipShortcut={resizeHandleTooltipShortcut}
tooltipShortcutClassName={resizeHandleTooltipShortcutClassName}
maxWidth={maxWidth}
minWidth={minWidth}
onOpen={onOpen}
onResizing={onResizing}
onWidthChange={onWidthChange}
open={open}
resizing={resizing}
dropTargetOptions={resizeHandleDropTargetOptions}
/>
</div>
);
}
);