Create React TypeScript components for the laser harp following established patterns. Use when implementing UI components like ControlPanel, Stage, VideoLayer, or CanvasLayer.
App (root)
├── ControlPanel (UI controls)
└── Stage (display area)
├── VideoLayer (camera feed - hidden, but needed as source)
├── EffectLayer (silhouette rendering)
└── CanvasLayer (zone boundaries + glow effects)
template-functional.tsx - Standard functional component with TypeScripttemplate-canvas.tsx - Canvas-based component with useRef and resize handlingLocation: src/components/ControlPanel.tsx
Props:
interface ControlPanelProps {
config: AppConfig;
onConfigChange: (config: AppConfig) => void;
onStart: () => void;
onStop: () => void;
isRunning: boolean;
}
Responsibilities:
Key Implementation Points:
await Tone.start() before onStart()Location: src/components/Stage.tsx
Props:
interface StageProps {
width: number;
height: number;
children: React.ReactNode;
}
Responsibilities:
Key Implementation Points:
position: relative for containerLocation: src/components/VideoLayer.tsx
Props:
interface VideoLayerProps {
videoRef: RefObject<HTMLVideoElement>;
}
Responsibilities:
Key Implementation Points:
<video> element with reftransform: scaleX(-1) for mirroringLocation: src/components/EffectLayer.tsx
Props:
interface EffectLayerProps {
videoRef: RefObject<HTMLVideoElement>;
width: number;
height: number;
}
Responsibilities:
Key Implementation Points:
Location: src/components/CanvasLayer.tsx
Props:
interface CanvasLayerProps {
canvasRef: RefObject<HTMLCanvasElement>;
width: number;
height: number;
strings: LaserString[];
hands: TrackedHand[];
activeZones: number[];
}
Responsibilities:
Key Implementation Points:
<canvas> element with refChoose the appropriate template
template-functional.tsx for ControlPanel, Stage, VideoLayertemplate-canvas.tsx for CanvasLayer, EffectLayerCheck if component already exists
# Check for existing file
ls src/components/[ComponentName].tsx
Copy template and customize
[ComponentName] with actual namesrc/types/index.tsImplement component logic
Add styling
Verify TypeScript types
useState for local stateuseEffect for side effectsuseRef for DOM refs and mutable valuesuseCallback for stable callbacks (if needed)All types should be imported from src/types/index.ts:
import type { AppConfig, Point2D, TrackedHand, LaserString } from '../types';
See .claude/docs/data-structures.md for complete type definitions.
See examples/ directory for complete reference implementations:
ControlPanel.example.tsx - Full ControlPanel with all featuresStage.example.tsx - Stage component with layoutconst ControlPanel: React.FC<ControlPanelProps> = ({ config, onConfigChange }) => {
const handleNumStringsChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const numStrings = parseInt(e.target.value, 10);
onConfigChange({ ...config, numStrings });
};
return (
<select value={config.numStrings} onChange={handleNumStringsChange}>
{/* options */}
</select>
);
};
const CanvasLayer: React.FC<CanvasLayerProps> = ({ canvasRef, width, height }) => {
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Drawing logic - zone boundaries and glow effects
}, [canvasRef, width, height]);
return <canvas ref={canvasRef} width={width} height={height} />;
};
const VideoLayer: React.FC<VideoLayerProps> = ({ videoRef }) => {
return (
<video
ref={videoRef}
autoPlay
playsInline
muted
style={{
transform: 'scaleX(-1)',
opacity: 0 // Hidden - silhouette only
}}
/>
);
};
const EffectLayer: React.FC<EffectLayerProps> = ({ videoRef, width, height }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
const video = videoRef.current;
if (!canvas || !video) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Frame processing loop for silhouette effect
}, [videoRef, width, height]);
return <canvas ref={canvasRef} width={width} height={height} />;
};
After creating a component, verify: