JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 315
  • Score
    100M100P100Q80504F
  • License MIT

MedixDeck shared UI component library — built with Chakra UI v3 and Vite

Package Exports

  • @medixdeck/ui

Readme

@medixdeck/ui

Shared UI Component Library for MedixDeck — built with Vite, React 18, TypeScript, and Chakra UI v3.

npm version TypeScript Chakra UI License


Overview

@medixdeck/ui is a production-ready, design-system-driven React component library that provides:

  • 43 components covering primitives, forms, layout, navigation, feedback, data display, and healthcare-specific UI
  • Full TypeScript support with exported prop interfaces
  • Light & dark mode via semantic tokens + next-themes (.dark class on <html>)
  • Satoshi font (Fontshare CDN) + Inter (Google Fonts) — both loaded automatically by MedixProvider
  • Logo component — inline SVG, 4 color variants (blue/purple/black/white), full + icon-only modes
  • ESM + CJS dual outputs with .d.ts declarations
  • Zero runtime CSS — styles are generated by Chakra UI v3's Panda CSS engine

Quick Start

Install

npm install @medixdeck/ui @chakra-ui/react next-themes

Wrap your root

// app/layout.tsx (Next.js App Router)
import { MedixProvider } from "@medixdeck/ui";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* MedixProvider injects Satoshi + Inter fonts and all CSS vars automatically */}
        <MedixProvider defaultColorMode="light">
          {children}
        </MedixProvider>
      </body>
    </html>
  );
}
// main.tsx (Vite)
import { MedixProvider } from "@medixdeck/ui";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <MedixProvider defaultColorMode="light">
    <App />
  </MedixProvider>
);

Use components

import { Button, DoctorCard, OTPInput, PhoneInput, DataTable, Logo } from "@medixdeck/ui";

// Button
<Button variant="solid" colorScheme="blue">Talk to a Doctor</Button>

// Logo — inline SVG, no image assets needed
<Logo />                                       // full blue, 32px
<Logo variant="purple" type="icon" />          // icon-only, purple
<Logo variant="white" height={28} />           // white full logo
<Logo variant="black" type="icon" height={48} />  // black icon

// Logo in a Navbar (or pass no logo prop for automatic default)
<Navbar logo={<Logo variant="purple" height={28} />} navItems={[...]} />

// Phone input with country code
<PhoneInput label="Phone number" defaultCountryCode="+234" onChange={setPhone} />

// OTP verification
<OTPInput length={6} label="Enter verification code" onComplete={verifyCode} />

// Data table with sorting
<DataTable
  columns={[{ key: "name", label: "Patient", sortable: true }]}
  data={patients}
  rowKey="id"
  striped
  onRowClick={(row) => router.push(`/patients/${row.id}`)}
/>

Component Index

🔵 Primitive (8)

Component Props Description
Button variant, size, colorScheme, isLoading, leftIcon, rightIcon Primary action button
IconButton icon, label, variant, size Icon-only button
Badge status, variant, size Status indicator label
Avatar src, name, size, showStatus, statusColor Profile picture with initials fallback
AvatarGroup max, size Stacked avatar display
Spinner / FullPageSpinner size, label, color Loading animation
Tag colorScheme, variant, onClose Filter chip
Divider orientation, label Section separator
Logo variant, type, height MedixDeck brand logo — inline SVG, no asset files needed

📝 Form (10)

Component Props Description
Input type, placeholder, isInvalid, leftIcon, rightIcon Text field
SearchInput placeholder, onChange Search field with icon
Textarea rows, maxLength, showCount Multi-line input
Select options[], placeholder, isInvalid Native dropdown
Checkbox colorScheme, description Single checkbox
RadioGroup name, options[], onChange, direction Radio button group
Switch label, description, size, colorScheme Toggle switch
FormControl label, helperText, errorMessage, isRequired Field wrapper with label/error
OTPInput / PinInput length, value, onChange, onComplete, mask, isInvalid One-time password boxes
PhoneInput defaultCountryCode, placeholder, onChange Phone + country code selector
DatePicker value, min, max, onChange, includeTime Styled date/datetime input

🧩 Layout (4)

Component Props Description
Card / CardHeader / CardBody / CardFooter hoverable, title, subtitle Surface container
StatCard value, label, change, trend KPI metric card
Container maxWidth Centered page wrapper
SectionHeader eyebrow, title, description, align Section heading block

🔗 Navigation (5)

Component Props Description
Navbar logo?, navItems[], ctaLabel, isSticky, renderLink, variant Responsive header. Falls back to <Logo /> when logo is omitted
Breadcrumb items[], separator, renderLink Page path navigator
Tabs tabs[], variant, activeId, onChange Line and pill variants
Pagination total, pageSize, currentPage, onChange, compact Page navigation
Stepper steps[], currentStep, orientation, colorScheme Multi-step progress

