Skip to content

PyroRadius UI Component Library

Document: PHASE2_11_UI_Component_Library.md Version: 2.0 Date: 2026-07-13 Author: PyroNet Solutions


Overview

PyroRadius uses a custom React component library built on top of Tailwind CSS and Headless UI. All components are located in resources/js/Components/ and are shared across all Inertia.js pages. This document catalogs every reusable component, its props, usage patterns, and design system rules.

Framework: React 18 + Inertia.js Styling: Tailwind CSS v3 with custom theme tokens Accessible components: Headless UI v2 Charts: Recharts Icons: Heroicons + custom SVGs


Design System

Color Tokens

The design system uses semantic color tokens defined in tailwind.config.js. Do not use raw Tailwind colors (e.g., red-500) — always use semantic tokens.

Token Light Mode Dark Mode Usage
ember / flame orange-600 orange-500 Brand primary, primary actions
success green-600 green-400 Active status, success states
info blue-600 blue-400 Informational states
warning amber-500 amber-400 Expiring soon, warnings
danger red-600 red-400 Destructive actions, error states
muted gray-500 gray-400 Secondary text, placeholders
surface white gray-900 Page background
card white gray-800 Card/panel background
border gray-200 gray-700 Borders and dividers

Typography Scale

Class Size Weight Usage
text-2xl font-bold 24px 700 Page titles
text-xl font-semibold 20px 600 Section headings
text-lg font-semibold 18px 600 Card titles, sub-headings
text-base 16px 400 Body text
text-sm 14px 400 Table content, labels
text-xs 12px 400 Helper text, badges
text-muted Muted/secondary text

Spacing Conventions

Element Tailwind Classes
Page container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8
Card / panel p-6 rounded-xl shadow-sm
Section gap gap-6 or space-y-6
Form group space-y-4
Button padding px-4 py-2
Input padding px-3 py-2

Dark Mode Strategy

All components support dark mode via Tailwind's dark: prefix. Dark mode is activated by: 1. Adding the dark class to the <html> element 2. User preference stored in localStorage key theme

Components follow this pattern:

<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
  {children}
</div>


Core Components


File: Components/ApplicationLogo.jsx Used In: Login page header, sidebar top section, email templates

The primary brand logo for PyroRadius/PyroNet. Renders an SVG or image logo. Supports both the PyroNet primary brand and the Indus Broadband white-label variant (configured via app settings).

Props:

Prop Type Required Default Description
className string No "" Tailwind classes for sizing and positioning
variant string No "default" "default" or "white" for dark backgrounds

Usage Example:

// In login page
<ApplicationLogo className="w-40 h-auto" />

// White variant for dark sidebar
<ApplicationLogo className="w-32 h-auto" variant="white" />

Notes:

The logo automatically adapts to dark mode if variant="default" is used. On very dark backgrounds (sidebar), use variant="white" to ensure visibility. SVG-based for crisp rendering at all sizes.


StatusPill

File: Components/StatusPill.jsx Used In: Customer list table, customer detail page, dashboard customer cards, reports tables, search results

One of the most widely used components. Renders a colored pill badge showing a customer's current connection/billing status.

Props:

Prop Type Required Default Description
status string Yes Customer status value (see below)
size string No "md" "sm", "md", "lg"
className string No "" Additional Tailwind classes

Status Values and Colors:

Status Value Display Text Color Meaning
active Active Green Customer is active and connected
expired Expired Red Service has expired, PPPoE blocked
suspended Suspended Amber/Yellow Manually suspended
disabled Disabled Gray Admin-disabled, cannot connect
temp_extended Extended Blue Temporary extension applied

Usage Example:

// In customer table row
<StatusPill status={customer.status} />

// Smaller badge in compact list
<StatusPill status={customer.status} size="sm" />

// Large pill in detail page header
<StatusPill status={customer.status} size="lg" />

Implementation Notes:

