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

@qkit-emr/web-ui

v2.0.8

Published

Shared UI Component Library for EMR Monorepo

Readme

@qkit-emr/web-ui

Thư viện UI component dùng chung cho hệ thống EMR (Electronic Medical Record) Monorepo, được xây dựng trên nền tảng React, TypeScript và Tailwind CSS với tích hợp shadcn/ui.

🚀 Tính năng chính

  • Atomic Design: Tổ chức component theo nguyên tắc Atomic Design (atoms, molecules, organisms, templates, pages)
  • shadcn/ui Integration: Tích hợp đầy đủ các component từ shadcn/ui với customization
  • Separate Client/Server Exports: Tối ưu bundle size với entry points riêng cho client và server ⚡
  • TypeScript: Full TypeScript support với type safety
  • Tailwind CSS: Built on Tailwind CSS với utility-first approach
  • Responsive Design: Mobile-first responsive design với breakpoints chuẩn
  • Accessibility: WCAG 2.1 AA compliant components
  • Theme Support: Light/dark theme switching
  • Healthcare UI: Components được tối ưu cho giao diện y tế
  • Performance: Optimized cho healthcare applications với tree-shaking tối ưu

📦 Cài đặt

npm install @qkit-emr/web-ui

🎯 Sử dụng cơ bản

Import Strategy - Hybrid Approach ⚡

Từ phiên bản 1.7.0, @qkit-emr/web-ui hỗ trợ hybrid import pattern - kết hợp tốt nhất của simple imports và optimized server utilities:

Recommended: Client Components (Simple & Clean)

'use client'

// ✅ Import client components & hooks từ root (simple)
import { 
  Button, 
  Card, 
  Input, 
  useTheme, 
  useFormField,
  cn
} from '@qkit-emr/web-ui'

export function PatientForm() {
  const { theme } = useTheme()
  
  return (
    <Card className={cn('p-6', theme === 'dark' && 'bg-gray-800')}>
      <h2 className="text-xl font-bold mb-4">Quản lý bệnh nhân</h2>
      <Input placeholder="Tìm kiếm bệnh nhân..." className="mb-4" />
      <Button>Thêm bệnh nhân mới</Button>
    </Card>
  )
}

Server Components (Explicit & Optimized)

// ✅ Import server utilities & types từ /server (explicit)
import { cn, colors, BREAKPOINTS } from '@qkit-emr/web-ui/server'
import { PatientForm } from '@/components/PatientForm' // Client component

export default function PatientsPage() {
  return (
    <div 
      className={cn('container', 'mx-auto', 'p-4')}
      style={{ 
        maxWidth: BREAKPOINTS.xl,
        backgroundColor: colors.background.default 
      }}
    >
      <h1 className="text-2xl font-bold">Patients</h1>
      <PatientForm />
    </div>
  )
}

💡 Alternative: Pure UI Components

'use client'

// Alternative: Import từ /ui nếu chỉ cần pure shadcn/ui
import { Button, Card, Input } from '@qkit-emr/web-ui/ui'

export function SimpleCard() {
  return (
    <Card>
      <Input placeholder="Search..." />
      <Button variant="default" size="lg">
        Click me
      </Button>
    </Card>
  )
}

🔄 Advanced: Full Separation (Optional)

'use client'

// Optional: Import từ /client nếu muốn explicit separation
import { Button, Card, useTheme } from '@qkit-emr/web-ui/client'

function App() {
  const { theme } = useTheme()
  return (
    <Card className="p-6">
      <Button>Themed Button</Button>
    </Card>
  )
}

📊 Bundle Size & Usage Comparison

| Pattern | Client Import | Server Import | Total Bundle | Use Case | |---------|--------------|---------------|--------------|----------| | Hybrid (Recommended) ⭐ | Root (~250KB) | /server (~20KB) | ~270KB | ✅ Best DX + Server optimization | | Full Separation | /client (~180KB) | /server (~20KB) | ~200KB | ✅ Maximum optimization | | Pure UI Only | /ui (~150KB) | /server (~20KB) | ~170KB | ✅ Lightweight UI-only |

Why Hybrid Pattern?

  • Simple client imports - không cần remember paths
  • Explicit server separation - clear intent, minimal bundle
  • Best developer experience - easy to read & maintain
  • Server optimization - chỉ ~20KB cho server utilities

