Interactive with React State
Adjust the controls to see the layout update in real-time - including the margin the Graphic reserves around the content
import { Graphic } from "@modular-svg/react";
import { useState } from "react";
export function InteractiveState() {
const [radius, setRadius] = useState(20);
const [spacing, setSpacing] = useState(10);
const [showRect, setShowRect] = useState(true);
const [margin, setMargin] = useState(10);
return (
<div>
<div style={{ marginBottom: "1rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Circle Radius: {radius}px
<input
type="range"
min="10"
max="40"
value={radius}
onChange={(e) => setRadius(Number(e.target.value))}
style={{ marginLeft: "1rem", width: "200px" }}
/>
</label>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Spacing: {spacing}px
<input
type="range"
min="5"
max="30"
value={spacing}
onChange={(e) => setSpacing(Number(e.target.value))}
style={{ marginLeft: "1rem", width: "200px" }}
/>
</label>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
Graphic margin: {margin}px
<input
type="range"
min="0"
max="50"
value={margin}
onChange={(e) => setMargin(Number(e.target.value))}
style={{ marginLeft: "1rem", width: "200px" }}
/>
</label>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
<input
type="checkbox"
checked={showRect}
onChange={(e) => setShowRect(e.target.checked)}
style={{ marginRight: "0.5rem" }}
/>
Show Rectangle
</label>
</div>
<Graphic
style={{
border: "1px solid #ddd",
borderRadius: "8px",
display: "inline-block",
}}
margin={margin}
>
<stackH spacing={spacing}>
<circle r={radius} fill="hotpink" />
{showRect && <rect width={radius * 2} height={radius} fill="gold" />}
<circle r={radius} fill="cyan" />
</stackH>
</Graphic>
</div>
);
}
Interactive Event Handlers
Click on circles to see event handlers in action
import { Graphic } from "@modular-svg/react";
import { useState } from "react";
export function EventHandlers() {
const [clicked, setClicked] = useState<string | null>(null);
const [hovered, setHovered] = useState<string | null>(null);
const circles = [
{
key: "red-circle",
name: "Red Circle",
fill: "#ff0000",
hover: "#ff6666",
stroke: "#990000",
},
{
key: "green-circle",
name: "Green Circle",
fill: "#00ff00",
hover: "#66ff66",
stroke: "#009900",
},
{
key: "blue-circle",
name: "Blue Circle",
fill: "#0000ff",
hover: "#6666ff",
stroke: "#000099",
},
];
return (
<div>
<p style={{ color: "#333", fontSize: "14px", marginBottom: "1rem" }}>
{clicked ? `You clicked: ${clicked}` : "Click on any circle below"}
</p>
<Graphic
style={{
border: "1px solid #ddd",
borderRadius: "8px",
display: "inline-block",
cursor: "pointer",
}}
margin={10}
title="Interactive color circles"
>
<stackH spacing={20}>
{circles.map((c) => (
<circle
key={c.key}
r={25}
fill={hovered === c.name ? c.hover : c.fill}
stroke={c.stroke}
stroke-width={hovered === c.name ? 3 : 1}
onClick={() => setClicked(c.name)}
onMouseEnter={() => setHovered(c.name)}
onMouseLeave={() => setHovered(null)}
/>
))}
</stackH>
</Graphic>
</div>
);
}
Click on any circle below
React Context Forwarding
React contexts automatically flow into Graphic children - no special setup required
import { Graphic } from "@modular-svg/react";
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext<"light" | "dark">("light");
const SizeScaleContext = createContext(1);
function ThemedCircle() {
const theme = useContext(ThemeContext);
const scale = useContext(SizeScaleContext);
const colors = {
light: { fill: "#3b82f6", stroke: "#1e40af" },
dark: { fill: "#1e293b", stroke: "#475569" },
};
return (
<circle
r={20 * scale}
fill={colors[theme].fill}
stroke={colors[theme].stroke}
stroke-width={2}
/>
);
}
export function ContextForwarding() {
const [theme, setTheme] = useState<"light" | "dark">("light");
const [scale, setScale] = useState(1);
return (
<ThemeContext.Provider value={theme}>
<SizeScaleContext.Provider value={scale}>
<div>
<div style={{ marginBottom: "1rem" }}>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
<strong>Theme Context:</strong>
<select
value={theme}
onChange={(e) => setTheme(e.target.value as "light" | "dark")}
style={{ marginLeft: "1rem", padding: "4px 8px" }}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label style={{ display: "block", marginBottom: "0.5rem" }}>
<strong>Size Scale Context:</strong> {scale.toFixed(1)}x
<input
type="range"
min="0.5"
max="2"
step="0.1"
value={scale}
onChange={(e) => setScale(Number(e.target.value))}
style={{ marginLeft: "1rem", width: "200px" }}
/>
</label>
</div>
<Graphic
style={{
border: "1px solid #ddd",
borderRadius: "8px",
display: "inline-block",
}}
margin={10}
data-testid="context-canvas"
>
<stackH spacing={15}>
<ThemedCircle />
<ThemedCircle />
<ThemedCircle />
</stackH>
</Graphic>
</div>
</SizeScaleContext.Provider>
</ThemeContext.Provider>
);
}