mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
docs(docs): fix cursor handling and hover tip refactor in docs UI components
This commit is contained in:
1 parent
0d066b8104
commit
7ead021ee2
5 files changed
+21
-15
No files matched your search
@@ -193,6 +193,8 @@ export default function DependencySpine() {
|
||||
};
|
||||
const [routes, setRoutes] = useState<Record<string, string> | null>(null);
|
||||
const drag = useRef<{ x: number; y: number; moved: boolean } | null>(null);
|
||||
// The cursor reads this, not `drag`: a ref read during render never re-renders.
|
||||
const [panning, setPanning] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
@@ -244,6 +246,7 @@ export default function DependencySpine() {
|
||||
// Capture only once it is a drag: capturing on pointerdown would retarget
|
||||
// pointerup (and so the click) to the <svg>, and a node's link would never fire.
|
||||
d.moved = true;
|
||||
setPanning(true);
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
}
|
||||
d.x = e.clientX;
|
||||
@@ -254,6 +257,7 @@ export default function DependencySpine() {
|
||||
};
|
||||
const onPointerUp = (e: PointerEvent<SVGSVGElement>) => {
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
setPanning(false);
|
||||
// Cleared on the next tick so a node's click handler can still see `moved`.
|
||||
setTimeout(() => { drag.current = null; }, 0);
|
||||
};
|
||||
@@ -293,7 +297,7 @@ export default function DependencySpine() {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="dependency-spine-title dependency-spine-desc"
|
||||
style={{ display: "block", width: "100%", height: "auto", touchAction: "none", cursor: drag.current?.moved ? "grabbing" : zoom > 1.001 ? "grab" : "default", userSelect: "none" }}
|
||||
style={{ display: "block", width: "100%", height: "auto", touchAction: "none", cursor: panning ? "grabbing" : zoom > 1.001 ? "grab" : "default", userSelect: "none" }}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
|
||||
@@ -57,14 +57,14 @@ export default function SolvedRing() {
|
||||
const done = rows.reduce((n, r) => n + r.done, 0);
|
||||
const span = SWEEP - GAP * (rows.length - 1);
|
||||
|
||||
const segments: ((typeof rows)[number] & { from: number; to: number; solvedTo: number })[] = [];
|
||||
let cursor = START;
|
||||
const segments = rows.map((r) => {
|
||||
for (const r of rows) {
|
||||
const size = total > 0 ? (span * r.total) / total : 0;
|
||||
const from = cursor;
|
||||
cursor -= size + GAP;
|
||||
const solved = r.total > 0 ? size * (r.done / r.total) : 0;
|
||||
return { ...r, from, to: from - size, solvedTo: from - solved };
|
||||
});
|
||||
segments.push({ ...r, from: cursor, to: cursor - size, solvedTo: cursor - solved });
|
||||
cursor -= size + GAP;
|
||||
}
|
||||
|
||||
const focused = focus ? rows.find((r) => r.difficulty === focus) : undefined;
|
||||
const arc = { innerRadius: "80%", outerRadius: "88%", cornerRadius: 99, stroke: "none", dataKey: "value" } as const;
|
||||
|
||||
@@ -76,7 +76,7 @@ function layout(year: Day[]): { blocks: Block[]; width: number } {
|
||||
|
||||
export default function SubmissionHeatmap() {
|
||||
const load = useStats();
|
||||
const hover = useHoverTip<HTMLDivElement>();
|
||||
const { ref, show, hide, node } = useHoverTip<HTMLDivElement>();
|
||||
|
||||
if (load.phase !== "ready") {
|
||||
return (
|
||||
@@ -116,7 +116,7 @@ export default function SubmissionHeatmap() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center">
|
||||
<div ref={hover.ref} className="relative w-full" onPointerLeave={hover.hide}>
|
||||
<div ref={ref} className="relative w-full" onPointerLeave={hide}>
|
||||
<svg
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
className="h-auto w-full"
|
||||
@@ -135,7 +135,7 @@ export default function SubmissionHeatmap() {
|
||||
rx={2}
|
||||
fill={HEAT[Math.min(cell.attempts, 4)]}
|
||||
onPointerMove={(event) =>
|
||||
hover.show(
|
||||
show(
|
||||
event,
|
||||
<>
|
||||
<div className="font-medium">{DAY.format(cell.date)}</div>
|
||||
@@ -157,7 +157,7 @@ export default function SubmissionHeatmap() {
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
{hover.node}
|
||||
{node}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -35,7 +35,7 @@ function lines(name: string): string[] {
|
||||
|
||||
export default function TopicBubbles() {
|
||||
const load = useStats();
|
||||
const hover = useHoverTip<HTMLDivElement>();
|
||||
const { ref, show, hide, node } = useHoverTip<HTMLDivElement>();
|
||||
const clip = useId().replace(/:/g, "");
|
||||
|
||||
if (load.phase !== "ready") {
|
||||
@@ -70,7 +70,7 @@ export default function TopicBubbles() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div ref={hover.ref} className="relative mx-auto w-full max-w-xl" onPointerLeave={hover.hide}>
|
||||
<div ref={ref} className="relative mx-auto w-full max-w-xl" onPointerLeave={hide}>
|
||||
<svg
|
||||
viewBox={`${bound.x - bound.r} ${bound.y - bound.r} ${bound.r * 2} ${bound.r * 2}`}
|
||||
className="h-auto w-full"
|
||||
@@ -89,7 +89,7 @@ export default function TopicBubbles() {
|
||||
key={c.issue}
|
||||
className={cn("transition-opacity hover:opacity-90", !c.taught && "opacity-50")}
|
||||
onPointerMove={(event) =>
|
||||
hover.show(
|
||||
show(
|
||||
event,
|
||||
<>
|
||||
<div className="font-medium">{c.name}</div>
|
||||
@@ -131,7 +131,7 @@ export default function TopicBubbles() {
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
{hover.node}
|
||||
{node}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -66,10 +66,12 @@ let request: Promise<Stats | null> | undefined;
|
||||
export function useStats(): Load {
|
||||
const [load, setLoad] = useState<Load>({ phase: "loading" });
|
||||
useEffect(() => {
|
||||
request ??= fetch(`${API_BASE}/api/stats`)
|
||||
if (!request) {
|
||||
request = fetch(`${API_BASE}/api/stats`)
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((body: unknown) => (isStats(body) ? body : null))
|
||||
.catch(() => null);
|
||||
}
|
||||
let live = true;
|
||||
request.then((stats) => {
|
||||
if (live) setLoad(stats ? { phase: "ready", stats } : { phase: "error" });
|
||||
|
||||
Reference in new issue
Block a user