From 699e9cd936ecb3f57617a564273cb796e419ee98 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sat, 25 Jan 2025 10:40:28 +0100 Subject: [PATCH 01/14] fix: Change button shape to rounded-full in Tooth component --- src/components/Tooth.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index 28dfba3..a16bd75 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -98,7 +98,7 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, @@ -505,9 +553,8 @@ const ElasticPlacer = () => { + ); }); diff --git a/tailwind.config.js b/tailwind.config.js index 99d31a5..e8c6456 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -9,6 +9,14 @@ export default { fontFamily: { sans: ["Montserrat", "Open Sans", "sans-serif"], }, + spacing: { + 21: '5.25rem', + 22: '5.5rem', + 34: '8.5rem', + 35: '8.75rem', + 37: '9.25rem', + 38: '9.5rem', + }, }, }, plugins: [], From 89bbaf9e64c82a1bc084333beee33a7d10e21b14 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sat, 25 Jan 2025 16:07:47 +0100 Subject: [PATCH 06/14] feat: Refactor layout in App component to improve responsiveness and structure --- src/App.tsx | 113 +++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a4539da..9e26703 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -351,74 +351,79 @@ const ElasticPlacer = () => { return (
-
+

{t("title")}

- {/* View Toggle */} - {FEATURES.MIRROR_VIEW && ( - setIsMirrorView((prev) => !prev)} - /> - )} - - {/* Legend for Teeth */} - {(FEATURES.HIGHLIGHT_SPECIAL_TEETH || FEATURES.DISABLE_TEETH) && ( -
- {FEATURES.HIGHLIGHT_SPECIAL_TEETH && ( - <> +
+ + {/* View Toggle */} + {FEATURES.MIRROR_VIEW && ( + setIsMirrorView((prev) => !prev)} + /> + )} + + {/* Legend for Teeth */} + {(FEATURES.HIGHLIGHT_SPECIAL_TEETH || FEATURES.DISABLE_TEETH) && ( +
+ {FEATURES.HIGHLIGHT_SPECIAL_TEETH && ( + <> +
+
+ {t("legend.middleIncisors")} +
+
+
+ {t("legend.canines")} +
+ + )} + {FEATURES.DISABLE_TEETH && (
-
- {t("legend.middleIncisors")} +
+ {t("legend.disabledTeeth")}
-
-
- {t("legend.canines")} + )} +
+ )} + + {/* Legend for Elastics */} +
+
+ {ELASTIC_TYPES.map((etype) => ( +
+ + + + {etype.icon} + + {t("legend.elasticType", { type: etype.name })} +
- - )} - {FEATURES.DISABLE_TEETH && ( -
-
- {t("legend.disabledTeeth")} -
- )} + ))} +
- )} - {/* Legend for Elastics */} -
-
- {ELASTIC_TYPES.map((etype) => ( -
- - - - {etype.icon} - - {t("legend.elasticType", { type: etype.name })} - -
- ))} -
+ {/* Teeth Grid */}
Date: Sat, 25 Jan 2025 16:17:11 +0100 Subject: [PATCH 07/14] feat: Implement TeethGrid component to streamline tooth rendering in App component --- src/App.tsx | 84 +------------------------- src/components/TeethGrid.tsx | 110 +++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 src/components/TeethGrid.tsx diff --git a/src/App.tsx b/src/App.tsx index 9e26703..e13529d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,11 +3,10 @@ import { X, PlusCircle, Share2, Redo2 } from "lucide-react"; import { QRCodeSVG } from "qrcode.react"; import ViewToggle from "./components/ViewToggle"; import { useTranslation } from "react-i18next"; -import Tooth from "./components/Tooth"; import { FEATURES } from "./config"; import LanguageButtons from "./components/LanguageButtons"; import Logo from "./components/Logo"; -import { classNames } from "./util/class-names"; +import { TeethGrid } from "./components/TeethGrid"; const teethLayout = { topLeft: [18, 17, 16, 15, 14, 13, 12, 11], @@ -424,86 +423,7 @@ const ElasticPlacer = () => {
{/* Teeth Grid */} -
-
- -
- {teethLayout.topLeft.map((tooth) => ( - - ))} -
-
- {teethLayout.topRight.map((tooth) => ( - - ))} -
-
- {teethLayout.bottomLeft.map((tooth) => ( - - ))} -
-
- {teethLayout.bottomRight.map((tooth) => ( - - ))} -
-
-
+
diff --git a/src/components/TeethGrid.tsx b/src/components/TeethGrid.tsx new file mode 100644 index 0000000..3becb70 --- /dev/null +++ b/src/components/TeethGrid.tsx @@ -0,0 +1,110 @@ + +import React from "react"; + +import { FEATURES } from "../config"; +import Tooth from "./Tooth"; + +type TeethGridProps = { + teethLayout: { + topLeft: number[]; + topRight: number[]; + bottomLeft: number[]; + bottomRight: number[]; + }; + currentElastic: number[]; + disabledTeeth: number[]; + handleToothClick: (number: number) => void; + handleToothToggle: (number: number) => void; + setToothRef: (number: number, ref: HTMLButtonElement) => void; + isMirrorView: boolean; + svgRef: React.MutableRefObject; +}; + +export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleToothClick, handleToothToggle, setToothRef, isMirrorView, svgRef + + }: TeethGridProps) { + + return ( +
+
+ +
+ {teethLayout.topLeft.map((tooth) => ( + + ))} +
+
+ {teethLayout.topRight.map((tooth) => ( + + ))} +
+
+ {teethLayout.bottomLeft.map((tooth) => ( + + ))} +
+
+ {teethLayout.bottomRight.map((tooth) => ( + + ))} +
+
+
+ ) + +} From 76fd1dab4722601e7fe243174bc32af08e289ed0 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sun, 26 Jan 2025 12:20:10 +0100 Subject: [PATCH 08/14] feat: Add side buttons to Tooth component for enhanced interaction and visual feedback --- src/components/Tooth.tsx | 44 +++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index 3c29b4f..ae23d81 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -73,6 +73,16 @@ const getToothColor = (number: number, selected: boolean, disabled: boolean) => return 'bg-yellow-50'; }; +const getToothSideColor = (number: number, selected: boolean, disabled: boolean) => { + if (disabled && FEATURES.DISABLE_TEETH) return 'bg-gray-300'; + if (selected) return 'bg-blue-500'; + if (FEATURES.HIGHLIGHT_SPECIAL_TEETH) { + if (middleIncisors.includes(number)) return 'bg-green-200'; + if (canines.includes(number)) return 'bg-purple-200'; + } + return 'bg-yellow-50'; +}; + const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, setRef, isMirrorView }: ToothMemo & { isMirrorView: boolean }) => { const handleClick = (e: React.MouseEvent) => { @@ -97,14 +107,12 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, const teethModification = teethModifications[number]; return ( - + + {/* outer half */} +
); }); From aaaf7aac9bacf0d0c1b4d916c1f5c06793ad3023 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sun, 26 Jan 2025 12:20:30 +0100 Subject: [PATCH 09/14] refactor: Remove comments on tooth size modifications for cleaner code --- src/components/Tooth.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index ae23d81..2e931b2 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -14,9 +14,6 @@ type TeethMofidication = { }; } -// Molars should be slightly wider than premolars -// Canines should be slightly pointier -// Incisors should be slightly smaller const teethModifications: TeethMofidication = { 18: { className: 'translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, 17: { className: 'translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, From 689f4495fd22a52d288eb56facb71059cd3a58c2 Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sun, 26 Jan 2025 12:49:23 +0100 Subject: [PATCH 10/14] feat: Refactor Elastic handling to support outside tooth clicks and update types --- src/App.tsx | 53 ++++++++++++------------------------ src/components/TeethGrid.tsx | 15 +++++----- src/components/Tooth.tsx | 26 +++++++----------- src/types/index.ts | 25 +++++++++++++++++ 4 files changed, 61 insertions(+), 58 deletions(-) create mode 100644 src/types/index.ts diff --git a/src/App.tsx b/src/App.tsx index e13529d..57adf7e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { FEATURES } from "./config"; import LanguageButtons from "./components/LanguageButtons"; import Logo from "./components/Logo"; import { TeethGrid } from "./components/TeethGrid"; +import { Elastic, ElasticPoint, TimeType } from "./types"; const teethLayout = { topLeft: [18, 17, 16, 15, 14, 13, 12, 11], @@ -21,27 +22,6 @@ const ELASTIC_TYPES = [ { id: 2, name: "Fox", color: "#44DD44", thickness: 5, icon: "🦊" }, ]; -type TimeType = "a" | "d" | "n"; - -type Elastic = { - teeth: number[]; - type: number; - - /* - * Which time of day the elastic should be worn - * 24h: 24 hours a day - * daytime: only during the day - * nighttime: only during the night - * (emojis are used for display) - */ - time: TimeType; - - /* - * Placed on the outer side of the teeth or inner side - */ - outer?: boolean; -}; - // Create a mapping for the time options const TIME_OPTIONS: { [key: string]: string } = { a: "24h", @@ -57,7 +37,7 @@ const ElasticPlacer = () => { const { t } = useTranslation(); const [isMirrorView, setIsMirrorView] = useState(false); const [elastics, setElastics] = useState([]); - const [currentElastic, setCurrentElastic] = useState([]); + const [currentElastic, setCurrentElastic] = useState([]); const [currentElasticType, setCurrentElasticType] = useState( ELASTIC_TYPES[0].id ); @@ -143,13 +123,13 @@ const ElasticPlacer = () => { const toothAlreadyInElastic = useCallback( (toothNumber: number) => { - return elastics.some((elastic) => elastic.teeth.includes(toothNumber)); + return elastics.some((elastic) => elastic.teeth.some(t => t.tooth === toothNumber)); }, [elastics] ); const handleToothClick = useCallback( - (number: number) => { + (number: number, outside: boolean) => { if ( !FEATURES.ALLOW_MULTIPLE_ELASTICS_PER_TOOTH && toothAlreadyInElastic(number) @@ -158,11 +138,12 @@ const ElasticPlacer = () => { } if (!FEATURES.DISABLE_TEETH || !disabledTeeth.includes(number)) { - setCurrentElastic((prev) => - prev.includes(number) - ? prev.filter((n) => n !== number) - : [...prev, number] - ); + setCurrentElastic((prev) => { + const newElastic = { tooth: number, outside }; + return prev.some((t) => t.tooth === number) + ? prev.filter((t) => t.tooth !== number) + : [...prev, newElastic]; + }); } }, [disabledTeeth, toothAlreadyInElastic] @@ -175,7 +156,7 @@ const ElasticPlacer = () => { ? prev.filter((n) => n !== number) : [...prev, number] ); - setCurrentElastic((prev) => prev.filter((n) => n !== number)); + setCurrentElastic((prev) => prev.filter((n) => n.tooth !== number)); } }, []); @@ -209,8 +190,9 @@ const ElasticPlacer = () => { setShareUrl(window.location.origin + window.location.pathname); }, []); - const setToothRef = useCallback((number: number, ref: HTMLButtonElement) => { - toothRefs.current[number] = ref; + const setToothRef = useCallback((number: number, outside: boolean, ref: HTMLButtonElement) => { + const toothRefKey = outside ? number * -1 : number; + toothRefs.current[toothRefKey] = ref; }, []); const drawElastics = useCallback(() => { @@ -231,8 +213,9 @@ const ElasticPlacer = () => { ELASTIC_TYPES.find((et) => et.id === elastic.type) || ELASTIC_TYPES[0]; const svgRect = svgRef.current.getBoundingClientRect(); const points = elastic.teeth - .map((toothNumber) => { - const rect = toothRefs.current[toothNumber]?.getBoundingClientRect(); + .map((elasticPoint) => { + const toothRefKey = elasticPoint.outside ? elasticPoint.tooth * -1 : elasticPoint.tooth; + const rect = toothRefs.current[toothRefKey]?.getBoundingClientRect(); if (!rect) return null; const baseX = rect.left - svgRect.left + rect.width / 2; @@ -532,7 +515,7 @@ const ElasticPlacer = () => { {t("elastics.elastic", { number: index + 1, - teeth: elastic.teeth.join(" → "), + teeth: elastic.teeth.map((t) => t.tooth).join(" → "), })}  -{" "} {t("elastics.elasticTypeDisplay", { type: etype.name })}  diff --git a/src/components/TeethGrid.tsx b/src/components/TeethGrid.tsx index 3becb70..e537d55 100644 --- a/src/components/TeethGrid.tsx +++ b/src/components/TeethGrid.tsx @@ -3,6 +3,7 @@ import React from "react"; import { FEATURES } from "../config"; import Tooth from "./Tooth"; +import { ElasticPoint } from "../types"; type TeethGridProps = { teethLayout: { @@ -11,11 +12,11 @@ type TeethGridProps = { bottomLeft: number[]; bottomRight: number[]; }; - currentElastic: number[]; + currentElastic: ElasticPoint[]; disabledTeeth: number[]; - handleToothClick: (number: number) => void; + handleToothClick: (number: number, outside: boolean) => void; handleToothToggle: (number: number) => void; - setToothRef: (number: number, ref: HTMLButtonElement) => void; + setToothRef: (number: number, outside: boolean, ref: HTMLButtonElement) => void; isMirrorView: boolean; svgRef: React.MutableRefObject; }; @@ -43,7 +44,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={0} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.includes(tooth)} + selected={currentElastic.some((e) => e.tooth === tooth)} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -60,7 +61,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={0} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.includes(tooth)} + selected={currentElastic.some((e) => e.tooth === tooth)} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -77,7 +78,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={1} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.includes(tooth)} + selected={currentElastic.some((e) => e.tooth === tooth)} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -94,7 +95,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={1} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.includes(tooth)} + selected={currentElastic.some((e) => e.tooth === tooth)} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index 2e931b2..d5a3556 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -53,11 +53,11 @@ const teethModifications: TeethMofidication = { type ToothMemo = { number: number; row: number; - onClick: (number: number) => void; + onClick: (number: number, outside: boolean) => void; onToggle: (number: number) => void; selected: boolean; disabled: boolean; - setRef: (number: number, ref: HTMLButtonElement) => void; + setRef: (number: number, outside: boolean, ref: HTMLButtonElement) => void; } const getToothColor = (number: number, selected: boolean, disabled: boolean) => { @@ -81,22 +81,16 @@ const getToothSideColor = (number: number, selected: boolean, disabled: boolean) }; const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, setRef, isMirrorView }: ToothMemo & { isMirrorView: boolean }) => { - const handleClick = (e: React.MouseEvent) => { - // get click location on the tooth (y coordinate is most important) - // we want to know if the click was on the top or bottom half of the tooth - const rect = e.currentTarget.getBoundingClientRect(); - const y = e.clientY - rect.top; - const half = rect.height / 2; - // @ts-ignore - const _top = y < half; + const handleClick = (e: React.MouseEvent, outside: boolean) => { - // TODO: implement top/bottom half click handling + // Set ref based on the element clicked + setRef(number, outside, e.currentTarget as HTMLButtonElement); if (FEATURES.DISABLE_TEETH && e.ctrlKey || e.metaKey) { onToggle(number); } else { - onClick(number); + onClick(number, outside); } }; @@ -130,8 +124,8 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, getToothSideColor(number, selected, disabled) )} style={{ top: 0, left: 0 }} - ref={(el: HTMLButtonElement) => setRef(number, el)} - onClick={handleClick} + ref={(el: HTMLButtonElement) => setRef(number, true, el)} + onClick={(e) => handleClick(e, true)} /> @@ -143,8 +137,8 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, getToothSideColor(number, selected, disabled) )} style={{ bottom: 0, left: 0 }} - ref={(el: HTMLButtonElement) => setRef(number, el)} - onClick={handleClick} + ref={(el: HTMLButtonElement) => setRef(number, false, el)} + onClick={(e) => handleClick(e, false)} />
diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..1186949 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,25 @@ +export type TimeType = "a" | "d" | "n"; + +export type ElasticPoint = { + tooth: number; + outside: boolean; +} + +export type Elastic = { + teeth: ElasticPoint[]; + type: number; + + /* + * Which time of day the elastic should be worn + * 24h: 24 hours a day + * daytime: only during the day + * nighttime: only during the night + * (emojis are used for display) + */ + time: TimeType; + + /* + * Placed on the outer side of the teeth or inner side + */ + outer?: boolean; +}; \ No newline at end of file From 1ac6c076dfd933dc0645d4334d9df6018c2f7a6a Mon Sep 17 00:00:00 2001 From: Drikus Roor Date: Sun, 26 Jan 2025 13:08:06 +0100 Subject: [PATCH 11/14] feat: Update tooth rotation values for improved alignment and add visual divider in Tooth component --- src/App.tsx | 6 ++++-- src/components/Tooth.tsx | 44 ++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 57adf7e..9d5e7e7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -219,10 +219,12 @@ const ElasticPlacer = () => { if (!rect) return null; const baseX = rect.left - svgRect.left + rect.width / 2; - const x = isMirrorView ? svgRect.width - baseX : baseX; + let x = isMirrorView ? svgRect.width - baseX : baseX; + let y = rect.top - svgRect.top + rect.height / 2; + return { x, - y: rect.top - svgRect.top + rect.height / 2, + y, }; }) .filter((point) => point !== null) as { x: number; y: number }[]; diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index d5a3556..6d9f7c7 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -17,35 +17,35 @@ type TeethMofidication = { const teethModifications: TeethMofidication = { 18: { className: 'translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, 17: { className: 'translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, - 16: { className: 'translate-y-0 ml-2', rotation: '-rotate-[75deg]', counterRotation: 'rotate-[75deg]' }, - 15: { className: 'translate-y-0 ml-4', rotation: '-rotate-[60deg]', counterRotation: 'rotate-[60deg]' }, - 14: { className: 'translate-y-0 ml-6', rotation: '-rotate-[60deg]', counterRotation: 'rotate-[60deg]' }, - 13: { className: 'translate-y-0 ml-10', rotation: '-rotate-45', counterRotation: 'rotate-45' }, + 16: { className: 'translate-y-0 ml-2', rotation: '-rotate-[85deg]', counterRotation: 'rotate-[75deg]' }, + 15: { className: 'translate-y-0 ml-4', rotation: '-rotate-[80deg]', counterRotation: 'rotate-[60deg]' }, + 14: { className: 'translate-y-0 ml-6', rotation: '-rotate-[75deg]', counterRotation: 'rotate-[60deg]' }, + 13: { className: 'translate-y-0 ml-10', rotation: '-rotate-[60deg]', counterRotation: 'rotate-45' }, 12: { className: 'translate-y-4 ml-22', rotation: '-rotate-[30deg]', counterRotation: 'rotate-[30deg]' }, - 11: { className: 'translate-y-16 ml-37', rotation: '-rotate-[10deg]', counterRotation: 'rotate-[10deg]' }, - 21: { className: 'translate-y-16 mr-37', rotation: 'rotate-[10deg]', counterRotation: '-rotate-[10deg]' }, + 11: { className: 'translate-y-16 ml-37', rotation: '-rotate-[5deg]', counterRotation: 'rotate-[10deg]' }, + 21: { className: 'translate-y-16 mr-37', rotation: 'rotate-[5deg]', counterRotation: '-rotate-[10deg]' }, 22: { className: 'translate-y-4 mr-22', rotation: 'rotate-[30deg]', counterRotation: '-rotate-[30deg]' }, - 23: { className: 'translate-y-0 mr-10', rotation: 'rotate-45', counterRotation: '-rotate-45' }, - 24: { className: 'translate-y-0 mr-6', rotation: 'rotate-[60deg]', counterRotation: '-rotate-[60deg]' }, - 25: { className: 'translate-y-0 mr-4', rotation: 'rotate-[60deg]', counterRotation: '-rotate-[60deg]' }, - 26: { className: 'translate-y-0 mr-2', rotation: 'rotate-[75deg]', counterRotation: '-rotate-[75deg]' }, + 23: { className: 'translate-y-0 mr-10', rotation: 'rotate-[60deg]', counterRotation: '-rotate-45' }, + 24: { className: 'translate-y-0 mr-6', rotation: 'rotate-[75deg]', counterRotation: '-rotate-[60deg]' }, + 25: { className: 'translate-y-0 mr-4', rotation: 'rotate-[80deg]', counterRotation: '-rotate-[60deg]' }, + 26: { className: 'translate-y-0 mr-2', rotation: 'rotate-[85deg]', counterRotation: '-rotate-[75deg]' }, 27: { className: 'translate-y-0', rotation: 'rotate-90', counterRotation: '-rotate-90' }, 28: { className: 'translate-y-0', rotation: 'rotate-90', counterRotation: '-rotate-90' }, 48: { className: '-translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, 47: { className: 'translate-y-0', rotation: '-rotate-90', counterRotation: 'rotate-90' }, - 46: { className: 'translate-y-0 ml-2', rotation: '-rotate-[105deg]', counterRotation: 'rotate-[105deg]' }, - 45: { className: 'translate-y-0 ml-4', rotation: '-rotate-[120deg]', counterRotation: 'rotate-[120deg]' }, - 44: { className: 'translate-y-0 ml-6', rotation: '-rotate-[120deg]', counterRotation: 'rotate-[120deg]' }, - 43: { className: 'translate-y-0 ml-10', rotation: '-rotate-[135deg]', counterRotation: 'rotate-[135deg]' }, + 46: { className: 'translate-y-0 ml-2', rotation: '-rotate-[95deg]', counterRotation: 'rotate-[105deg]' }, + 45: { className: 'translate-y-0 ml-4', rotation: '-rotate-[100deg]', counterRotation: 'rotate-[120deg]' }, + 44: { className: 'translate-y-0 ml-6', rotation: '-rotate-[105deg]', counterRotation: 'rotate-[120deg]' }, + 43: { className: 'translate-y-0 ml-10', rotation: '-rotate-[120deg]', counterRotation: 'rotate-[135deg]' }, 42: { className: '-translate-y-4 ml-20', rotation: '-rotate-[150deg]', counterRotation: 'rotate-[150deg]' }, - 41: { className: '-translate-y-16 ml-35', rotation: '-rotate-[170deg]', counterRotation: 'rotate-[170deg]' }, - 31: { className: '-translate-y-16 mr-35', rotation: 'rotate-[170deg]', counterRotation: '-rotate-[170deg]' }, + 41: { className: '-translate-y-16 ml-35', rotation: '-rotate-[175deg]', counterRotation: 'rotate-[170deg]' }, + 31: { className: '-translate-y-16 mr-35', rotation: 'rotate-[175deg]', counterRotation: '-rotate-[170deg]' }, 32: { className: '-translate-y-4 mr-20', rotation: 'rotate-[150deg]', counterRotation: '-rotate-[150deg]' }, - 33: { className: 'translate-y-0 mr-10', rotation: 'rotate-[135deg]', counterRotation: '-rotate-[135deg]' }, - 34: { className: 'translate-y-0 mr-6', rotation: 'rotate-[120deg]', counterRotation: '-rotate-[120deg]' }, - 35: { className: 'translate-y-0 mr-4', rotation: 'rotate-[120deg]', counterRotation: '-rotate-[120deg]' }, - 36: { className: 'translate-y-0 mr-2', rotation: 'rotate-[105deg]', counterRotation: '-rotate-[105deg]' }, + 33: { className: 'translate-y-0 mr-10', rotation: 'rotate-[120deg]', counterRotation: '-rotate-[135deg]' }, + 34: { className: 'translate-y-0 mr-6', rotation: 'rotate-[105deg]', counterRotation: '-rotate-[120deg]' }, + 35: { className: 'translate-y-0 mr-4', rotation: 'rotate-[100deg]', counterRotation: '-rotate-[120deg]' }, + 36: { className: 'translate-y-0 mr-2', rotation: 'rotate-[95deg]', counterRotation: '-rotate-[105deg]' }, 37: { className: 'translate-y-0', rotation: 'rotate-90', counterRotation: '-rotate-90' }, 38: { className: 'translate-y-0', rotation: 'rotate-90', counterRotation: '-rotate-90' }, } @@ -104,6 +104,10 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, ${teethModification?.className || ''} ${teethModification?.rotation || ''} `}> + + {/* thin line that divides the tooth in half */} +
+ {FEATURES.TOOTH_ICONS && (
Date: Sun, 26 Jan 2025 13:26:56 +0100 Subject: [PATCH 12/14] feat: Update TeethGrid and Tooth components to use currentElastic for selection state and improve styling --- src/App.tsx | 13 +++++++++++-- src/components/TeethGrid.tsx | 8 ++++---- src/components/Tooth.tsx | 27 ++++++++++++++++----------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9d5e7e7..55ed57e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -219,7 +219,7 @@ const ElasticPlacer = () => { if (!rect) return null; const baseX = rect.left - svgRect.left + rect.width / 2; - let x = isMirrorView ? svgRect.width - baseX : baseX; + let x = isMirrorView ? svgRect.width - baseX : baseX; let y = rect.top - svgRect.top + rect.height / 2; return { @@ -408,7 +408,16 @@ const ElasticPlacer = () => {
{/* Teeth Grid */} - +
diff --git a/src/components/TeethGrid.tsx b/src/components/TeethGrid.tsx index e537d55..374ed16 100644 --- a/src/components/TeethGrid.tsx +++ b/src/components/TeethGrid.tsx @@ -44,7 +44,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={0} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.some((e) => e.tooth === tooth)} + currentElastic={currentElastic} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -61,7 +61,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={0} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.some((e) => e.tooth === tooth)} + currentElastic={currentElastic} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -78,7 +78,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={1} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.some((e) => e.tooth === tooth)} + currentElastic={currentElastic} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } @@ -95,7 +95,7 @@ export function TeethGrid({ teethLayout, currentElastic, disabledTeeth, handleTo row={1} onClick={handleToothClick} onToggle={handleToothToggle} - selected={currentElastic.some((e) => e.tooth === tooth)} + currentElastic={currentElastic} disabled={ FEATURES.DISABLE_TEETH && disabledTeeth.includes(tooth) } diff --git a/src/components/Tooth.tsx b/src/components/Tooth.tsx index 6d9f7c7..4ca240c 100644 --- a/src/components/Tooth.tsx +++ b/src/components/Tooth.tsx @@ -2,6 +2,7 @@ import React from "react"; import { FEATURES } from "../config"; import ToothIcon, { TOOTH_TYPE_MAP } from "./ToothIcon"; import { classNames } from "../util/class-names"; +import { ElasticPoint } from "../types"; const middleIncisors = [11, 21, 31, 41]; const canines = [13, 23, 33, 43]; @@ -55,7 +56,7 @@ type ToothMemo = { row: number; onClick: (number: number, outside: boolean) => void; onToggle: (number: number) => void; - selected: boolean; + currentElastic: ElasticPoint[]; disabled: boolean; setRef: (number: number, outside: boolean, ref: HTMLButtonElement) => void; } @@ -70,17 +71,17 @@ const getToothColor = (number: number, selected: boolean, disabled: boolean) => return 'bg-yellow-50'; }; -const getToothSideColor = (number: number, selected: boolean, disabled: boolean) => { +const getToothSideClassNames = (number: number, selected: boolean, disabled: boolean) => { if (disabled && FEATURES.DISABLE_TEETH) return 'bg-gray-300'; - if (selected) return 'bg-blue-500'; + if (selected) return 'border border-blue-900 bg-blue-500 bg-opacity-50'; if (FEATURES.HIGHLIGHT_SPECIAL_TEETH) { if (middleIncisors.includes(number)) return 'bg-green-200'; if (canines.includes(number)) return 'bg-purple-200'; } - return 'bg-yellow-50'; + return ''; }; -const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, setRef, isMirrorView }: ToothMemo & { isMirrorView: boolean }) => { +const Tooth = React.memo(({ number, row, onClick, onToggle, currentElastic, disabled, setRef, isMirrorView }: ToothMemo & { isMirrorView: boolean }) => { const handleClick = (e: React.MouseEvent, outside: boolean) => { @@ -94,13 +95,17 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled, } }; + const isSelected = (tooth: number, outside: boolean) => { + return currentElastic.some(e => e.tooth === tooth && e.outside === outside); + }; + const toothType = TOOTH_TYPE_MAP[number]; const teethModification = teethModifications[number]; return (
@@ -112,7 +117,7 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled,
)} @@ -124,8 +129,8 @@ const Tooth = React.memo(({ number, row, onClick, onToggle, selected, disabled,