228 lines
7.2 KiB
TypeScript
228 lines
7.2 KiB
TypeScript
import React, { useState } from "react";
|
||
|
||
const CompositeAreaWidget: React.FC = () => {
|
||
const [mode, setMode] = useState<"add" | "subtract">("add");
|
||
const [width, setWidth] = useState(10);
|
||
const [height, setHeight] = useState(6);
|
||
|
||
// Scale for display
|
||
const scale = 20;
|
||
const displayW = width * scale;
|
||
const displayH = height * scale;
|
||
const radius = width / 2; // Semicircle on top (width is diameter)
|
||
const displayR = radius * scale;
|
||
|
||
// Areas
|
||
const rectArea = width * height;
|
||
const semiArea = 0.5 * Math.PI * radius * radius;
|
||
const totalArea = mode === "add" ? rectArea + semiArea : rectArea - semiArea;
|
||
|
||
return (
|
||
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200 flex flex-col items-center">
|
||
<div className="flex gap-4 mb-8">
|
||
<button
|
||
onClick={() => setMode("add")}
|
||
className={`px-4 py-2 rounded-full font-bold text-sm transition-all ${
|
||
mode === "add"
|
||
? "bg-orange-600 text-white shadow-md transform scale-105"
|
||
: "bg-slate-100 text-slate-500 hover:bg-slate-200"
|
||
}`}
|
||
>
|
||
Add Semicircle (Composite)
|
||
</button>
|
||
<button
|
||
onClick={() => setMode("subtract")}
|
||
className={`px-4 py-2 rounded-full font-bold text-sm transition-all ${
|
||
mode === "subtract"
|
||
? "bg-rose-600 text-white shadow-md transform scale-105"
|
||
: "bg-slate-100 text-slate-500 hover:bg-slate-200"
|
||
}`}
|
||
>
|
||
Subtract Semicircle (Hole)
|
||
</button>
|
||
</div>
|
||
|
||
<div
|
||
className="relative mb-8 flex items-end justify-center"
|
||
style={{ height: "300px", width: "100%" }}
|
||
>
|
||
<svg
|
||
width="400"
|
||
height="300"
|
||
className="overflow-visible transition-all duration-500"
|
||
>
|
||
<defs>
|
||
<pattern
|
||
id="grid"
|
||
width="20"
|
||
height="20"
|
||
patternUnits="userSpaceOnUse"
|
||
>
|
||
<path
|
||
d="M 20 0 L 0 0 0 20"
|
||
fill="none"
|
||
stroke="#f1f5f9"
|
||
strokeWidth="1"
|
||
/>
|
||
</pattern>
|
||
</defs>
|
||
|
||
<g transform={`translate(${200 - displayW / 2}, ${250})`}>
|
||
{/* Rectangle */}
|
||
<rect
|
||
x="0"
|
||
y={-displayH}
|
||
width={displayW}
|
||
height={displayH}
|
||
fill={
|
||
mode === "add"
|
||
? "rgba(255,237,213, 1)"
|
||
: "rgba(254, 226, 226, 1)"
|
||
}
|
||
stroke={mode === "add" ? "#f97316" : "#e11d48"}
|
||
strokeWidth="2"
|
||
/>
|
||
|
||
{mode === "add" && (
|
||
// Semicircle on TOP
|
||
<path
|
||
d={`M 0 ${-displayH} A ${displayR} ${displayR} 0 0 1 ${displayW} ${-displayH} Z`}
|
||
fill="rgba(255,237,213, 1)"
|
||
stroke="#f97316"
|
||
strokeWidth="2"
|
||
transform={`translate(0,0)`}
|
||
/>
|
||
)}
|
||
|
||
{mode === "add" && (
|
||
// Hide the seam line
|
||
<line
|
||
x1="2"
|
||
y1={-displayH}
|
||
x2={displayW - 2}
|
||
y2={-displayH}
|
||
stroke="rgba(255,237,213, 1)"
|
||
strokeWidth="4"
|
||
/>
|
||
)}
|
||
|
||
{mode === "subtract" && (
|
||
// Semicircle Cutting INTO top
|
||
<path
|
||
d={`M 0 ${-displayH} A ${displayR} ${displayR} 0 0 0 ${displayW} ${-displayH} Z`}
|
||
fill="white"
|
||
stroke="#e11d48"
|
||
strokeWidth="2"
|
||
strokeDasharray="4,4"
|
||
/>
|
||
)}
|
||
|
||
{/* Labels */}
|
||
<text
|
||
x={displayW / 2}
|
||
y={-displayH / 2}
|
||
textAnchor="middle"
|
||
className="font-bold fill-slate-500 opacity-50 text-xl"
|
||
>
|
||
Rect
|
||
</text>
|
||
|
||
{mode === "add" && (
|
||
<text
|
||
x={displayW / 2}
|
||
y={-displayH - displayR / 2}
|
||
textAnchor="middle"
|
||
className="font-bold fill-orange-600 text-sm"
|
||
>
|
||
Semicircle
|
||
</text>
|
||
)}
|
||
{mode === "subtract" && (
|
||
<text
|
||
x={displayW / 2}
|
||
y={-displayH + displayR / 2}
|
||
textAnchor="middle"
|
||
className="font-bold fill-rose-600 text-sm"
|
||
>
|
||
Hole
|
||
</text>
|
||
)}
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-8 w-full max-w-2xl">
|
||
<div>
|
||
<label className="text-xs font-bold text-slate-400 uppercase">
|
||
Width (Diameter)
|
||
</label>
|
||
<input
|
||
type="range"
|
||
min="4"
|
||
max="14"
|
||
step="2"
|
||
value={width}
|
||
onChange={(e) => setWidth(parseInt(e.target.value))}
|
||
className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-slate-600 mt-2"
|
||
/>
|
||
<div className="text-right font-mono font-bold text-slate-700">
|
||
{width}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs font-bold text-slate-400 uppercase">
|
||
Height
|
||
</label>
|
||
<input
|
||
type="range"
|
||
min="4"
|
||
max="12"
|
||
step="1"
|
||
value={height}
|
||
onChange={(e) => setHeight(parseInt(e.target.value))}
|
||
className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-slate-600 mt-2"
|
||
/>
|
||
<div className="text-right font-mono font-bold text-slate-700">
|
||
{height}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-8 p-4 bg-slate-50 rounded-xl border border-slate-200 w-full max-w-2xl">
|
||
<div className="flex justify-between items-center mb-4">
|
||
<span className="font-bold text-slate-700">Calculation</span>
|
||
<span
|
||
className={`px-2 py-1 rounded text-xs font-bold uppercase ${mode === "add" ? "bg-orange-100 text-orange-800" : "bg-rose-100 text-rose-800"}`}
|
||
>
|
||
{mode === "add" ? "Sum" : "Difference"}
|
||
</span>
|
||
</div>
|
||
<div className="font-mono text-lg space-y-2">
|
||
<div className="flex justify-between">
|
||
<span className="text-slate-500">Rectangle Area (w×h)</span>
|
||
<span>
|
||
{width} × {height} = <strong>{rectArea}</strong>
|
||
</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-slate-500">Semicircle Area (½πr²)</span>
|
||
<span>
|
||
½ × π × {radius}² ≈ <strong>{semiArea.toFixed(1)}</strong>
|
||
</span>
|
||
</div>
|
||
<div className="border-t border-slate-300 my-2 pt-2 flex justify-between font-bold text-xl">
|
||
<span>Total Area</span>
|
||
<span
|
||
className={mode === "add" ? "text-orange-600" : "text-rose-600"}
|
||
>
|
||
{totalArea.toFixed(1)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CompositeAreaWidget;
|