// Internal color mapping
const statusConfig = {
  active:        { bg: 'bg-green-100 dark:bg-green-900/30',  text: 'text-green-700 dark:text-green-400',  label: 'Active' },
  expired:       { bg: 'bg-red-100 dark:bg-red-900/30',     text: 'text-red-700 dark:text-red-400',      label: 'Expired' },
  suspended:     { bg: 'bg-amber-100 dark:bg-amber-900/30', text: 'text-amber-700 dark:text-amber-400',  label: 'Suspended' },
  disabled:      { bg: 'bg-gray-100 dark:bg-gray-700',      text: 'text-gray-600 dark:text-gray-400',    label: 'Disabled' },
  temp_extended: { bg: 'bg-blue-100 dark:bg-blue-900/30',   text: 'text-blue-700 dark:text-blue-400',   label: 'Extended' },
};

Dark Mode: Fully supported — uses lighter backgrounds in dark mode with adjusted text colors for contrast.

Accessibility: role="status" with aria-label={status} for screen readers.


File: Components/Modal.jsx Used In: Confirm delete dialogs, edit forms (customer, package, NAS, dealer), archive confirmation, bulk action confirmations, WhatsApp preview

A Headless UI Dialog wrapper providing accessible modal dialogs with backdrop blur, focus trapping, and keyboard dismissal (Escape key).

Props:

Prop Type Required Default Description
show boolean Yes Controls modal visibility
onClose function Yes Called when user dismisses modal
maxWidth string No "md" "sm", "md", "lg", "xl", "2xl", "full"
closeable boolean No true Whether clicking backdrop closes modal
children ReactNode Yes Modal content

Usage Example:

const [showDeleteModal, setShowDeleteModal] = useState(false);

<Modal show={showDeleteModal} onClose={() => setShowDeleteModal(false)} maxWidth="sm">
  <div className="p-6">
    <h2 className="text-lg font-semibold">Confirm Delete</h2>
    <p className="mt-2 text-sm text-gray-600">
      Are you sure you want to delete this customer? This action cannot be undone.
    </p>
    <div className="mt-4 flex gap-3 justify-end">
      <SecondaryButton onClick={() => setShowDeleteModal(false)}>
        Cancel
      </SecondaryButton>
      <DangerButton onClick={handleDelete}>
        Delete
      </DangerButton>
    </div>
  </div>
</Modal>

Notes:

  • Focus is automatically trapped inside the modal when open.
  • Escape key calls onClose if closeable is true.
  • Clicking the backdrop calls onClose if closeable is true.
  • closeable={false} is used for critical operations like bulk recharge where accidental dismissal should be prevented.
  • Modal stacking (nested modals) is not supported — avoid nested modals.
  • The modal renders a portal to document.body to avoid z-index issues.

File: Components/Dropdown.jsx Used In: User profile menu (top-right nav), action menus in customer table rows, bulk action dropdowns, NAS context menus

A Headless UI Menu-based dropdown component with sub-components for composability.

Sub-Components:

  • Dropdown.Trigger — The element that opens the dropdown
  • Dropdown.Content — The dropdown panel container
  • Dropdown.Link — A navigation item inside the dropdown (renders <Link> from Inertia)
  • Dropdown.Button — A non-navigation button item

Props — Dropdown:

Prop Type Required Default Description
children ReactNode Yes Must contain Trigger and Content

Props — Dropdown.Content:

Prop Type Required Default Description
align string No "right" "left" or "right" — panel alignment
width string No "48" Tailwind width class (e.g., "48", "64")
children ReactNode Yes Dropdown items

Usage Example:

// User menu in navigation bar
<Dropdown>
  <Dropdown.Trigger>
    <button className="flex items-center gap-2">
      {user.name}
      <ChevronDownIcon className="w-4 h-4" />
    </button>
  </Dropdown.Trigger>

  <Dropdown.Content>
    <Dropdown.Link href={route('profile.edit')}>
      Profile
    </Dropdown.Link>
    <Dropdown.Link href={route('settings.index')}>
      Settings
    </Dropdown.Link>
    <Dropdown.Button onClick={handleLogout}>
      Logout
    </Dropdown.Button>
  </Dropdown.Content>