Note: Root import (~250KB) là acceptable cho client vì client components thường cần nhiều features. Optimization tập trung vào server components với /server entry point (~20KB).

💡 Learn More: Xem MIGRATION-GUIDE-CLIENT-SERVER.md để biết chi tiết patterns và best practices.

Setup Tailwind CSS

Bắt buộc thêm content path tới dist của thư viện (để Tailwind sinh đủ class cho component) và preset:

module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@qkit-emr/web-ui/dist/**/*.{js,ts,jsx,tsx}",
  ],
  presets: [require("@qkit-emr/web-ui/tailwind.preset")],
}

Dùng từ NPM: Xem docs/CONSUMING-NPM.md để biết vì sao bản npm có thể “không giống Storybook” và checklist đầy đủ (content, preset, thứ tự CSS, peer deps).

Import styles

import '@qkit-emr/web-ui/styles';

🏗️ Cấu trúc component chi tiết

📋 ATOMS - Thành phần cơ bản nhất

Self-Implemented Atoms (Complex - có thư mục riêng)

import { 
  Button, 
  ButtonGroup, 
  ButtonGroupItem, 
  useButtonGroup,
  Input, 
  InputAtom, 
  MemoizedInputAtom,
  Label, 
  LabelAtom, 
  MemoizedLabelAtom,
  Icon 
} from '@qkit-emr/web-ui';

// Button với variants và medical-specific features
<Button variant="primary" size="md" medical>Khám bệnh</Button>
<ButtonGroup>
  <ButtonGroupItem>Lưu</ButtonGroupItem>
  <ButtonGroupItem>Hủy</ButtonGroupItem>
</ButtonGroup>

// Input với validation và medical masks
<Input placeholder="Nhập tên bệnh nhân" />
<InputAtom type="phone" mask="(999) 999-9999" />

// Label với accessibility
<Label htmlFor="patient-name">Họ và tên</Label>

// Icon với medical icon mapping
<Icon name="stethoscope" size="md" />

UI-Dependent Atoms (Simple - single file)

import {
  // Data Display
  BadgeAtom,
  StatusBadgeAtom,
  ProgressAtom,
  TimelineAtom,
  DataTableAtom,
  
  // Form Controls
  DatePickerAtom,
  TimePickerAtom,
  MultiSelectAtom,
  RadioGroupAtom,
  InputOTPAtom,
  SecureInputAtom,
  
  // UI Enhancement
  ModalAtom,
  DrawerAtom,
  TabsAtom,
  AccordionAtom,
  TooltipAtom,
  CarouselAtom,
  
  // Medical-Specific
  LabResultAtom,
  
  // Utilities
  SpinnerAtom as Spinner,
  DividerAtom as Divider,
  TypographyAtom as Typography,
  FileInputAtom
} from '@qkit-emr/web-ui';

// Medical status badges
<StatusBadgeAtom status="critical" />
<StatusBadgeAtom status="stable" />
<StatusBadgeAtom status="discharged" />

// Progress indicators
<ProgressAtom value={75} label="Tiến độ điều trị" />

// Medical timeline
<TimelineAtom items={medicalEvents} />

// Lab results display
<LabResultAtom 
  testName="Xét nghiệm máu"
  result="120 mg/dL"
  normalRange="70-100 mg/dL"
  status="high"
/>

// Secure input for sensitive data
<SecureInputAtom 
  placeholder="Nhập mật khẩu"
  showToggle={true}
/>

// OTP input for 2FA
<InputOTPAtom length={6} />

// Medical file upload
<FileInputAtom 
  accept=".pdf,.jpg,.png"
  maxSize={5 * 1024 * 1024} // 5MB
  medical={true}
/>

Typography Atoms

import {
  Heading1,
  Heading2, 
  Heading3,
  Paragraph,
  Caption,
  ErrorText,
  SuccessText
} from '@qkit-emr/web-ui';

<Heading1>Tiêu đề chính</Heading1>
<Heading2>Tiêu đề phụ</Heading2>
<Paragraph>Nội dung văn bản</Paragraph>
<ErrorText>Thông báo lỗi</ErrorText>
<SuccessText>Thông báo thành công</SuccessText>

🧬 MOLECULES - Kết hợp Atoms

import { 
  FormField,
  SearchBar,
  Alert,
  Badge,
  Card,
  Modal
} from '@qkit-emr/web-ui';

// Form field với validation
<FormField
  label="Họ và tên"
  error="Tên không được để trống"
  required
