Minor UI, fix dragging to select
This commit is contained in:
@@ -36,22 +36,22 @@ export const ConfirmModal: React.FC<ConfirmModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative w-full max-w-md bg-white rounded-2xl border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] p-6 animate-in fade-in zoom-in-95 duration-200">
|
<div className="relative w-full max-w-md bg-white dark:bg-neutral-900 rounded-2xl border-2 border-black dark:border-neutral-700 shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.08)] p-6 animate-in fade-in zoom-in-95 duration-200">
|
||||||
<button
|
<button
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="absolute right-4 top-4 text-neutral-400 hover:text-neutral-900 transition-colors"
|
className="absolute right-4 top-4 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex flex-col items-center text-center gap-4">
|
<div className="flex flex-col items-center text-center gap-4">
|
||||||
<div className="w-12 h-12 rounded-full bg-rose-100 flex items-center justify-center text-rose-600 border-2 border-rose-200">
|
<div className="w-12 h-12 rounded-full bg-rose-100 dark:bg-rose-900/30 flex items-center justify-center text-rose-600 dark:text-rose-300 border-2 border-rose-200 dark:border-rose-900/30">
|
||||||
<AlertTriangle size={24} strokeWidth={2.5} />
|
<AlertTriangle size={24} strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-xl font-bold text-neutral-900 tracking-tight">{title}</h3>
|
<h3 className="text-xl font-bold text-neutral-900 dark:text-neutral-100 tracking-tight">{title}</h3>
|
||||||
<p className="text-sm font-medium text-neutral-500 leading-relaxed">
|
<p className="text-sm font-medium text-neutral-500 dark:text-neutral-400 leading-relaxed">
|
||||||
{message}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +61,7 @@ export const ConfirmModal: React.FC<ConfirmModalProps> = ({
|
|||||||
{showCancel && (
|
{showCancel && (
|
||||||
<button
|
<button
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="flex-1 px-4 py-2.5 bg-emerald-50 text-emerald-700 font-bold rounded-xl border-2 border-emerald-200 hover:bg-emerald-100 hover:border-emerald-300 hover:-translate-y-0.5 transition-all duration-200"
|
className="flex-1 px-4 py-2.5 bg-emerald-50 dark:bg-neutral-800 text-emerald-700 dark:text-emerald-200 font-bold rounded-xl border-2 border-emerald-200 dark:border-neutral-700 hover:bg-emerald-100 dark:hover:bg-neutral-700 hover:border-emerald-300 dark:hover:border-neutral-600 hover:-translate-y-0.5 transition-all duration-200"
|
||||||
>
|
>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ const SidebarItem: React.FC<SidebarItemProps> = ({
|
|||||||
setIsDragOver(false);
|
setIsDragOver(false);
|
||||||
onDrop?.(e, id);
|
onDrop?.(e, id);
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-full flex items-center gap-3 px-3 py-2.5 text-sm font-bold rounded-lg transition-all duration-200 border-2 group cursor-pointer outline-none focus:ring-2 focus:ring-indigo-500",
|
"w-full flex items-center gap-3 px-3 py-2.5 text-sm font-bold rounded-lg transition-all duration-200 border-2 group cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:focus-visible:ring-2 dark:focus-visible:ring-neutral-500",
|
||||||
isActive || isDragOver
|
isActive || isDragOver
|
||||||
? "bg-indigo-50 dark:bg-neutral-800 text-indigo-900 dark:text-neutral-200 border-black dark:border-neutral-700 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)] -translate-y-0.5"
|
? "bg-indigo-50 dark:bg-neutral-800 text-indigo-900 dark:text-neutral-200 border-black dark:border-neutral-700 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)] -translate-y-0.5"
|
||||||
: "text-slate-600 dark:text-neutral-400 border-transparent hover:bg-slate-50 dark:hover:bg-neutral-800 hover:border-black dark:hover:border-neutral-700 hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)] hover:-translate-y-0.5"
|
: "text-slate-600 dark:text-neutral-400 border-transparent hover:bg-slate-50 dark:hover:bg-neutral-800 hover:border-black dark:hover:border-neutral-700 hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)] hover:-translate-y-0.5"
|
||||||
|
|||||||
@@ -11,6 +11,32 @@ import clsx from 'clsx';
|
|||||||
import { ConfirmModal } from '../components/ConfirmModal';
|
import { ConfirmModal } from '../components/ConfirmModal';
|
||||||
import { importDrawings, importLibrary } from '../utils/importUtils';
|
import { importDrawings, importLibrary } from '../utils/importUtils';
|
||||||
|
|
||||||
|
type Point = { x: number; y: number };
|
||||||
|
|
||||||
|
type SelectionBounds = {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectionBounds = (start: Point, current: Point): SelectionBounds => {
|
||||||
|
const left = Math.min(start.x, current.x);
|
||||||
|
const right = Math.max(start.x, current.x);
|
||||||
|
const top = Math.min(start.y, current.y);
|
||||||
|
const bottom = Math.max(start.y, current.y);
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
width: right - left,
|
||||||
|
height: bottom - top,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const DragOverlayPortal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
const DragOverlayPortal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
return createPortal(children, document.body);
|
return createPortal(children, document.body);
|
||||||
};
|
};
|
||||||
@@ -55,8 +81,8 @@ export const Dashboard: React.FC = () => {
|
|||||||
|
|
||||||
// Drag Selection State
|
// Drag Selection State
|
||||||
const [isDragSelecting, setIsDragSelecting] = useState(false);
|
const [isDragSelecting, setIsDragSelecting] = useState(false);
|
||||||
const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null);
|
const [dragStart, setDragStart] = useState<Point | null>(null);
|
||||||
const [dragCurrent, setDragCurrent] = useState<{ x: number; y: number } | null>(null);
|
const [dragCurrent, setDragCurrent] = useState<Point | null>(null);
|
||||||
const [potentialDragId, setPotentialDragId] = useState<string | null>(null);
|
const [potentialDragId, setPotentialDragId] = useState<string | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -123,6 +149,11 @@ export const Dashboard: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const selectionBounds = React.useMemo<SelectionBounds | null>(() => {
|
||||||
|
if (!dragStart || !dragCurrent) return null;
|
||||||
|
return getSelectionBounds(dragStart, dragCurrent);
|
||||||
|
}, [dragStart, dragCurrent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDragSelecting) return;
|
if (!isDragSelecting) return;
|
||||||
|
|
||||||
@@ -138,14 +169,9 @@ export const Dashboard: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate selection rect
|
const selectionRect = getSelectionBounds(dragStart, dragCurrent);
|
||||||
const left = Math.min(dragStart.x, dragCurrent.x);
|
|
||||||
const top = Math.min(dragStart.y, dragCurrent.y);
|
|
||||||
const width = Math.abs(dragCurrent.x - dragStart.x);
|
|
||||||
const height = Math.abs(dragCurrent.y - dragStart.y);
|
|
||||||
const selectionRect = { left, top, right: left + width, bottom: top + height };
|
|
||||||
|
|
||||||
if (width > 5 || height > 5) {
|
if (selectionRect.width > 5 || selectionRect.height > 5) {
|
||||||
const newSelectedIds = new Set(selectedIds);
|
const newSelectedIds = new Set(selectedIds);
|
||||||
drawings.forEach(drawing => {
|
drawings.forEach(drawing => {
|
||||||
const card = document.getElementById(`drawing-card-${drawing.id}`);
|
const card = document.getElementById(`drawing-card-${drawing.id}`);
|
||||||
@@ -636,15 +662,15 @@ export const Dashboard: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Drag Selection Overlay */}
|
{/* Drag Selection Overlay */}
|
||||||
{isDragSelecting && dragStart && dragCurrent && (
|
{isDragSelecting && selectionBounds && (
|
||||||
<DragOverlayPortal>
|
<DragOverlayPortal>
|
||||||
<div
|
<div
|
||||||
className="fixed z-50 pointer-events-none border-2 border-black dark:border-neutral-500 bg-neutral-500/20 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)]"
|
className="fixed z-50 pointer-events-none border-2 border-black dark:border-neutral-500 bg-neutral-500/20 shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] dark:shadow-[2px_2px_0px_0px_rgba(255,255,255,0.2)]"
|
||||||
style={{
|
style={{
|
||||||
left: Math.min(dragStart.x, dragCurrent.x),
|
left: selectionBounds.left,
|
||||||
top: Math.min(dragStart.y, dragCurrent.y),
|
top: selectionBounds.top,
|
||||||
width: Math.abs(dragCurrent.x - dragStart.x),
|
width: selectionBounds.width,
|
||||||
height: Math.abs(dragCurrent.y - dragStart.y),
|
height: selectionBounds.height,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DragOverlayPortal>
|
</DragOverlayPortal>
|
||||||
|
|||||||
Reference in New Issue
Block a user