feat(career): builder UX overhaul — DnD, rich text, per-item editing, richer preview
CI and Deploy / test (push) Failing after 1m56s
CI and Deploy / deploy (push) Has been skipped

Phase 4.5 (priorities 1, 2, 5, 6).
- Content tab now reads GET /api/cv/outline: each entry-based section expands
  to its entries with per-item hide, title/subtitle override, and rich-text
  bullet editing (RichTextField markdown toolbar).
- Native HTML5 drag-and-drop reorder for sections AND entries (useDragReorder,
  zero deps), with drop-line + dim animations; arrow buttons remain for keyboard.
- Preview: zoom presets (+/- , slider, Fit), measured page count with page
  navigation and dashed page-break indicators, an "updating…" chip, 300ms debounce.
- Save indicator now distinguishes Unsaved / Saving / Saved / failed (aria-live).
- Loading skeletons, better empty states, ATS-friendly theme badge, relative
  times in History; a11y: ARIA labels, keyboard-selectable theme cards, focus rings.
- Pure helpers moveItem/wrapSelection extracted + unit-tested; tsc + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-18 14:46:37 +02:00
parent e3b255f226
commit 582c4e07e9
5 changed files with 498 additions and 99 deletions
@@ -0,0 +1,40 @@
import React, { useCallback, useState } from "react";
// Native HTML5 drag-and-drop reordering — zero dependencies. Pointer users drag; keyboard users use
// the up/down buttons the lists also render (the accessible path). Returns per-item props plus the
// current drag/over indices so the caller can animate (dragged item dims, drop target shows a line).
export function useDragReorder(onReorder: (from: number, to: number) => void) {
const [dragIndex, setDragIndex] = useState<number | null>(null);
const [overIndex, setOverIndex] = useState<number | null>(null);
const reset = useCallback(() => {
setDragIndex(null);
setOverIndex(null);
}, []);
const getItemProps = useCallback(
(index: number) => ({
draggable: true,
onDragStart: (e: React.DragEvent) => {
setDragIndex(index);
e.dataTransfer.effectAllowed = "move";
// Firefox needs data set for a drag to start.
e.dataTransfer.setData("text/plain", String(index));
},
onDragOver: (e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
if (overIndex !== index) setOverIndex(index);
},
onDrop: (e: React.DragEvent) => {
e.preventDefault();
if (dragIndex !== null && dragIndex !== index) onReorder(dragIndex, index);
reset();
},
onDragEnd: reset,
}),
[dragIndex, overIndex, onReorder, reset],
);
return { dragIndex, overIndex, getItemProps };
}