workspaces to dashboards, imap email sync, ffmpeg tool, tts fix, file browser refresh, automation sidebar reorder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,14 +11,24 @@ type DockPillProps = {
|
||||
visible: boolean;
|
||||
onAction: (path: string) => void;
|
||||
onDragStart: (ev: DragEvent, path: string) => void;
|
||||
onDropOnPill?: (draggedPath: string) => void;
|
||||
dropIndicator?: 'left' | 'right' | null;
|
||||
onDragOverPill?: (ev: DragEvent) => void;
|
||||
onDragLeavePill?: () => void;
|
||||
};
|
||||
|
||||
const DockPill = ({ label, path, color, visible, onAction, onDragStart }: DockPillProps) => (
|
||||
const DockPill = ({
|
||||
label, path, color, visible, onAction, onDragStart, onDropOnPill, dropIndicator, onDragOverPill, onDragLeavePill,
|
||||
}: DockPillProps) => (
|
||||
<span
|
||||
draggable
|
||||
onDragStart={(ev) => onDragStart(ev, path)}
|
||||
className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium cursor-grab active:cursor-grabbing select-none border border-duck-dark/15 dark:border-foreground/15 bg-background/60 text-duck-dark/80 dark:text-foreground/80 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 transition-colors"
|
||||
onDragOver={onDragOverPill}
|
||||
onDragLeave={onDragLeavePill}
|
||||
onDrop={onDropOnPill ? (ev) => { ev.preventDefault(); ev.stopPropagation(); const p = ev.dataTransfer.getData('text/plain'); if (p) onDropOnPill(p); } : undefined}
|
||||
className={`relative inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium cursor-grab active:cursor-grabbing select-none border border-duck-dark/15 dark:border-foreground/15 bg-background/60 text-duck-dark/80 dark:text-foreground/80 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 transition-colors ${dropIndicator === 'left' ? 'ring-l-2 ring-duck-teal' : ''} ${dropIndicator === 'right' ? 'ring-r-2 ring-duck-teal' : ''}`}
|
||||
>
|
||||
{dropIndicator === 'left' && <span className="absolute -left-1 top-1 bottom-1 w-0.5 rounded-full bg-duck-teal" />}
|
||||
<span className="w-2 h-2 rounded-full shrink-0" style={{ background: color }} />
|
||||
{label}
|
||||
<button
|
||||
@@ -27,6 +37,7 @@ const DockPill = ({ label, path, color, visible, onAction, onDragStart }: DockPi
|
||||
>
|
||||
{visible ? <X className="h-3 w-3" /> : <Plus className="h-3 w-3" />}
|
||||
</button>
|
||||
{dropIndicator === 'right' && <span className="absolute -right-1 top-1 bottom-1 w-0.5 rounded-full bg-duck-teal" />}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -73,6 +84,7 @@ const DropZone = ({ label, children, onDrop }: DropZoneProps) => {
|
||||
|
||||
export const DockSettings = () => {
|
||||
const { items, allItems, setItems, reset } = useDock(ALL_DOCK_ITEMS, DEFAULT_DOCK_PATHS);
|
||||
const [dropTarget, setDropTarget] = useState<{ path: string; side: 'left' | 'right' } | null>(null);
|
||||
|
||||
const visiblePaths = new Set(items.map((i) => i.to));
|
||||
const hiddenItems = allItems.filter((i) => !visiblePaths.has(i.to));
|
||||
@@ -97,8 +109,42 @@ export const DockSettings = () => {
|
||||
[items, setItems],
|
||||
);
|
||||
|
||||
const insertAt = useCallback(
|
||||
(draggedPath: string, targetPath: string, side: 'left' | 'right') => {
|
||||
const paths = items.filter((i) => i.to !== draggedPath).map((i) => i.to);
|
||||
const targetIdx = paths.indexOf(targetPath);
|
||||
if (targetIdx === -1) return;
|
||||
const insertIdx = side === 'left' ? targetIdx : targetIdx + 1;
|
||||
paths.splice(insertIdx, 0, draggedPath);
|
||||
setItems(paths);
|
||||
setDropTarget(null);
|
||||
},
|
||||
[items, setItems],
|
||||
);
|
||||
|
||||
const handlePillDragOver = useCallback((ev: DragEvent, pillPath: string) => {
|
||||
ev.preventDefault();
|
||||
ev.dataTransfer.dropEffect = 'move';
|
||||
const rect = (ev.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
const midX = rect.left + rect.width / 2;
|
||||
const side = ev.clientX < midX ? 'left' : 'right';
|
||||
setDropTarget((prev) => (prev?.path === pillPath && prev?.side === side ? prev : { path: pillPath, side }));
|
||||
}, []);
|
||||
|
||||
const handlePillDragLeave = useCallback(() => setDropTarget(null), []);
|
||||
|
||||
const handlePillDrop = useCallback(
|
||||
(draggedPath: string, targetPath: string) => {
|
||||
if (draggedPath === targetPath) { setDropTarget(null); return; }
|
||||
const side = dropTarget?.path === targetPath ? dropTarget.side : 'right';
|
||||
insertAt(draggedPath, targetPath, side);
|
||||
},
|
||||
[dropTarget, insertAt],
|
||||
);
|
||||
|
||||
const onDropVisible = useCallback(
|
||||
(path: string) => {
|
||||
setDropTarget(null);
|
||||
if (visiblePaths.has(path)) return;
|
||||
addItem(path);
|
||||
},
|
||||
@@ -107,6 +153,7 @@ export const DockSettings = () => {
|
||||
|
||||
const onDropHidden = useCallback(
|
||||
(path: string) => {
|
||||
setDropTarget(null);
|
||||
if (!visiblePaths.has(path)) return;
|
||||
removeItem(path);
|
||||
},
|
||||
@@ -126,6 +173,10 @@ export const DockSettings = () => {
|
||||
visible
|
||||
onAction={removeItem}
|
||||
onDragStart={onDragStart}
|
||||
dropIndicator={dropTarget?.path === item.to ? dropTarget.side : null}
|
||||
onDragOverPill={(ev) => handlePillDragOver(ev, item.to)}
|
||||
onDragLeavePill={handlePillDragLeave}
|
||||
onDropOnPill={(draggedPath) => handlePillDrop(draggedPath, item.to)}
|
||||
/>
|
||||
))}
|
||||
</DropZone>
|
||||
|
||||
Reference in New Issue
Block a user