84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
'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>
|
|
);
|
|
}
|