>
  <Input placeholder="Nhập họ và tên" />
</FormField>

// Search bar với medical context
<SearchBar 
  placeholder="Tìm kiếm bệnh nhân, mã BHYT..."
  onSearch={(value) => console.log(value)}
  medical={true}
/>

// Medical alert
<Alert 
  type="warning"
  title="Cảnh báo thuốc"
  description="Bệnh nhân có tiền sử dị ứng với Penicillin"
/>

// Medical calendar - FullCalendar
<FullCalendar
  events={medicalEvents}
  initialView="timeGridWeek"
  medical={true}
  selectable={true}
  onEventClick={(info) => console.log(info.event)}
  onDateClick={(info) => console.log('Clicked date:', info.date)}
/>

📅 FullCalendar - Medical Scheduling

✨ Fully integrated with shadcn/ui design system!

⚠️ Important: FullCalendar CSS is separated from main bundle for optimal performance:

// 1️⃣ Import FullCalendar CSS ONLY when using calendar (không tự động load)
import '@qkit-emr/web-ui/fullcalendar-styles';

// 2️⃣ Import component
import { FullCalendar, type MedicalEvent } from '@qkit-emr/web-ui';

// 3️⃣ Define medical events
const appointments: MedicalEvent[] = [
  {
    id: '1',
    title: 'Khám bệnh - Nguyễn Văn A',
    start: '2024-01-15T09:00:00',
    end: '2024-01-15T09:30:00',
    medicalType: 'appointment',
    status: 'confirmed',
    priority: 'normal',
    patientName: 'Nguyễn Văn A',
    doctorName: 'BS. Trần Thị B',
    department: 'Khoa Nội',
    location: 'Phòng 101',
  },
  {
    id: '2',
    title: 'Phẫu thuật - Lê Thị C',
    start: '2024-01-15T14:00:00',
    end: '2024-01-15T16:00:00',
    medicalType: 'surgery',
    status: 'scheduled',
    priority: 'high',
    patientName: 'Lê Thị C',
    doctorName: 'PGS.TS Nguyễn Văn D',
    department: 'Khoa Ngoại',
    location: 'Phòng mổ 1',
  },
  {
    id: '3',
    title: 'Cấp cứu - BHYT',
    start: '2024-01-16T10:30:00',
    medicalType: 'emergency',
    status: 'inProgress',
    priority: 'emergency',
    department: 'Khoa Cấp cứu',
  },
];

// Month view with full event details
<FullCalendar
  events={appointments}
  initialView="dayGridMonth"
  medical={true}
/>

// Month view with dot display (compact)
<FullCalendar
  events={appointments}
  initialView="dayGridMonth"
  medical={true}
  dotView={true}
  dayMaxEvents={3} // Show max 3 events, rest in "+X more"
/>

// Week view with time slots
<FullCalendar
  events={appointments}
  initialView="timeGridWeek"
  medical={true}
  selectable={true}
  editable={true}
  slotMinTime="08:00:00"
  slotMaxTime="18:00:00"
  businessHours={{
    daysOfWeek: [1, 2, 3, 4, 5],
    startTime: '08:00',
    endTime: '18:00',
  }}
  onEventClick={(info) => {
    console.log('Event clicked:', info.event);
  }}
  onSelect={(info) => {
    console.log('Time slot selected:', info.start, 'to', info.end);
  }}
/>

// Day view with drag & drop
<FullCalendar
  events={appointments}
  initialView="timeGridDay"
  medical={true}
  selectable={true}
  editable={true}
  droppable={true}
  nowIndicator={true}
  onEventDrop={(info) => {
    console.log('Event moved:', info.event, 'Delta:', info.delta);
  }}
  onEventResize={(info) => {
    console.log('Event resized:', info.event);
  }}
/>

// List view
<FullCalendar
  events={appointments}
  initialView="listWeek"
  medical={true}
/>

Medical Event Types:

  • appointment - Lịch khám bệnh
  • surgery - Phẫu thuật
  • shift - Ca trực
  • meeting - Họp
  • emergency - Cấp cứu
  • checkup - Khám định kỳ
  • followup - Tái khám
  • vaccination - Tiêm chủng

Event Status:

  • scheduled - Đã lên lịch
  • confirmed - Đã xác nhận
  • inProgress - Đang diễn ra
  • completed - Hoàn thành
  • cancelled - Đã hủy
  • noShow - Không đến
  • rescheduled - Đã dời lịch

