Skip to content

Commit ce5e99e

Browse files
feat(example): modularize interactive feature demos
Extract the features page demos into dedicated modules and shared helpers. Add the Alt+Right interaction preview and update the features page copy/navigation to reflect the broader interaction model.
1 parent 99bd89b commit ce5e99e

22 files changed

+3109
-2603
lines changed

packages/example/src/app/SideNav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ export function SideNav() {
390390
href: "/features",
391391
label: "Features",
392392
items: [
393-
{ id: 'annotation-modes', text: 'Annotation Modes' },
393+
{ id: 'annotation-modes', text: 'Interaction Modes' },
394394
{ id: 'toolbar-controls', text: 'Toolbar Controls' },
395395
{ id: 'marker-types', text: 'Marker Types' },
396396
{ id: 'smart-identification', text: 'Smart Identification' },

packages/example/src/app/components/DeepSelectDemo.tsx

Lines changed: 65 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"use client";
22

3-
import { useState, useEffect, useRef } from "react";
3+
import { useRef, useState } from "react";
4+
5+
import { useDemoAnimationLoop } from "./features-demo/useDemoAnimationLoop";
6+
import { useMeasureOnResize } from "./features-demo/useMeasureOnResize";
47
import "./FeaturesDemo.css";
58
import "./DeepSelectDemo.css";
69

7-
function delay(ms: number) {
8-
return new Promise((r) => setTimeout(r, ms));
9-
}
10-
1110
/* ─────────────────────────────────────────────────────────
1211
* ANIMATION STORYBOARD
1312
*
@@ -86,42 +85,37 @@ export function DeepSelectDemo() {
8685
};
8786
};
8887

89-
useEffect(() => {
90-
const timer = setTimeout(measure, 100);
91-
window.addEventListener("resize", measure);
92-
return () => {
93-
clearTimeout(timer);
94-
window.removeEventListener("resize", measure);
95-
};
96-
}, []);
88+
useMeasureOnResize({ measure });
9789

98-
useEffect(() => {
99-
let cancelled = false;
100-
const feedbackText = "Add loading state";
90+
const feedbackText = "Add loading state";
10191

102-
const run = async () => {
103-
// Reset
104-
setCursorPos({ x: 280, y: 40 });
105-
setHighlight({ visible: false, mode: "normal", rect: { x: 0, y: 0, w: 0, h: 0 } });
106-
setTooltip({ visible: false, text: "", type: "wrong", x: 0, y: 0 });
107-
setShowPopup(false);
108-
setTypedText("");
109-
setShowMarker(false);
110-
setOverlayFlash(false);
111-
setActiveCaption("idle");
92+
const resetDemo = (): void => {
93+
setCursorPos({ x: 280, y: 40 });
94+
setHighlight({ visible: false, mode: "normal", rect: { x: 0, y: 0, w: 0, h: 0 } });
95+
setTooltip({ visible: false, text: "", type: "wrong", x: 0, y: 0 });
96+
setShowPopup(false);
97+
setTypedText("");
98+
setShowMarker(false);
99+
setOverlayFlash(false);
100+
setActiveCaption("idle");
101+
};
102+
103+
useDemoAnimationLoop(
104+
async ({ isCancelled, wait }) => {
105+
resetDemo();
112106

113-
await delay(600);
114-
if (cancelled) return;
107+
if (!(await wait(600))) {
108+
return;
109+
}
115110

116-
// Re-measure before using positions (layout may have shifted)
117111
measure();
118112
const card = cardPosRef.current;
119113
const btn = btnPosRef.current;
120114
setCursorPos({ x: btn.x + btn.w / 2, y: btn.y + btn.h / 2 });
121-
await delay(400);
122-
if (cancelled) return;
115+
if (!(await wait(400))) {
116+
return;
117+
}
123118

124-
// Normal hover — highlights the whole card (the overlay intercepts)
125119
setOverlayFlash(true);
126120
setHighlight({
127121
visible: true,
@@ -135,22 +129,22 @@ export function DeepSelectDemo() {
135129
x: card.x + card.w / 2,
136130
y: card.y - 10,
137131
});
138-
await delay(1600);
139-
if (cancelled) return;
132+
if (!(await wait(1600))) {
133+
return;
134+
}
140135

141-
// Fade highlight + tooltip + overlay flash
142-
setHighlight((h) => ({ ...h, visible: false }));
143-
setTooltip((t) => ({ ...t, visible: false }));
136+
setHighlight((currentHighlight) => ({ ...currentHighlight, visible: false }));
137+
setTooltip((currentTooltip) => ({ ...currentTooltip, visible: false }));
144138
setOverlayFlash(false);
145-
await delay(400);
146-
if (cancelled) return;
139+
if (!(await wait(400))) {
140+
return;
141+
}
147142

148-
// ⌘ beat — caption explains the feature
149143
setActiveCaption("cmd");
150-
await delay(2000);
151-
if (cancelled) return;
144+
if (!(await wait(2000))) {
145+
return;
146+
}
152147

153-
// Pierce hover — highlights just the button
154148
setActiveCaption("correct");
155149
setHighlight({
156150
visible: true,
@@ -164,61 +158,46 @@ export function DeepSelectDemo() {
164158
x: btn.x + btn.w / 2,
165159
y: btn.y - 10,
166160
});
167-
await delay(1400);
168-
if (cancelled) return;
161+
if (!(await wait(1400))) {
162+
return;
163+
}
169164

170-
// Click — show popup
171165
setShowPopup(true);
172-
await delay(300);
173-
if (cancelled) return;
166+
if (!(await wait(300))) {
167+
return;
168+
}
174169

175-
// Type feedback
176170
for (let i = 0; i <= feedbackText.length; i++) {
177-
if (cancelled) return;
171+
if (isCancelled()) {
172+
return;
173+
}
174+
178175
setTypedText(feedbackText.slice(0, i));
179-
await delay(30);
176+
if (!(await wait(30))) {
177+
return;
178+
}
179+
}
180+
if (!(await wait(400))) {
181+
return;
180182
}
181-
await delay(400);
182-
if (cancelled) return;
183183

184-
// Close popup, show marker
185184
setShowPopup(false);
186-
setHighlight((h) => ({ ...h, visible: false }));
187-
setTooltip((t) => ({ ...t, visible: false }));
188-
await delay(200);
189-
if (cancelled) return;
185+
setHighlight((currentHighlight) => ({ ...currentHighlight, visible: false }));
186+
setTooltip((currentTooltip) => ({ ...currentTooltip, visible: false }));
187+
if (!(await wait(200))) {
188+
return;
189+
}
190190
setShowMarker(true);
191191

192-
await delay(2200);
193-
if (cancelled) return;
194-
195-
// Clean up for next loop
196-
setShowMarker(false);
197-
await delay(300);
198-
};
199-
200-
run();
201-
let interval = setInterval(run, LOOP_INTERVAL);
202-
203-
const handleVisibility = () => {
204-
if (document.visibilityState === "visible") {
205-
cancelled = true;
206-
clearInterval(interval);
207-
setTimeout(() => {
208-
cancelled = false;
209-
run();
210-
interval = setInterval(run, LOOP_INTERVAL);
211-
}, 100);
192+
if (!(await wait(2200))) {
193+
return;
212194
}
213-
};
214-
document.addEventListener("visibilitychange", handleVisibility);
215195

216-
return () => {
217-
cancelled = true;
218-
clearInterval(interval);
219-
document.removeEventListener("visibilitychange", handleVisibility);
220-
};
221-
}, []);
196+
setShowMarker(false);
197+
await wait(300);
198+
},
199+
{ intervalMs: LOOP_INTERVAL, onVisibilityRestart: resetDemo },
200+
);
222201

223202
return (
224203
<div className="fd-container">

packages/example/src/app/components/FeaturesDemo.css

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,75 @@
524524
border-radius: 6px;
525525
}
526526

527+
.ard-plan-card {
528+
position: relative;
529+
}
530+
531+
.ard-action-row {
532+
display: flex;
533+
justify-content: flex-end;
534+
gap: 8px;
535+
}
536+
537+
.ard-secondary-button {
538+
width: 70px;
539+
height: 32px;
540+
background: rgba(0, 0, 0, 0.04);
541+
border-radius: 6px;
542+
}
543+
544+
.ard-export-button {
545+
display: flex;
546+
align-items: center;
547+
justify-content: center;
548+
min-width: 118px;
549+
height: 32px;
550+
padding: 0 14px;
551+
background: rgba(0, 0, 0, 0.9);
552+
border-radius: 6px;
553+
color: rgba(255,255,255,0.94);
554+
font-size: 11px;
555+
font-weight: 600;
556+
letter-spacing: 0.01em;
557+
font-family: system-ui, sans-serif;
558+
}
559+
560+
.ard-shortcut-pill {
561+
position: absolute;
562+
padding: 4px 8px;
563+
border-radius: 999px;
564+
background: rgba(15, 23, 42, 0.9);
565+
color: rgba(255,255,255,0.92);
566+
font-size: 10px;
567+
font-weight: 600;
568+
letter-spacing: 0.01em;
569+
transform: translateY(4px) scale(0.95);
570+
opacity: 0;
571+
transition: opacity 0.18s ease, transform 0.18s ease;
572+
z-index: 48;
573+
pointer-events: none;
574+
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
575+
white-space: nowrap;
576+
}
577+
578+
.ard-shortcut-pill.visible {
579+
opacity: 1;
580+
transform: translateY(0) scale(1);
581+
}
582+
583+
.ard-component-menu {
584+
pointer-events: none;
585+
z-index: 52 !important;
586+
}
587+
588+
.ard-toolbar-shell {
589+
z-index: 45 !important;
590+
}
591+
592+
.ard-toolbar-container {
593+
transition: transform 0.15s ease !important;
594+
}
595+
527596
/* Multi-Select Demo specific */
528597
.msd-faux-title {
529598
width: 40px;

0 commit comments

Comments
 (0)