</Dropdown>

// Action menu in table row
<Dropdown>
  <Dropdown.Trigger>
    <button>
      <EllipsisVerticalIcon className="w-5 h-5" />
    </button>
  </Dropdown.Trigger>

  <Dropdown.Content align="right" width="56">
    <Dropdown.Link href={route('customers.show', customer.id)}>
      View Details
    </Dropdown.Link>
    <Dropdown.Button onClick={() => openRechargeModal(customer)}>
      Recharge
    </Dropdown.Button>
  </Dropdown.Content>
</Dropdown>

Notes:

Closes automatically when clicking outside or pressing Escape. Keyboard navigation (Arrow keys, Enter, Escape) supported. Positioning is handled automatically — the panel flips to avoid viewport overflow.


PrimaryButton

File: Components/PrimaryButton.jsx Used In: Form submissions, primary action triggers, login button, save buttons

The main call-to-action button using the brand ember/flame color.

Props:

Prop Type Required Default Description
type string No "submit" HTML button type
disabled boolean No false Disables button and shows reduced opacity
children ReactNode Yes Button label/content
className string No "" Additional Tailwind classes
onClick function No Click handler

Usage Example:

<PrimaryButton type="submit" disabled={processing}>
  {processing ? 'Saving...' : 'Save Customer'}
</PrimaryButton>

<PrimaryButton onClick={handleRecharge}>
  Recharge
</PrimaryButton>

Notes:

When disabled, the button shows opacity-50 cursor-not-allowed. The processing state from Inertia's useForm hook is typically passed as disabled={processing} to prevent double-submit. Uses transition-colors for smooth hover effect.


SecondaryButton

File: Components/SecondaryButton.jsx Used In: Cancel actions, secondary form actions, "Back" buttons, export buttons

A muted button for non-primary actions. Does not draw as much visual attention as PrimaryButton.

Props:

Prop Type Required Default Description
type string No "button" HTML button type
disabled boolean No false Disables button
children ReactNode Yes Button label/content
className string No "" Additional Tailwind classes
onClick function No Click handler

Usage Example:

// In a modal footer
<div className="flex gap-3 justify-end">
  <SecondaryButton onClick={onClose}>
    Cancel
  </SecondaryButton>
  <PrimaryButton type="submit">
    Save
  </PrimaryButton>
</div>

DangerButton

File: Components/DangerButton.jsx Used In: Delete confirmations, account suspension, bulk delete actions

A red destructive action button. Always paired with a confirmation step (Modal) before the action is executed.

Props:

Prop Type Required Default Description
type string No "button" HTML button type
disabled boolean No false Disables button
children ReactNode Yes Button label/content
className string No "" Additional Tailwind classes
onClick function No Click handler

Usage Example:

<DangerButton onClick={confirmDelete} disabled={processing}>
  {processing ? 'Deleting...' : 'Delete Customer'}
</DangerButton>

Design Rule: Never use DangerButton as a primary action without a confirmation modal. Always require an explicit user confirmation before executing irreversible actions.


TextInput

File: Components/TextInput.jsx Used In: All forms throughout the application — customer create/edit, package forms, NAS forms, login, settings, search bars

A consistently styled form text input with proper focus ring and dark mode support.

Props:

Prop Type Required Default Description
type string No "text" HTML input type (text, email, password, number, tel, date)
id string No Input ID (for label association)
name string No Input name
value string/number No Controlled value
defaultValue string/number No Uncontrolled default value
onChange function No Change handler
onBlur function No Blur handler
placeholder string No "" Placeholder text
className string No "" Additional classes
autoComplete string No HTML autocomplete attribute
disabled boolean No false Disables input
required boolean No false Marks as required
isFocused boolean No false Auto-focuses on mount