🔔 Feedback & Overlays (8)

Component Props Description
Alert status, variant, title, description, closable Inline message
Skeleton / SkeletonText / SkeletonCard isLoaded, lines Loading placeholder
Progress value, colorScheme, size, isIndeterminate, showLabel Progress bar
Modal isOpen, onClose, title, description, size, footer Dialog overlay
Drawer isOpen, onClose, title, placement, size, footer Slide-in panel
Tooltip label, placement Hover label
EmptyState icon, title, description, actionLabel Zero-data screen

📊 Data Display (4)

Component Props Description
Accordion items[], allowMultiple, variant Collapsible FAQ
TestimonialCard quote, authorName, authorTitle, rating Review card
BlogCard title, excerpt, category, coverImage, href Article preview
DataTable columns[], data[], sortable, striped, onRowClick, isLoading Sortable data table

🏥 Healthcare-Specific (3)

Component Props Description
DoctorCard name, specialty, rating, consultationFee, isVerified, isAvailable, onBookClick Doctor profile card
VitalBadge label, value, unit, status Vital sign display
AppointmentCard doctorName, date, time, type, status, onJoin, onReschedule, onCancel Appointment summary

Design Tokens

Color Palette

Token Light Dark Hex
bg #FEFEFE #0A1220
bg.surface #F6F6F6 #152035
text.heading #111926 #F5F6F8
text.body #374151 #CBD5E1
text.muted #6B7280 #94A3B8
border #E2E8F0 #1E3554
blue.500 #0685FF
purple.500 #7700CC

Typography

// CSS custom properties injected by MedixProvider
--font-body:    "Satoshi", sans-serif
--font-heading: "Satoshi", sans-serif

Using tokens directly

import { Box } from "@medixdeck/ui";

// Use semantic tokens in any Box prop
<Box bg="bg.surface" color="text.heading" border="1px solid" borderColor="border">
  Hello World
</Box>

Accessing the raw system object

import { system } from "@medixdeck/ui";
import { ChakraProvider } from "@chakra-ui/react";

// Use the design system without MedixProvider
<ChakraProvider value={system}>
  {children}
</ChakraProvider>

Dark Mode

Dark mode is managed by next-themes. MedixProvider handles it automatically by applying a .dark class to <html>.

// next-themes hook — use in any child component
import { useTheme } from "next-themes";

function DarkModeButton() {
  const { theme, setTheme } = useTheme();
  return (
    <Button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      {theme === "light" ? "🌙 Dark" : "☀️ Light"}
    </Button>
  );
}

Or manage it manually — just add/remove dark from document.documentElement:

// Toggle dark mode without next-themes (e.g. in a Vite dev preview)
useEffect(() => {
  document.documentElement.classList.toggle("dark", isDark);
}, [isDark]);

Important: Always apply the dark class on <html> (or a root ancestor that wraps all components including <Navbar>). Applying it on an inner <Box> while leaving sticky/portal components outside will break their dark mode.


For Next.js or React Router routing without anchor reloads:

import Link from "next/link"; // or from react-router-dom

// Navbar
<Navbar
  navItems={[{ label: "Home", href: "/" }, { label: "Doctors", href: "/doctors" }]}
  renderLink={(item, children) => (
    <Link href={item.href} style={{ textDecoration: "none" }}>
      {children}
    </Link>
  )}
/>

// Breadcrumb
<Breadcrumb
  items={[{ label: "Home", href: "/" }, { label: "Doctors" }]}
  renderLink={(item, children) => <Link href={item.href}>{children}</Link>}
/>

OTPInput / PinInput

<OTPInput
  length={6}
  label="Enter verification code"
  helperText="We sent a 6-digit code to your registered phone"
  value={otp}
  onChange={setOtp}
  onComplete={(code) => verifyOtp(code)}
/>

// Masked PIN mode
<PinInput length={4} mask label="Enter your PIN" />

PhoneInput

<PhoneInput
  label="Phone number"
  defaultCountryCode="+234"
  placeholder="80 000 0000"
  value={phone}
  onChange={setPhone}
  helperText="We'll send your confirmation here"
/>

Supported country codes: 🇳🇬 +234, 🇺🇸 +1, 🇬🇧 +44, 🇬🇭 +233, 🇰🇪 +254, 🇿🇦 +27, 🇪🇹 +251, 🇹🇿 +255


DataTable

