Files
dyolink/frontend/src/components/ui/treatment/FdiToothChart.tsx

84 lines
3.0 KiB
TypeScript
Raw Normal View History

'use client';
import { useId } from 'react';
import { FDI_LOWER_LEFT_TO_RIGHT, FDI_UPPER_LEFT_TO_RIGHT, getToothShapeKind } from '@/components/treatment/fdiToothMeta';
import { ToothGlyph } from '@/components/ui/treatment/ToothGlyph';
import type { FdiToothId } from '@/types/treatment';
function quadrantMirrored(fdi: FdiToothId): boolean {
const q = fdi[0];
return q === '2' || q === '3';
}
interface FdiToothChartProps {
selected: ReadonlySet<FdiToothId>;
onToggle: (fdi: FdiToothId) => void;
disabled?: boolean;
}
export function FdiToothChart({ selected, onToggle, disabled }: FdiToothChartProps) {
const uid = useId().replace(/:/g, '');
const Row = ({ teeth }: { teeth: FdiToothId[] }) => (
<div className="flex flex-wrap justify-center gap-x-1 gap-y-2">
{teeth.map((fdi, i) => {
const kind = getToothShapeKind(fdi);
const gid = `${uid}-g-${fdi}-${i}`;
const isSel = selected.has(fdi);
return (
<div key={fdi} className="flex flex-col items-center w-[2.25rem]">
<button
type="button"
disabled={disabled}
onClick={() => onToggle(fdi)}
className={`
rounded-[var(--radius-sm)] p-0.5 transition-transform
focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50
${disabled ? 'opacity-50 cursor-not-allowed' : 'hover:scale-105 active:scale-95'}
`}
aria-pressed={isSel}
aria-label={`FDI tooth ${fdi}${isSel ? ', selected' : ''}`}
>
<ToothGlyph kind={kind} gradientId={gid} selected={isSel} mirrored={quadrantMirrored(fdi)} />
</button>
<span
className={`text-[10px] tabular-nums leading-none mt-0.5 font-medium ${
isSel ? 'text-primary' : 'text-text-muted'
}`}
>
{fdi}
</span>
</div>
);
})}
</div>
);
return (
<div className="surface-card p-4 space-y-6">
<div className="flex items-start justify-between gap-3 flex-wrap">
<div>
<h3 className="text-sm font-semibold text-text-primary">FDI tooth chart</h3>
<p className="text-xs text-text-muted mt-0.5">
Tap teeth to multi-select (FDI). Selection applies to the active treatment record until you save.
</p>
</div>
<p className="text-xs text-text-secondary tabular-nums">
Selected: {selected.size === 0 ? '—' : [...selected].sort().join(', ')}
</p>
</div>
<div className="space-y-6">
<div>
<p className="text-[11px] uppercase tracking-wide text-text-muted mb-2 text-center">Upper arch</p>
<Row teeth={FDI_UPPER_LEFT_TO_RIGHT} />
</div>
<div className="border-t border-border/60 pt-4">
<p className="text-[11px] uppercase tracking-wide text-text-muted mb-2 text-center">Lower arch</p>
<Row teeth={FDI_LOWER_LEFT_TO_RIGHT} />
</div>
</div>
</div>
);
}