Usage Example:

// Controlled input in Inertia form
<TextInput
  id="pppoe_username"
  name="pppoe_username"
  type="text"
  value={data.pppoe_username}
  onChange={(e) => setData('pppoe_username', e.target.value)}
  autoComplete="off"
  placeholder="e.g., ali_01"
/>

// Password input
<TextInput
  id="password"
  type="password"
  value={data.password}
  onChange={(e) => setData('password', e.target.value)}
  autoComplete="current-password"
/>

Notes:

Uses ref forwarding for the isFocused prop to auto-focus on mount. Focus ring uses the brand ember color. Compatible with Inertia.js useForm hook's data and setData. Dark mode: bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600.


InputLabel

File: Components/InputLabel.jsx Used In: All form fields — always paired with TextInput, Checkbox, or select elements

Renders a styled <label> element with consistent typography and spacing.

Props:

Prop Type Required Default Description
htmlFor string No ID of associated input element
value string No Label text (alternative to children)
children ReactNode No Label content (if not using value prop)
className string No "" Additional classes

Usage Example:

// Using value prop
<InputLabel htmlFor="pppoe_username" value="PPPoE Username" />
<TextInput id="pppoe_username" ... />

// Using children (for labels with embedded elements)
<InputLabel htmlFor="package_id">
  Package <span className="text-red-500">*</span>
</InputLabel>

Notes:

Always associate labels with inputs via htmlFor/id for accessibility. text-sm font-medium text-gray-700 dark:text-gray-300.


InputError

File: Components/InputError.jsx Used In: All form fields — renders below the input when there is a validation error

Displays a red validation error message below a form field.

Props:

Prop Type Required Default Description
message string No Error message to display
className string No "" Additional classes

Usage Example:

// Typically used with Inertia useForm errors
<TextInput
  id="pppoe_username"
  value={data.pppoe_username}
  onChange={(e) => setData('pppoe_username', e.target.value)}
/>
<InputError message={errors.pppoe_username} className="mt-1" />

Notes:

Renders nothing (returns null) if message is falsy — safe to always include in forms. Uses text-red-600 dark:text-red-400 text-sm. The role="alert" attribute is set for screen reader compatibility.


Checkbox

File: Components/Checkbox.jsx Used In: Bulk selection in customer list, permission toggles in user settings, feature flags in package settings

A styled checkbox input consistent with the design system.

Props:

Prop Type Required Default Description
id string No Input ID
name string No Input name
checked boolean Yes Controlled checked state
onChange function Yes Change handler
disabled boolean No false Disables the checkbox
className string No "" Additional classes

Usage Example:

// Single checkbox
<div className="flex items-center gap-2">
  <Checkbox
    id="auto_renew"
    name="auto_renew"
    checked={data.auto_renew}
    onChange={(e) => setData('auto_renew', e.target.checked)}
  />
  <InputLabel htmlFor="auto_renew" value="Auto-renew on expiry" />
</div>

// Bulk select in table header
<Checkbox
  checked={allSelected}
  onChange={toggleSelectAll}
/>

Notes:

Uses brand ember color for checked state. The onChange event provides the full event object — use e.target.checked (not e.target.value) for boolean checkboxes.


File: Components/NavLink.jsx Used In: Sidebar navigation links, mobile navigation menu

An Inertia Link component styled as a navigation item with active and inactive states.

Props:

Prop Type Required Default Description
href string Yes Navigation URL (Inertia route)
active boolean No false Whether this link is the current page
children ReactNode Yes Link content (text + optional icon)
className string No "" Additional classes

Usage Example:

// In sidebar layout
<NavLink
  href={route('customers.index')}
  active={route().current('customers.*')}
>
  <UsersIcon className="w-5 h-5" />
  <span>Customers</span>
</NavLink>

<NavLink
  href={route('dashboard')}
  active={route().current('dashboard')}
