141 lines
3.8 KiB
TypeScript
141 lines
3.8 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { memo } from 'react';
|
||
|
|
import type { ToothShapeKind } from '@/components/treatment/fdiToothMeta';
|
||
|
|
|
||
|
|
interface ToothGlyphProps {
|
||
|
|
kind: ToothShapeKind;
|
||
|
|
gradientId: string;
|
||
|
|
selected: boolean;
|
||
|
|
mirrored?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Stylized single tooth for FDI chart (outline + radial fill). Crown above, root(s) below. */
|
||
|
|
export const ToothGlyph = memo(function ToothGlyph({
|
||
|
|
kind,
|
||
|
|
gradientId,
|
||
|
|
selected,
|
||
|
|
mirrored,
|
||
|
|
}: ToothGlyphProps) {
|
||
|
|
const stroke = selected ? 'var(--color-primary)' : '#1e293b';
|
||
|
|
const strokeW = selected ? 2.4 : 1.35;
|
||
|
|
const filter = selected ? 'drop-shadow(0 0 6px rgba(9, 169, 188, 0.65))' : undefined;
|
||
|
|
|
||
|
|
const inner = () => {
|
||
|
|
switch (kind) {
|
||
|
|
case 'incisor':
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<path
|
||
|
|
d="M10 7h16q3 0 3 3v22q0 3-3 3h-16q-3 0-3-3v-22q0-3 3-3z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
<path
|
||
|
|
d="M14 35v26q4 10 8 0v-26"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
case 'canine':
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<path
|
||
|
|
d="M18 6 L26 14 L24 35 H12 L10 14 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
<path
|
||
|
|
d="M14 35 L12 62 Q18 74 24 62 L22 35 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
case 'premolar':
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<path
|
||
|
|
d="M8 12 Q18 6 28 12 L30 22 Q31 28 26 34 H10 Q5 28 6 22 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
<path
|
||
|
|
d="M11 34 L10 58 Q18 68 26 58 L25 34 Q18 30 11 34 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
case 'molar':
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<path
|
||
|
|
d="M6 14 Q10 8 14 10 Q18 7 22 10 Q26 8 30 14 L31 26 Q32 32 27 36 H9 Q4 32 5 26 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
<path
|
||
|
|
d="M10 36 L8 56 Q14 66 18 58 Q22 66 28 56 L26 36 Q18 32 10 36 Z"
|
||
|
|
fill={`url(#${gradientId})`}
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW}
|
||
|
|
strokeLinejoin="round"
|
||
|
|
/>
|
||
|
|
<path
|
||
|
|
d="M12 58 Q18 72 24 58"
|
||
|
|
fill="none"
|
||
|
|
stroke={stroke}
|
||
|
|
strokeWidth={strokeW * 0.85}
|
||
|
|
strokeLinecap="round"
|
||
|
|
/>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
default:
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<svg
|
||
|
|
viewBox="0 0 36 78"
|
||
|
|
className="w-8 h-[4.85rem] shrink-0"
|
||
|
|
style={{ filter }}
|
||
|
|
aria-hidden
|
||
|
|
>
|
||
|
|
<defs>
|
||
|
|
<radialGradient id={gradientId} cx="45%" cy="35%" r="65%">
|
||
|
|
<stop
|
||
|
|
offset="0%"
|
||
|
|
stopColor={selected ? '#cffafe' : '#ffffff'}
|
||
|
|
/>
|
||
|
|
<stop
|
||
|
|
offset="55%"
|
||
|
|
stopColor={selected ? '#5eead4' : '#e0f2fe'}
|
||
|
|
/>
|
||
|
|
<stop
|
||
|
|
offset="100%"
|
||
|
|
stopColor={selected ? '#0e7490' : '#93c5fd'}
|
||
|
|
/>
|
||
|
|
</radialGradient>
|
||
|
|
</defs>
|
||
|
|
<g transform={mirrored ? 'translate(36 0) scale(-1 1)' : undefined}>{inner()}</g>
|
||
|
|
</svg>
|
||
|
|
);
|
||
|
|
});
|