<DataTable
  columns={[
    { key: "name",     label: "Patient Name", sortable: true },
    { key: "date",     label: "Date",         sortable: true },
    {
      key: "status",
      label: "Status",
      render: (val) => <Badge status={val === "Active" ? "success" : "warning"}>{val}</Badge>,
    },
  ]}
  data={patients}
  rowKey="id"
  striped
  isLoading={isFetching}
  onRowClick={(row) => router.push(`/patients/${row.id}`)}
/>

Project Structure

medixdeck-ui/
├── lib/                              ← Library source
│   ├── index.ts                      ← Single export entry point
│   ├── theme/
│   │   ├── index.ts                  ← createSystem (Chakra v3)
│   │   ├── colors.ts                 ← Primitive color tokens
│   │   ├── typography.ts             ← Font scale & text styles
│   │   └── spacing.ts                ← Spacing, radii, shadows
│   └── components/
│       ├── provider/
│       │   └── MedixProvider.tsx     ← Theme + dark mode provider
│       ├── primitive/
│       │   ├── Button.tsx
│       │   ├── IconButton.tsx
│       │   ├── Badge.tsx
│       │   ├── Avatar.tsx            ← Avatar + AvatarGroup
│       │   ├── Spinner.tsx
│       │   ├── Tag.tsx
│       │   ├── Divider.tsx
│       │   └── Logo.tsx              ← Brand logo, 4 variants, full + icon modes
│       ├── form/
│       │   ├── Input.tsx             ← Input + SearchInput
│       │   ├── Textarea.tsx
│       │   ├── Select.tsx
│       │   ├── CheckboxRadio.tsx     ← Checkbox + RadioGroup
│       │   ├── Switch.tsx
│       │   ├── FormControl.tsx
│       │   ├── OTPInput.tsx          ← OTPInput + PinInput
│       │   ├── PhoneInput.tsx        ← With country code selector
│       │   └── DatePicker.tsx
│       ├── layout/
│       │   ├── Card.tsx              ← Card + Header/Body/Footer
│       │   ├── StatCard.tsx
│       │   └── Container.tsx         ← Container + SectionHeader
│       ├── navigation/
│       │   ├── Navbar.tsx
│       │   ├── Breadcrumb.tsx
│       │   ├── Tabs.tsx
│       │   ├── Pagination.tsx
│       │   └── Stepper.tsx
│       ├── feedback/
│       │   ├── Alert.tsx
│       │   ├── Skeleton.tsx          ← Skeleton + SkeletonText + SkeletonCard
│       │   ├── Progress.tsx
│       │   ├── Modal.tsx
│       │   ├── Drawer.tsx
│       │   ├── Tooltip.tsx
│       │   └── EmptyState.tsx
│       ├── data/
│       │   ├── Accordion.tsx
│       │   ├── Cards.tsx             ← TestimonialCard + BlogCard
│       │   └── DataTable.tsx
│       └── healthcare/
│           └── DoctorCard.tsx        ← DoctorCard + VitalBadge + AppointmentCard
├── src/
│   ├── main.tsx                      ← Dev preview entry
│   └── App.tsx                       ← Component showcase app
├── dist/                             ← Built output (git-ignored)
│   ├── index.js                      ← ESM build
│   ├── index.cjs                     ← CJS build
│   └── index.d.ts                    ← TypeScript declarations
├── AGENTS.md                         ← AI agent guidelines
├── index.html
├── vite.config.ts
├── tsconfig.json
├── tsconfig.build.json
└── package.json

Scripts

Script Command Description
Dev preview npm run dev Start Vite dev server (http://localhost:5173)
Build library npm run build Compile to dist/ (ESM + CJS + types)
Type check npm run typecheck Run tsc --noEmit

Publishing

# Bump version in package.json first, then:
npm run build
npm publish --access public

Roadmap

  • Primitive components (Button, Badge, Avatar, Spinner, Tag, Divider, Logo)
  • Form components (Input, Textarea, Select, Checkbox, RadioGroup, Switch, FormControl)
  • OTPInput / PinInput
  • PhoneInput with country code selector
  • DatePicker
  • Layout components (Card, StatCard, Container, SectionHeader)
  • Navigation (Navbar with default Logo fallback, Breadcrumb, Tabs, Pagination, Stepper)
  • Feedback (Alert, Skeleton, Progress, Modal, Drawer, Tooltip, EmptyState)
  • DataTable with sorting + loading state
  • Healthcare components (DoctorCard, VitalBadge, AppointmentCard)
  • Font auto-injection (Satoshi + Inter via MedixProvider)
  • Dark mode via .dark class on <html> (all components including Navbar respond correctly)
  • Storybook interactive docs
  • Vitest + React Testing Library unit tests
  • GitHub Actions CI/CD for automated publishing
  • DateRangePicker component
  • Combobox / searchable select
  • FileUpload component
  • Notification / toast system