>
  <HomeIcon className="w-5 h-5" />
  <span>Dashboard</span>
</NavLink>

Notes:

Active state: bg-ember-50 text-ember-700 dark:bg-ember-900/20 dark:text-ember-400 font-semibold. Inactive state: text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700. Uses Inertia Link (not <a>) for SPA navigation without full page reload.


BandwidthChart

File: Components/BandwidthChart.jsx Used In: Customer detail page (usage history tab), NAS detail page (per-interface graph), Network overview page, Reports bandwidth section

A Recharts-based area chart showing download and upload bandwidth over time.

Props:

Prop Type Required Default Description
data array Yes Array of { time, rx, tx } objects
height number No 250 Chart height in pixels
rxColor string No "#10b981" Line color for download (RX)
txColor string No "#f97316" Line color for upload (TX)
unit string No "Mbps" Y-axis unit label
showLegend boolean No true Show legend below chart
className string No "" Container class

Data Format:

const data = [
  { time: "14:00", rx: 4.5, tx: 1.2 },
  { time: "14:05", rx: 6.2, tx: 0.8 },
  { time: "14:10", rx: 3.8, tx: 2.1 },
  // ... more data points
];

Usage Example:

// Customer usage chart (fetched from traffic-history API)
<BandwidthChart
  data={trafficData}
  height={300}
  unit="Mbps"
  showLegend={true}
/>

// Compact chart in dashboard widget
<BandwidthChart
  data={networkData}
  height={120}
  showLegend={false}
/>

Notes:

Uses Recharts AreaChart with gradient fills. RX (download) is typically green; TX (upload) is amber/orange. Responsive via Recharts ResponsiveContainer — always fills the parent width. Tooltip shows exact values on hover. Dark mode: background-transparent, axis text uses text-gray-400. Performance: for charts with more than 200 data points, the data is automatically downsampled using the LTTB (Largest Triangle Three Buckets) algorithm.


LiveTrafficChart

File: Components/LiveTrafficChart.jsx Used In: Customer detail page (live traffic tab), NAS real-time monitoring panel

A real-time bandwidth display component showing current download/upload speeds as animated gauges or live line charts.

Props:

Prop Type Required Default Description
rxBps number Yes Current download speed in bits per second
txBps number Yes Current upload speed in bits per second
available boolean No true Whether live data is available (customer online)
packageSpeedKbps number No Package speed limit in Kbps (for gauge max)
refreshInterval number No 5000 Auto-refresh interval in milliseconds
onRefresh function No Callback to fetch new data

Usage Example:

// Customer detail live traffic widget
const [liveData, setLiveData] = useState({ rx: 0, tx: 0 });

useEffect(() => {
  const interval = setInterval(async () => {
    const response = await axios.get(`/api/mobile/customers/${customerId}/live-traffic`);
    setLiveData({ rx: response.data.rx_bps, tx: response.data.tx_bps });
  }, 5000);
  return () => clearInterval(interval);
}, []);

<LiveTrafficChart
  rxBps={liveData.rx}
  txBps={liveData.tx}
  available={customer.online}
  packageSpeedKbps={customer.package.speed_download_kbps}
/>

Notes:

Displays human-readable speeds (auto-converts bps → Kbps → Mbps → Gbps). When available is false, shows "Customer Offline" state with gray gauges. The rolling line chart keeps the last 60 data points (5 minutes at 5-second intervals). Does not self-fetch — the parent component is responsible for polling and passing updated values.


ThemeToggle

File: Components/ThemeToggle.jsx Used In: Navigation bar (top-right area), settings page

A button that toggles between dark and light mode.

Props:

Prop Type Required Default Description
className string No "" Additional classes

Usage Example:

// In the main navigation bar
<div className="flex items-center gap-4">
  <ThemeToggle />
  <Dropdown>{/* user menu */}</Dropdown>
</div>

Implementation Notes:

// Theme persistence logic
const [theme, setTheme] = useState(
  localStorage.getItem('theme') ?? 'light'
);

