Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
248 lines
6.9 KiB
TypeScript
248 lines
6.9 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;
|
|
onWidthChanged?: (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;
|
|
onWidthChanged?: (width: number) => void;
|
|
}
|
|
|
|
const ResizeHandle = ({
|
|
className,
|
|
resizing,
|
|
minWidth,
|
|
maxWidth,
|
|
resizeHandlePos,
|
|
resizeHandleOffset,
|
|
resizeHandleVerticalPadding,
|
|
dropTargetOptions,
|
|
open,
|
|
onOpen,
|
|
onResizing,
|
|
onWidthChange,
|
|
onWidthChanged,
|
|
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();
|
|
|
|
let lastWidth: number;
|
|
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
|
|
)
|
|
);
|
|
lastWidth = newWidth;
|
|
onWidthChange(newWidth);
|
|
onResizing(true);
|
|
resized = true;
|
|
}
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener(
|
|
'mouseup',
|
|
() => {
|
|
// if not resized, toggle sidebar
|
|
if (!resized) {
|
|
onOpen(false);
|
|
}
|
|
onResizing(false);
|
|
lastWidth && onWidthChanged?.(lastWidth);
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.body.style.cursor = '';
|
|
},
|
|
{ once: true }
|
|
);
|
|
},
|
|
[
|
|
maxWidth,
|
|
resizeHandlePos,
|
|
minWidth,
|
|
onWidthChange,
|
|
onResizing,
|
|
onWidthChanged,
|
|
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,
|
|
onWidthChanged,
|
|
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}
|
|
onWidthChanged={onWidthChanged}
|
|
open={open}
|
|
resizing={resizing}
|
|
dropTargetOptions={resizeHandleDropTargetOptions}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
);
|