npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@salloomd/teeth-selector

v0.0.5

Published

A headless React component for dental tooth selection with bridge support. Follows the [shadcn/ui](https://ui.shadcn.com/) code style and patterns.

Readme

@salloomd/teeth-selector

A headless React component for dental tooth selection with bridge support. Follows the shadcn/ui code style and patterns.

Installation

bun add @salloomd/teeth-selector
# or
npm install @salloomd/teeth-selector

Features

  • 🎨 Headless - Bring your own styles
  • 🦷 Complete dental chart - All 32 teeth with FDI numbering
  • 🌉 Bridge support - Select dental bridges between adjacent teeth
  • 📱 Touch support - Drag selection works on mobile
  • 🎯 Fully typed - Written in TypeScript
  • Render props - Full control over rendering

Usage

Basic Example (with Tailwind CSS)

import { TeethSelector, TeethChart } from "@salloomd/teeth-selector";

function MyDentalForm() {
  return (
    <TeethSelector
      onChange={(selectedTeeth, bridges) => {
        console.log("Selected:", selectedTeeth);
        console.log("Bridges:", bridges);
      }}
    >
      {({ selectedTeeth, clearAll, selectUpper, selectLower }) => (
        <div className="space-y-4">
          <div className="flex gap-2">
            <button onClick={selectUpper}>Select Upper</button>
            <button onClick={selectLower}>Select Lower</button>
            {selectedTeeth.length > 0 && (
              <button onClick={clearAll}>Clear All</button>
            )}
          </div>

          <TeethChart
            width={300}
            height={500}
            renderTooth={({ tooth, isSelected, isInDragRange }, defaultEl) => (
              <path
                key={tooth.id}
                id={`teeth-${tooth.id}`}
                d={tooth.d}
                className={`
                  cursor-pointer transition-colors
                  ${isSelected ? "fill-amber-400 stroke-gray-600" : "fill-white stroke-gray-400"}
                  ${isInDragRange ? "fill-blue-300" : ""}
                  hover:fill-amber-200
                `}
                onClick={defaultEl.props.onClick}
                onMouseDown={defaultEl.props.onMouseDown}
                onMouseEnter={defaultEl.props.onMouseEnter}
              />
            )}
            renderBridge={({ fromId, toId, exists, position }, defaultEl) => (
              <g key={`${fromId}-${toId}`}>
                {exists && (
                  <line
                    x1={position.x1}
                    y1={position.y1}
                    x2={position.x2}
                    y2={position.y2}
                    stroke="#ff6b6b"
                    strokeWidth={4}
                    strokeDasharray="5,3"
                  />
                )}
                <circle
                  cx={position.midX}
                  cy={position.midY}
                  r={8}
                  className={`
                    cursor-pointer transition-all
                    ${
                      exists
                        ? "fill-blue-500 stroke-white stroke-2"
                        : "fill-gray-200 stroke-gray-400 opacity-0 hover:opacity-100"
                    }
                  `}
                  onClick={defaultEl.props.children[1].props.onClick}
                />
              </g>
            )}
          />
        </div>
      )}
    </TeethSelector>
  );
}

TeethPreview (Read-only)

import { TeethPreview } from "@salloomd/teeth-selector";

function Preview() {
  // String format: individual teeth or bridged ranges
  // "11, 21, [31, 33]" means teeth 11, 21, and bridged 31-32-33
  return (
    <TeethPreview
      teeth="11, 21, [31, 33]"
      width={150}
      height={250}
      renderTooth={({ tooth, isSelected }, defaultEl) => (
        <path
          key={tooth.id}
          id={`teeth-preview-${tooth.id}`}
          d={tooth.d}
          className={
            isSelected
              ? "fill-amber-400 stroke-gray-600"
              : "fill-white stroke-gray-400"
          }
        />
      )}
    />
  );
}

Controlled Mode

import { useState } from "react";
import {
  TeethSelector,
  TeethChart,
  type Tooth,
} from "@salloomd/teeth-selector";

function ControlledExample() {
  const [selectedTeeth, setSelectedTeeth] = useState<number[]>([11, 21]);
  const [bridges, setBridges] = useState<number[][]>([[11, 21]]);

  return (
    <TeethSelector
      selectedTeeth={selectedTeeth}
      bridges={bridges}
      onSelectionChange={(teeth: Tooth[]) => {
        setSelectedTeeth(teeth.map((t) => t.id));
      }}
      onBridgeChange={setBridges}
    >
      {(props) => (
        <TeethChart
          renderTooth={({ tooth, isSelected }, defaultEl) => (
            <path
              key={tooth.id}
              id={`teeth-${tooth.id}`}
              d={tooth.d}
              style={{
                fill: isSelected ? "#fbbf24" : "#fff",
                stroke: isSelected ? "#4b5563" : "#9ca3af",
                cursor: "pointer",
              }}
              onClick={defaultEl.props.onClick}
              onMouseDown={defaultEl.props.onMouseDown}
              onMouseEnter={defaultEl.props.onMouseEnter}
            />
          )}
        />
      )}
    </TeethSelector>
  );
}

API Reference

TeethSelector

The root component that manages selection state.

| Prop | Type | Description | | ---------------------- | ------------------------------------------------ | ----------------------------- | | defaultSelectedTeeth | number[] | Initial selected teeth IDs | | defaultBridges | number[][] | Initial bridges as pairs | | selectedTeeth | number[] | Controlled selected teeth | | bridges | number[][] | Controlled bridges | | onSelectionChange | (teeth: Tooth[]) => void | Called when selection changes | | onBridgeChange | (bridges: number[][]) => void | Called when bridges change | | onChange | (teeth: Tooth[], bridges: number[][]) => void | Unified change callback | | children | (props: TeethSelectorRenderProps) => ReactNode | Render function |

Render Props

interface TeethSelectorRenderProps {
  selectedTeeth: number[];
  bridges: number[][];
  toggleTooth: (toothId: number) => void;
  toggleBridge: (fromId: number, toId: number) => void;
  selectUpper: () => void;
  selectLower: () => void;
  selectRange: (teethIds: number[]) => void;
  clearAll: () => void;
  isSelected: (toothId: number) => boolean;
  hasBridge: (fromId: number, toId: number) => boolean;
  getSelectedTeethData: () => Tooth[];
}

TeethChart

SVG component for rendering the interactive tooth chart. Must be used inside TeethSelector.

| Prop | Type | Default | Description | | --------------------- | ---------- | ------- | ---------------------- | | width | number | 300 | SVG width | | height | number | 500 | SVG height | | className | string | - | Additional class names | | enableDragSelection | boolean | true | Enable drag to select | | renderTooth | function | - | Custom tooth renderer | | renderBridge | function | - | Custom bridge renderer |

TeethPreview

Read-only component for displaying a teeth selection string.

| Prop | Type | Default | Description | | -------------- | ---------- | ------- | --------------------------------------- | | teeth | string | - | Selection string (e.g., "11, [21, 23]") | | width | number | 150 | SVG width | | height | number | 250 | SVG height | | className | string | - | Additional class names | | renderTooth | function | - | Custom tooth renderer | | renderBridge | function | - | Custom bridge renderer |

Utilities

import {
  teethData, // All 32 teeth with metadata
  adjacentTeethPairs, // Valid bridge pairs
  getTeethInRange, // Get teeth between two IDs
  parseTeethSelection, // Parse string to selection
  formatTeethSelection, // Format selection to string
} from "@salloomd/teeth-selector";

Tooth Data Structure

interface Tooth {
  order: number; // Display order (0-31)
  id: number; // FDI tooth number (11-48)
  name: string; // Full name
  position: "upper" | "lower";
  side: "left" | "right";
  d: string; // SVG path data
}

Dental Numbering (FDI)

The component uses FDI World Dental Federation notation:

  • Upper Right: 18-11 (third molar to central incisor)
  • Upper Left: 21-28 (central incisor to third molar)
  • Lower Left: 38-31 (third molar to central incisor)
  • Lower Right: 41-48 (central incisor to third molar)

License

MIT