Priority Levels:

  • low - Thấp
  • normal - Bình thường
  • high - Cao
  • urgent - Khẩn cấp
  • emergency - Cấp cứu

🧠 ORGANISMS - Components phức tạp

import { 
  Header,
  Sidebar,
  Navigation,
  DataTable,
  Breadcrumb,
  Pagination
} from '@qkit-emr/web-ui';

// Medical data table
<DataTable
  data={patients}
  columns={[
    { key: 'name', label: 'Họ tên' },
    { key: 'age', label: 'Tuổi' },
    { key: 'diagnosis', label: 'Chẩn đoán' },
    { key: 'status', label: 'Trạng thái' }
  ]}
  pagination={{
    currentPage: 1,
    totalPages: 10,
    onPageChange: (page) => console.log(page)
  }}
  medical={true}
/>

// Medical navigation
<Navigation
  items={[
    { label: 'Dashboard', icon: 'dashboard', href: '/' },
    { label: 'Bệnh nhân', icon: 'users', href: '/patients' },
    { label: 'Lịch hẹn', icon: 'calendar', href: '/appointments' },
    { label: 'Báo cáo', icon: 'chart', href: '/reports' }
  ]}
  medical={true}
/>

📄 TEMPLATES - Layout patterns

import {
  AuthTemplate,
  DashboardTemplate,
  DetailTemplate,
  ErrorTemplate,
  FormTemplate,
  ListTemplate
} from '@qkit-emr/web-ui';

// Medical dashboard template
<DashboardTemplate
  header={<Header title="Dashboard Bệnh viện" />}
  sidebar={<Sidebar />}
  content={<DashboardContent />}
  medical={true}
/>

// Patient detail template
<DetailTemplate
  header={<PatientHeader patient={patient} />}
  content={<PatientDetails patient={patient} />}
  actions={<PatientActions patient={patient} />}
  medical={true}
/>

// Medical form template
<FormTemplate
  title="Thêm bệnh nhân mới"
  form={<PatientForm />}
  actions={<FormActions />}
  medical={true}
/>

📱 RESPONSIVE SYSTEM - Mobile-first development

import {
  // Responsive Providers & Hooks
  ResponsiveProvider,
  useResponsive,
  
  // Responsive Layout Components
  ResponsiveLayoutContainer,
  ResponsiveGrid,
  
  // Mobile Components
  MobileLayout,
  MobileStack,
  MobileContainer,
  MobileBottomNavigation,
  MobileDrawerNavigation,
  MobileHamburgerMenu,
  
  // Tablet Components
  TabletLayout,
  TabletContainer,
  TabletStack,
  
  // Desktop Components
  DesktopLayout,
  DesktopContainer,
  DesktopStack,
  DesktopGrid,
  
  // Responsive Form Components
  ResponsiveForm,
  ResponsiveFormField,
  ResponsiveInputField,
  ResponsiveSelectField,
  ResponsiveTextareaField,
  ResponsiveCheckboxField,
  ResponsiveRadioField,
  ResponsiveFormActions
} from '@qkit-emr/web-ui';

// Responsive medical form
<ResponsiveProvider>
  <ResponsiveForm>
    <ResponsiveFormField label="Họ và tên">
      <ResponsiveInputField placeholder="Nhập họ và tên" />
    </ResponsiveFormField>
    <ResponsiveFormField label="Ngày sinh">
      <ResponsiveInputField type="date" />
    </ResponsiveFormField>
    <ResponsiveFormActions>
      <Button>Lưu</Button>
      <Button variant="outline">Hủy</Button>
    </ResponsiveFormActions>
  </ResponsiveForm>
</ResponsiveProvider>

🎨 Theme và Styling

Theme Provider

import { ThemeProvider } from '@qkit-emr/web-ui';

function App() {
  return (
    <ThemeProvider>
      <YourApp />
    </ThemeProvider>
  );
}

Theme Toggle

import { useThemeToggle } from '@qkit-emr/web-ui';

function ThemeToggle() {
  const { toggleTheme, theme } = useThemeToggle();
  
  return (
    <Button onClick={toggleTheme}>
      {theme === 'dark' ? '🌞' : '🌙'}
    </Button>
  );
}

Medical Theme Colors

// Primary medical colors
className="bg-blue-600 text-white" // Medical blue
className="bg-green-500 text-white" // Success green
className="bg-red-600 text-white"   // Error red
className="bg-yellow-500 text-white" // Warning yellow