const toggleTheme = () => {
  const newTheme = theme === 'light' ? 'dark' : 'light';
  setTheme(newTheme);
  localStorage.setItem('theme', newTheme);
  document.documentElement.classList.toggle('dark', newTheme === 'dark');
};

Uses a sun icon for light mode and moon icon for dark mode (Heroicons). Smooth transition on icon swap. The dark class is applied to <html> element to enable Tailwind dark mode for all child components.


WhatsAppIcon

File: Components/WhatsAppIcon.jsx Used In: WhatsApp send buttons, WhatsApp section headers, bulk WhatsApp campaign UI, customer communication panel

The official WhatsApp brand icon as an inline SVG component.

Props:

Prop Type Required Default Description
className string No "w-5 h-5" Tailwind size and color classes
color string No "#25D366" SVG fill color (use brand green)

Usage Example:

// Send WhatsApp button
<button className="flex items-center gap-2 text-green-600">
  <WhatsAppIcon className="w-5 h-5" />
  Send WhatsApp
</button>

// Icon-only button with tooltip
<button title="Send WhatsApp receipt">
  <WhatsAppIcon className="w-6 h-6" />
</button>

Notes:

Always use the official WhatsApp green (#25D366) for the icon color. Do not invert or recolor for decorative purposes per WhatsApp brand guidelines. On dark backgrounds, the icon remains green (do not make white).


AreaCrud

File: Components/AreaCrud.jsx Used In: Settings → Areas & Cities management page, NAS assignment forms

A complete CRUD UI component for managing geographic areas (cities and sub-areas/sectors).

Props:

Prop Type Required Default Description
areas array Yes Current list of area objects
cities array Yes Available cities for assignment
onSave function Yes Callback when area is saved/edited
onDelete function Yes Callback when area is deleted
canEdit boolean No true Whether edit controls are shown
canDelete boolean No true Whether delete button is shown

Data Format:

const areas = [
  { id: 1, name: "Johar Town", city_id: 1, city_name: "Lahore", customer_count: 145 },
  { id: 2, name: "DHA Phase 5", city_id: 1, city_name: "Lahore", customer_count: 98 },
];

Usage Example:

<AreaCrud
  areas={areas}
  cities={cities}
  onSave={(areaData) => router.post(route('areas.store'), areaData)}
  onDelete={(areaId) => router.delete(route('areas.destroy', areaId))}
/>

Notes:

Includes inline add/edit forms (not separate modals). Prevents deletion of areas with assigned customers (shows warning with customer count). Optimistic UI — shows changes immediately before server confirms.


UI Sub-Components (ui/ folder)

The resources/js/Components/ui/ folder contains lower-level primitives that are composed into the main components above.


ui/Badge

File: Components/ui/Badge.jsx Used In: Notification counts, system status indicators, package type labels

A simple badge/chip component for short labels.

Props:

Prop Type Required Default Description
variant string No "default" "default", "success", "warning", "danger", "info"
children ReactNode Yes Badge text/content
className string No "" Additional classes

Usage Example:

<Badge variant="success">Online</Badge>
<Badge variant="danger">3 Alerts</Badge>
<Badge variant="warning">Expiring Soon</Badge>

ui/Card

File: Components/ui/Card.jsx Used In: Dashboard stat widgets, form containers, detail page sections, report panels

A consistent card/panel container with shadow and border.

Props:

Prop Type Required Default Description
children ReactNode Yes Card content
className string No "" Additional classes
padding string No "p-6" Inner padding class

Usage Example:

<Card>
  <h3 className="text-lg font-semibold">Total Customers</h3>
  <p className="text-3xl font-bold mt-2">1,247</p>
</Card>

<Card padding="p-0">
  {/* Full-bleed table inside card */}
  <table>...</table>
</Card>

ui/Spinner

File: Components/ui/Spinner.jsx Used In: Loading states in data tables, async button states, page transition overlays

An animated loading spinner.

Props:

Prop Type Required Default Description
size string No "md" "sm", "md", "lg"
color string No "ember" Tailwind color token
className string No "" Additional classes

Usage Example:

// In table loading state
{loading ? (
  <div className="flex justify-center py-12">
    <Spinner size="lg" />
  </div>
) : (
  <CustomerTable data={customers} />
)}

// Inline in button
<PrimaryButton disabled={processing}>
  {processing && <Spinner size="sm" className="mr-2" />}
  Save
</PrimaryButton>

ui/EmptyState

File: Components/ui/EmptyState.jsx Used In: Customer list when no results, ticket list when empty, reports with no data

Displays a friendly empty state when a list or section has no data.

Props:

Prop Type Required Default Description
icon ReactNode No Icon to display (Heroicon component)
title string Yes Main empty state heading
description string No Supporting description
action ReactNode No CTA button (e.g., "Create first customer")

Usage Example:

<EmptyState
  icon={<UsersIcon className="w-12 h-12 text-gray-400" />}
  title="No customers found"
  description="No customers match your current filters. Try adjusting your search."
  action={
    <PrimaryButton onClick={() => router.visit(route('customers.create'))}>
      Add First Customer
    </PrimaryButton>
  }
/>

ui/StatCard

File: Components/ui/StatCard.jsx Used In: Dashboard KPI row (Total Customers, Active, Online Now, Revenue)

A pre-built card component for displaying a single metric with an icon, value, and trend.

Props:

Prop Type Required Default Description
title string Yes Metric label
value string/number Yes Metric value (formatted)
icon ReactNode No Icon component
trend object No { direction: 'up'/'down', percent: 8.1 }
color string No "ember" Brand color token
loading boolean No false Shows skeleton while loading

Usage Example:

<StatCard
  title="Online Now"
  value={stats.online_now}
  icon={<WifiIcon className="w-6 h-6" />}
  trend={{ direction: 'up', percent: 12 }}
  color="success"
/>

<StatCard
  title="Monthly Revenue"
  value={`PKR ${stats.monthly_revenue.toLocaleString()}`}
  icon={<CurrencyRupeeIcon className="w-6 h-6" />}
  color="ember"
/>

Component Usage Map

Quick reference for which component to use in each context:

Use Case Component
Show customer account state StatusPill
Confirm destructive action Modal + DangerButton
Navigation link in sidebar NavLink
Show historical bandwidth BandwidthChart
Show real-time speed LiveTrafficChart
Toggle dark/light mode ThemeToggle
Send WhatsApp button WhatsAppIcon + button
Form text field TextInput + InputLabel + InputError
Open dropdown menu Dropdown
Dashboard metric StatCard
Loading state Spinner
No data state EmptyState
Content container Card
Short label Badge

Adding New Components

When adding a new component to the library:

  1. File naming: Use PascalCase — MyComponent.jsx
  2. Location: resources/js/Components/ or resources/js/Components/ui/ for primitives
  3. Props documentation: Add JSDoc comment block at the top of the file
  4. Dark mode: Always add dark: variants for all color classes
  5. Accessibility: Add appropriate ARIA roles, labels, and keyboard handling
  6. Responsiveness: Mobile-first — test at 375px width minimum
  7. Export: Named export preferred (export function MyComponent)
/**
 * MyComponent — Brief description of what it does
 *
 * @param {string} title - Title to display
 * @param {boolean} [disabled=false] - Whether the component is disabled
 * @param {ReactNode} children - Child content
 */
export function MyComponent({ title, disabled = false, children }) {
  return (
    <div className={`... ${disabled ? 'opacity-50 pointer-events-none' : ''}`}>
      <h3 className="text-sm font-semibold text-gray-900 dark:text-white">
        {title}
      </h3>
      {children}
    </div>
  );
}

Last updated: 2026-07-13 | Maintained by: PyroNet Solutions Dev Team