@bezeldigital/medidrive-ui-kit
v0.3.1
Published
Production-ready React component library for building NEMT (Non-Emergency Medical Transportation) interfaces. Extracted from the MediDrive admin portal — battle-tested on 15,000+ daily trips.
Readme
@bezeldigital/medidrive-ui-kit
Production-ready React component library for building NEMT (Non-Emergency Medical Transportation) interfaces. Extracted from the MediDrive admin portal — battle-tested on 15,000+ daily trips.
Zero Next.js dependencies. Works in any React environment: Chrome extensions, Vite apps, CRA, Electron, etc.
Install
npm install @bezeldigital/medidrive-ui-kitPeer Dependencies
npm install react react-dom @radix-ui/react-dialog @radix-ui/react-dropdown-menu \
@radix-ui/react-label @radix-ui/react-select @radix-ui/react-slot \
@radix-ui/react-tooltip @remixicon/reactOptional (only needed if you use specific features):
# Maps (RouteMapPreview)
npm install mapbox-gl
# Address autocomplete
npm install @react-google-maps/api
# URL-synced filter state (falls back to useState if not installed)
npm install nuqsQuick Start
Wrap your app with MediDriveProvider to inject API credentials and platform adapters:
import { MediDriveProvider } from "@bezeldigital/medidrive-ui-kit"
function App() {
return (
<MediDriveProvider
apiKey="your-api-key"
backendUrl="https://medidrive-aetna-backend.fly.dev"
googleMapsApiKey="your-google-maps-key" // optional
mapboxToken="your-mapbox-token" // optional
getAccessToken={() => fetchAuthToken()} // optional — for authenticated requests
navigate={(path) => router.push(path)} // optional — platform-specific navigation
>
<YourApp />
</MediDriveProvider>
)
}All components read config from the provider context — no direct API imports needed.
Entry Points
The library ships 9 independent entry points. Import only what you need for optimal tree-shaking:
| Entry Point | Import Path | What's Inside |
|-------------|-------------|---------------|
| Core | @bezeldigital/medidrive-ui-kit | MediDriveProvider, useMediDriveConfig |
| Primitives | @bezeldigital/medidrive-ui-kit/primitives | Button, Input, Badge, Card, Dialog, Drawer, Select, Tooltip, etc. |
| Data Toolkit | @bezeldigital/medidrive-ui-kit/data-toolkit | DataPage, DataTable, FilterBar, SearchInput, Pagination, etc. |
| Trips | @bezeldigital/medidrive-ui-kit/trips | TripCreationWizard (8-step), step components |
| Maps | @bezeldigital/medidrive-ui-kit/maps | RouteMapPreview |
| Address | @bezeldigital/medidrive-ui-kit/address | AddressAutocomplete, FacilityAutocomplete |
| Adapters | @bezeldigital/medidrive-ui-kit/adapters | V1 legacy backend data transformers |
| Utils | @bezeldigital/medidrive-ui-kit/utils | cx, calculatePickupTime, resolveTransportMode, getTreatmentDefaults |
| Tailwind | @bezeldigital/medidrive-ui-kit/tailwind | MediDrive Tailwind CSS preset (colors, typography, spacing) |
Provider Config Reference
interface MediDriveConfig {
apiKey: string // Required — API authentication key
backendUrl: string // Required — Backend base URL
googleMapsApiKey?: string // For AddressAutocomplete
mapboxToken?: string // For RouteMapPreview
navigate?: (path: string) => void // Platform-specific navigation
getAccessToken?: () => Promise<string> // Dynamic auth token retrieval
Link?: React.ComponentType<LinkProps> // Platform-specific link component
useUrlState?: boolean // Enable URL-synced filter state (needs nuqs)
}Usage Examples
UI Primitives
import { Button, Badge, Card, Input, Dialog, DialogContent, DialogTrigger } from "@bezeldigital/medidrive-ui-kit/primitives"
<Card>
<Badge variant="success">Approved</Badge>
<Input placeholder="Search members..." />
<Button variant="primary" size="sm">Book Trip</Button>
</Card>14 components: Button, Input, Badge, Card, Dialog, Drawer, Callout, Label, Skeleton, DropdownMenu, Divider, Select, Tooltip, Textarea.
Data Toolkit (Tables + Filters)
Build Stripe-level data pages with config-driven filters, search, and tables:
import { DataPage, createDataPageConfig } from "@bezeldigital/medidrive-ui-kit/data-toolkit"
const config = createDataPageConfig({
title: "Trips",
searchPlaceholder: "Search by member or trip ID...",
filters: [
{ key: "status", label: "Status", type: "multi-select", options: [
{ value: "scheduled", label: "Scheduled" },
{ value: "completed", label: "Completed" },
{ value: "cancelled", label: "Cancelled" },
]},
{ key: "date", label: "Date", type: "date-range" },
],
quickTabs: [
{ key: "all", label: "All" },
{ key: "today", label: "Today" },
{ key: "urgent", label: "Urgent" },
],
})
<DataPage config={config} data={trips} columns={columns} isLoading={isLoading} />20+ components: DataPage, DataTable, FilterBar, FilterPill, FilterPopover, QuickTabs, QuickToggles, SearchInput, Pagination, EmptyState, ExportButton, and more.
Trip Creation Wizard
Full 8-step NEMT trip booking flow:
import { TripCreationWizard } from "@bezeldigital/medidrive-ui-kit/trips"
<TripCreationWizard
onComplete={(tripData) => submitTrip(tripData)}
onCancel={() => navigate("/trips")}
defaultMemberId="member-123"
/>Steps: Member Lookup > Journey > Appointment > Transport > Schedule > Driver Selection > Review > Confirmation.
Each step is also exported individually for custom flows:
import { MemberLookupStep, JourneyStep, ReviewStep } from "@bezeldigital/medidrive-ui-kit/trips"V1 Legacy Adapters
Transform old backend snake_case payloads into ui-kit prop shapes:
import { v1TripToWizardProps, v1MemberToLookupProps } from "@bezeldigital/medidrive-ui-kit/adapters"
// Old backend returns snake_case
const v1Trip = await fetch("/api/v1/trips/123").then(r => r.json())
// Transform to ui-kit format
const wizardProps = v1TripToWizardProps(v1Trip)
// { memberId, memberName, pickupAddress, dropoffAddress, appointmentDate, ... }Adapters are defensive — they never throw and return safe defaults for missing/malformed fields.
Maps & Address
import { RouteMapPreview } from "@bezeldigital/medidrive-ui-kit/maps"
import { AddressAutocomplete, FacilityAutocomplete } from "@bezeldigital/medidrive-ui-kit/address"
// Static route map (requires mapboxToken in provider)
<RouteMapPreview
pickup={{ lat: 37.3346, lng: -79.4322 }}
dropoff={{ lat: 37.5407, lng: -77.4360 }}
/>
// Google Places autocomplete (requires googleMapsApiKey in provider)
<AddressAutocomplete
value={address}
onChange={setAddress}
placeholder="Enter pickup address..."
/>Tailwind Preset
Add MediDrive design tokens to your Tailwind config:
// tailwind.config.js
import { medidrivePreset } from "@bezeldigital/medidrive-ui-kit/tailwind"
export default {
presets: [medidrivePreset],
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@bezeldigital/medidrive-ui-kit/dist/**/*.js",
],
}Utilities
import { calculatePickupTime, resolveTransportMode, cx } from "@bezeldigital/medidrive-ui-kit/utils"
// Calculate pickup time based on appointment, distance, and mobility needs
const pickup = calculatePickupTime({
appointmentTime: "14:00",
drivingMinutes: 35,
mobilityAid: "wheelchair",
})
// Resolve transport mode from member needs
const mode = resolveTransportMode({ wheelchairRequired: true, stretcherRequired: false })
// Merge Tailwind classes (clsx + tailwind-merge)
const classes = cx("px-4 py-2", isActive && "bg-blue-500", className)Chrome Extension Usage
The library is designed for Chrome extension consumption. No Next.js, no server components, no Node.js APIs.
// content-script.tsx
import { MediDriveProvider } from "@bezeldigital/medidrive-ui-kit"
import { TripCreationWizard } from "@bezeldigital/medidrive-ui-kit/trips"
import { v1TripToWizardProps } from "@bezeldigital/medidrive-ui-kit/adapters"
// Read data from the V1 page DOM
const v1Data = scrapeCurrentPageData()
// Transform to ui-kit format
const tripProps = v1TripToWizardProps(v1Data)
// Render MediDrive UI overlay
createRoot(container).render(
<MediDriveProvider
apiKey={chrome.storage.local.get("apiKey")}
backendUrl="https://medidrive-aetna-backend.fly.dev"
>
<TripCreationWizard defaultValues={tripProps} />
</MediDriveProvider>
)Build Info
- Format: ESM only
- Types: Full TypeScript declarations (
.d.ts) for every entry point - React: 18+ (tested on 19)
- Bundle sizes: primitives 33KB, data-toolkit 95KB, trips 136KB, adapters 3KB
- Tests: 139 passing across 7 test suites