// Medical status colors
className="bg-red-100 text-red-800" // Critical
className="bg-yellow-100 text-yellow-800" // Warning
className="bg-green-100 text-green-800" // Stable
className="bg-blue-100 text-blue-800" // Info

🔧 Customization

Override component styles

import { Button } from '@qkit-emr/web-ui';

// Sử dụng className để override
<Button className="bg-blue-600 hover:bg-blue-700">
  Custom Button
</Button>

Medical variants

import { Button } from '@qkit-emr/web-ui';

// Button với medical variant
<Button variant="medical" size="lg">
  Khám bệnh
</Button>

📱 Responsive Design

Tất cả components đều hỗ trợ responsive design với breakpoints:

  • Mobile: 320px - 640px (default)
  • Tablet: 640px+ (sm:)
  • Desktop: 1024px+ (lg:, xl:, 2xl:)
<Card className="p-4 sm:p-6 lg:p-8">
  <h1 className="text-xl sm:text-2xl lg:text-3xl">
    Responsive Title
  </h1>
</Card>

♿ Accessibility

Tất cả components đều tuân thủ WCAG 2.1 AA:

  • Keyboard navigation support
  • Screen reader compatibility
  • Focus management
  • ARIA labels
  • Color contrast compliance
<Button aria-label="Thêm bệnh nhân mới">
  <Icon name="plus" />
</Button>

🧪 Testing

Unit Testing

import { render, screen } from '@testing-library/react';
import { Button } from '@qkit-emr/web-ui';

