@salloomd/teeth-selector 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +277 -0
  2. package/dist/index.js +1875 -0
  3. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # @salloomd/teeth-selector
2
+
3
+ A headless React component for dental tooth selection with bridge support. Follows the [shadcn/ui](https://ui.shadcn.com/) code style and patterns.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @salloomd/teeth-selector
9
+ # or
10
+ npm install @salloomd/teeth-selector
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - 🎨 **Headless** - Bring your own styles
16
+ - 🦷 **Complete dental chart** - All 32 teeth with FDI numbering
17
+ - 🌉 **Bridge support** - Select dental bridges between adjacent teeth
18
+ - 📱 **Touch support** - Drag selection works on mobile
19
+ - 🎯 **Fully typed** - Written in TypeScript
20
+ - âš¡ **Render props** - Full control over rendering
21
+
22
+ ## Usage
23
+
24
+ ### Basic Example (with Tailwind CSS)
25
+
26
+ ```tsx
27
+ import { TeethSelector, TeethChart } from "@salloomd/teeth-selector";
28
+
29
+ function MyDentalForm() {
30
+ return (
31
+ <TeethSelector
32
+ onChange={(selectedTeeth, bridges) => {
33
+ console.log("Selected:", selectedTeeth);
34
+ console.log("Bridges:", bridges);
35
+ }}
36
+ >
37
+ {({ selectedTeeth, clearAll, selectUpper, selectLower }) => (
38
+ <div className="space-y-4">
39
+ <div className="flex gap-2">
40
+ <button onClick={selectUpper}>Select Upper</button>
41
+ <button onClick={selectLower}>Select Lower</button>
42
+ {selectedTeeth.length > 0 && (
43
+ <button onClick={clearAll}>Clear All</button>
44
+ )}
45
+ </div>
46
+
47
+ <TeethChart
48
+ width={300}
49
+ height={500}
50
+ renderTooth={({ tooth, isSelected, isInDragRange }, defaultEl) => (
51
+ <path
52
+ key={tooth.id}
53
+ id={`teeth-${tooth.id}`}
54
+ d={tooth.d}
55
+ className={`
56
+ cursor-pointer transition-colors
57
+ ${isSelected ? "fill-amber-400 stroke-gray-600" : "fill-white stroke-gray-400"}
58
+ ${isInDragRange ? "fill-blue-300" : ""}
59
+ hover:fill-amber-200
60
+ `}
61
+ onClick={defaultEl.props.onClick}
62
+ onMouseDown={defaultEl.props.onMouseDown}
63
+ onMouseEnter={defaultEl.props.onMouseEnter}
64
+ />
65
+ )}
66
+ renderBridge={({ fromId, toId, exists, position }, defaultEl) => (
67
+ <g key={`${fromId}-${toId}`}>
68
+ {exists && (
69
+ <line
70
+ x1={position.x1}
71
+ y1={position.y1}
72
+ x2={position.x2}
73
+ y2={position.y2}
74
+ stroke="#ff6b6b"
75
+ strokeWidth={4}
76
+ strokeDasharray="5,3"
77
+ />
78
+ )}
79
+ <circle
80
+ cx={position.midX}
81
+ cy={position.midY}
82
+ r={8}
83
+ className={`
84
+ cursor-pointer transition-all
85
+ ${
86
+ exists
87
+ ? "fill-blue-500 stroke-white stroke-2"
88
+ : "fill-gray-200 stroke-gray-400 opacity-0 hover:opacity-100"
89
+ }
90
+ `}
91
+ onClick={defaultEl.props.children[1].props.onClick}
92
+ />
93
+ </g>
94
+ )}
95
+ />
96
+ </div>
97
+ )}
98
+ </TeethSelector>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### TeethPreview (Read-only)
104
+
105
+ ```tsx
106
+ import { TeethPreview } from "@salloomd/teeth-selector";
107
+
108
+ function Preview() {
109
+ // String format: individual teeth or bridged ranges
110
+ // "11, 21, [31, 33]" means teeth 11, 21, and bridged 31-32-33
111
+ return (
112
+ <TeethPreview
113
+ teeth="11, 21, [31, 33]"
114
+ width={150}
115
+ height={250}
116
+ renderTooth={({ tooth, isSelected }, defaultEl) => (
117
+ <path
118
+ key={tooth.id}
119
+ id={`teeth-preview-${tooth.id}`}
120
+ d={tooth.d}
121
+ className={
122
+ isSelected
123
+ ? "fill-amber-400 stroke-gray-600"
124
+ : "fill-white stroke-gray-400"
125
+ }
126
+ />
127
+ )}
128
+ />
129
+ );
130
+ }
131
+ ```
132
+
133
+ ### Controlled Mode
134
+
135
+ ```tsx
136
+ import { useState } from "react";
137
+ import {
138
+ TeethSelector,
139
+ TeethChart,
140
+ type Tooth,
141
+ } from "@salloomd/teeth-selector";
142
+
143
+ function ControlledExample() {
144
+ const [selectedTeeth, setSelectedTeeth] = useState<number[]>([11, 21]);
145
+ const [bridges, setBridges] = useState<number[][]>([[11, 21]]);
146
+
147
+ return (
148
+ <TeethSelector
149
+ selectedTeeth={selectedTeeth}
150
+ bridges={bridges}
151
+ onSelectionChange={(teeth: Tooth[]) => {
152
+ setSelectedTeeth(teeth.map((t) => t.id));
153
+ }}
154
+ onBridgeChange={setBridges}
155
+ >
156
+ {(props) => (
157
+ <TeethChart
158
+ renderTooth={({ tooth, isSelected }, defaultEl) => (
159
+ <path
160
+ key={tooth.id}
161
+ id={`teeth-${tooth.id}`}
162
+ d={tooth.d}
163
+ style={{
164
+ fill: isSelected ? "#fbbf24" : "#fff",
165
+ stroke: isSelected ? "#4b5563" : "#9ca3af",
166
+ cursor: "pointer",
167
+ }}
168
+ onClick={defaultEl.props.onClick}
169
+ onMouseDown={defaultEl.props.onMouseDown}
170
+ onMouseEnter={defaultEl.props.onMouseEnter}
171
+ />
172
+ )}
173
+ />
174
+ )}
175
+ </TeethSelector>
176
+ );
177
+ }
178
+ ```
179
+
180
+ ## API Reference
181
+
182
+ ### TeethSelector
183
+
184
+ The root component that manages selection state.
185
+
186
+ | Prop | Type | Description |
187
+ | ---------------------- | ------------------------------------------------ | ----------------------------- |
188
+ | `defaultSelectedTeeth` | `number[]` | Initial selected teeth IDs |
189
+ | `defaultBridges` | `number[][]` | Initial bridges as pairs |
190
+ | `selectedTeeth` | `number[]` | Controlled selected teeth |
191
+ | `bridges` | `number[][]` | Controlled bridges |
192
+ | `onSelectionChange` | `(teeth: Tooth[]) => void` | Called when selection changes |
193
+ | `onBridgeChange` | `(bridges: number[][]) => void` | Called when bridges change |
194
+ | `onChange` | `(teeth: Tooth[], bridges: number[][]) => void` | Unified change callback |
195
+ | `children` | `(props: TeethSelectorRenderProps) => ReactNode` | Render function |
196
+
197
+ #### Render Props
198
+
199
+ ```ts
200
+ interface TeethSelectorRenderProps {
201
+ selectedTeeth: number[];
202
+ bridges: number[][];
203
+ toggleTooth: (toothId: number) => void;
204
+ toggleBridge: (fromId: number, toId: number) => void;
205
+ selectUpper: () => void;
206
+ selectLower: () => void;
207
+ selectRange: (teethIds: number[]) => void;
208
+ clearAll: () => void;
209
+ isSelected: (toothId: number) => boolean;
210
+ hasBridge: (fromId: number, toId: number) => boolean;
211
+ getSelectedTeethData: () => Tooth[];
212
+ }
213
+ ```
214
+
215
+ ### TeethChart
216
+
217
+ SVG component for rendering the interactive tooth chart. Must be used inside `TeethSelector`.
218
+
219
+ | Prop | Type | Default | Description |
220
+ | --------------------- | ---------- | ------- | ---------------------- |
221
+ | `width` | `number` | `300` | SVG width |
222
+ | `height` | `number` | `500` | SVG height |
223
+ | `className` | `string` | - | Additional class names |
224
+ | `enableDragSelection` | `boolean` | `true` | Enable drag to select |
225
+ | `renderTooth` | `function` | - | Custom tooth renderer |
226
+ | `renderBridge` | `function` | - | Custom bridge renderer |
227
+
228
+ ### TeethPreview
229
+
230
+ Read-only component for displaying a teeth selection string.
231
+
232
+ | Prop | Type | Default | Description |
233
+ | -------------- | ---------- | ------- | --------------------------------------- |
234
+ | `teeth` | `string` | - | Selection string (e.g., "11, [21, 23]") |
235
+ | `width` | `number` | `150` | SVG width |
236
+ | `height` | `number` | `250` | SVG height |
237
+ | `className` | `string` | - | Additional class names |
238
+ | `renderTooth` | `function` | - | Custom tooth renderer |
239
+ | `renderBridge` | `function` | - | Custom bridge renderer |
240
+
241
+ ### Utilities
242
+
243
+ ```ts
244
+ import {
245
+ teethData, // All 32 teeth with metadata
246
+ adjacentTeethPairs, // Valid bridge pairs
247
+ getTeethInRange, // Get teeth between two IDs
248
+ parseTeethSelection, // Parse string to selection
249
+ formatTeethSelection, // Format selection to string
250
+ } from "@salloomd/teeth-selector";
251
+ ```
252
+
253
+ ## Tooth Data Structure
254
+
255
+ ```ts
256
+ interface Tooth {
257
+ order: number; // Display order (0-31)
258
+ id: number; // FDI tooth number (11-48)
259
+ name: string; // Full name
260
+ position: "upper" | "lower";
261
+ side: "left" | "right";
262
+ d: string; // SVG path data
263
+ }
264
+ ```
265
+
266
+ ## Dental Numbering (FDI)
267
+
268
+ The component uses FDI World Dental Federation notation:
269
+
270
+ - **Upper Right**: 18-11 (third molar to central incisor)
271
+ - **Upper Left**: 21-28 (central incisor to third molar)
272
+ - **Lower Left**: 38-31 (third molar to central incisor)
273
+ - **Lower Right**: 41-48 (central incisor to third molar)
274
+
275
+ ## License
276
+
277
+ MIT