Files
SRCmail/components/layout/resize-handle.tsx
T
Linus Rath ab72fc06ff feat: add Stalwart account security management
- Add Stalwart API client library (lib/stalwart/client.ts)
- Add server-side proxy routes for auth, crypto, password, principal, probe
- Add account security Zustand store with full state management
- Add Security settings tab with password change, display name, TOTP 2FA,
  app passwords, and encryption-at-rest controls
- Add stalwartFeaturesEnabled config flag (opt-out via STALWART_FEATURES=false)
- Add i18n translations for all 8 locales (en, de, es, fr, it, ja, nl, pt)
- Add tests for Stalwart client (24 tests) and security store (29 tests)
2026-03-12 02:20:56 +01:00

81 lines
2.4 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface ResizeHandleProps {
onResizeStart?: () => void;
onResize: (delta: number) => void;
onResizeEnd?: () => void;
onDoubleClick?: () => void;
className?: string;
}
const KEYBOARD_STEP = 10;
export function ResizeHandle({ onResizeStart, onResize, onResizeEnd, onDoubleClick, className }: ResizeHandleProps) {
const isDragging = useRef(false);
const startX = useRef(0);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
isDragging.current = true;
startX.current = e.clientX;
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
onResizeStart?.();
}, [onResizeStart]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
let delta = 0;
if (e.key === "ArrowLeft") delta = -KEYBOARD_STEP;
else if (e.key === "ArrowRight") delta = KEYBOARD_STEP;
else return;
e.preventDefault();
onResize(delta);
onResizeEnd?.();
}, [onResize, onResizeEnd]);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging.current) return;
const delta = e.clientX - startX.current;
onResize(delta);
};
const handleMouseUp = () => {
if (!isDragging.current) return;
isDragging.current = false;
document.body.style.cursor = "";
document.body.style.userSelect = "";
onResizeEnd?.();
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [onResize, onResizeEnd]);
return (
<div
role="separator"
aria-orientation="vertical"
aria-label="Resize"
tabIndex={0}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
onDoubleClick={onDoubleClick}
className={cn(
"w-1 flex-shrink-0 cursor-col-resize hover:bg-primary/30 active:bg-primary/50 transition-colors relative group",
"focus-visible:outline-none focus-visible:bg-primary/40 focus-visible:ring-2 focus-visible:ring-primary/50",
className
)}
>
<div className="absolute inset-y-0 -left-1 -right-1" />
</div>
);
}