test('Button renders correctly', () => {
  render(<Button>Test Button</Button>);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

Storybook

Chạy Storybook để xem tất cả components:

npm run storybook

Truy cập online: https://qkit-emr-share-ui.web.app/

📚 API Reference

Button Component

interface ButtonProps {
  variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'medical';
  size?: 'default' | 'sm' | 'lg' | 'icon';
  disabled?: boolean;
  loading?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
  className?: string;
  medical?: boolean; // Medical-specific styling
}

Input Component

interface InputProps {
  type?: 'text' | 'email' | 'password' | 'number' | 'tel';
  placeholder?: string;
  value?: string;
  onChange?: (value: string) => void;
  error?: string;
  disabled?: boolean;
  className?: string;
  mask?: string; // Input mask for phone, date, etc.
  medical?: boolean; // Medical-specific validation
}

Medical Components

// LabResultAtom
interface LabResultAtomProps {
  testName: string;
  result: string;
  normalRange: string;
  status: 'normal' | 'high' | 'low' | 'critical';
  unit?: string;
  date?: Date;
}

// StatusBadgeAtom
interface StatusBadgeAtomProps {
  status: 'critical' | 'warning' | 'stable' | 'discharged' | 'admitted';
  size?: 'sm' | 'md' | 'lg';
}

// SecureInputAtom
interface SecureInputAtomProps {
  placeholder?: string;
  showToggle?: boolean;
  medical?: boolean; // HIPAA compliance
}

🔗 Dependencies

Peer Dependencies

{
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "@radix-ui/react-*": "^1.0.0",
  "tailwindcss": "^3.3.0",
  "class-variance-authority": "^0.7.0",
  "clsx": "^2.0.0",
  "tailwind-merge": "^2.0.0"
}

Dev Dependencies

{
  "@types/react": "^18.2.0",
  "@types/react-dom": "^18.2.0",
  "typescript": "~5.8.2"
}

🚀 Development

Build

npm run build

Development mode

npm run dev

Storybook

npm run storybook
npm run build-storybook

Testing

npm test
npm run test:watch

Linting

npm run lint
npm run lint:fix

Publishing to npm

  1. Đăng nhập: Chạy npm login (hoặc đã login rồi thì bỏ qua). pnpm dùng chung credential với npm.
  2. Publish (từ root repo):
pnpm run publish:web-ui

Hoặc từ thư mục libs/web-ui: npm run publish:npm.
(CI: set NPM_TOKEN trong env hoặc .env.local nếu dùng token thay cho login.)

📦 Exports Strategy

1️⃣ Root Entry Point (@qkit-emr/web-ui) ⭐ Recommended for Client

Use for: Client components, hooks, providers (cần 'use client' directive)

Why: Simple, clean imports - không cần remember complex paths

'use client'

import { 
  // Atoms
  Button, Input, Label, Icon,
  
  // Molecules
  Card, Badge, Alert, FormField,
  
  // Organisms
  DataTable, Header, Sidebar,
  
  // Hooks
  useTheme, useFormField, useResponsive,
  
  // Providers
  ThemeProvider, ResponsiveProvider,
  
  // UI Components
  Dialog, DropdownMenu, Select,
  
  // Utils
  cn
} from '@qkit-emr/web-ui'

Bundle Size: ~250KB (comprehensive, includes everything you need)

2️⃣ Server Entry Point (@qkit-emr/web-ui/server) ⭐ Recommended for Server

Use for: Server utilities, types, constants (KHÔNG cần 'use client')

Why: Explicit server-only code, minimal bundle (~20KB), clear separation

// Server Component - NO 'use client' needed
import { 
  // Utilities
  cn,
  
  // Theme Configuration
  colors, typography, spacing, shadows,
  BREAKPOINTS, animations,
  
  // Types
  type ButtonProps,
  type CardProps,
  type Theme,
  
  // Validators
  emailSchema, phoneSchema
} from '@qkit-emr/web-ui/server'

Bundle Size: ~20KB (minimal, server-safe, optimized) ✅

3️⃣ UI Entry Point (@qkit-emr/web-ui/ui) 💡 Alternative

Use for: Pure shadcn/ui components only (lighter weight alternative)

'use client'

import {
  // Pure shadcn/ui components
  Button, Input, Card, Dialog,
  Alert, Badge, Checkbox, Select,
  Table, Tabs, Toast, Tooltip
} from '@qkit-emr/web-ui/ui'

Bundle Size: ~150KB (pure UI components)

When to use: Khi bạn chỉ cần UI components mà không cần custom atoms/molecules/organisms

4️⃣ Client Entry Point (@qkit-emr/web-ui/client) 🔄 Advanced

Use for: Explicit client-side separation (advanced use cases)

'use client'

import { 
  Button, 
  Card, 
  useTheme, 
  cn 
} from '@qkit-emr/web-ui/client'

Bundle Size: ~180KB (optimized client bundle)

When to use: Khi bạn muốn explicit separation và maximum optimization

📋 Import Mapping Table (Hybrid Pattern)

| Component/Hook | Root (Client) | Server | UI | Client | |---------------|---------------|--------|-----|--------| | Button | ✅ Recommended | ❌ | ✅ | ✅ | | useTheme | ✅ Recommended | ❌ | ❌ | ✅ | | cn (in client) | ✅ Recommended | ❌ | ❌ | ✅ | | cn (in server) | ❌ | ✅ Recommended | ❌ | ❌ | | colors | ❌ | ✅ Only here | ❌ | ❌ | | BREAKPOINTS | ❌ | ✅ Only here | ❌ | ❌ | | type ButtonProps | ❌ | ✅ Only here | ❌ | ❌ |

Recommended Pattern:

  • ✅ Client components → Import từ root (@qkit-emr/web-ui)
  • ✅ Server components → Import từ /server (@qkit-emr/web-ui/server)

🤝 Contributing

  1. Fork repository
  2. Tạo feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Tạo Pull Request

Development Guidelines

  • Tuân thủ Atomic Design principles
  • Viết TypeScript với strict mode
  • Thêm unit tests cho mọi component
  • Tạo Storybook stories
  • Đảm bảo accessibility compliance
  • Responsive design cho mọi component
  • Medical-specific features cho healthcare UI

📄 License

MIT License - xem file LICENSE để biết thêm chi tiết.

🆘 Support

🔄 Changelog

Xem CHANGELOG.md để biết lịch sử thay đổi.

Latest Changes (v1.7.0)

  • Separate Client/Server Exports: Tối ưu bundle size với entry points riêng
  • Bundle Size Optimization: Giảm 28% bundle size với optimized imports
  • Next.js 13+ Support: Full support cho App Router với proper 'use client' directives
  • Migration Guide: Comprehensive guide để migrate từ legacy imports
  • Backward Compatible: Legacy imports vẫn hoạt động

📖 Additional Documentation

  • 📚 Migration Guide - Hướng dẫn migrate từ legacy imports
  • 🎨 Storybook - Component documentation và examples
  • 📦 Build Guide - Hướng dẫn build và deploy
  • 🔧 Architecture - Chi tiết về architecture và best practices

Made with ❤️ by EMR Team