Skip to main content

Component Library

This document provides documentation for the reusable components in the Kuviq application.

Overview​

Components are organized into these categories:

  • Common - Generic, reusable UI components
  • Forms - Form controls and layouts
  • Dashboard - Dashboard widgets
  • Inspections - Inspection-specific components
  • Layout - Page layout components

All components are built with Material-UI and follow consistent patterns for:

  • Accessibility (WCAG AA compliant)
  • Responsive design
  • Theme integration
  • TypeScript types

Common Components​

DataTable​

A feature-rich data table with sorting, filtering, and pagination.

import DataTable from '@/components/common/DataTable'

Props​

PropTypeDescription
dataT[]Array of data items
columnsColumn<T>[]Column definitions
loadingbooleanShow loading state
onRowClick(row: T) => voidRow click handler
sortablebooleanEnable sorting
filterablebooleanEnable filtering
paginationbooleanEnable pagination
pageSizenumberItems per page
selectablebooleanEnable row selection
onSelectionChange(selected: T[]) => voidSelection handler

Example​

<DataTable
data={items}
columns={[
{ field: 'name', header: 'Name', sortable: true },
{ field: 'status', header: 'Status', render: (row) => <StatusChip status={row.status} /> },
{ field: 'updatedAt', header: 'Updated', format: 'date' },
]}
onRowClick={(item) => navigate(`/items/${item.id}`)}
pagination
pageSize={25}
/>

List (Deprecated)​

Deprecated

The List component is deprecated as of November 2025. Use UnifiedDataTable from the design system instead. Scheduled for removal in Q1 2026.

A unified list component for displaying collections.

// Deprecated - use UnifiedDataTable instead
import List from '@/components/common/list/List'

// New approach
import { UnifiedDataTable } from '@/design-system'

Props​

PropTypeDescription
itemsT[]Array of items
renderItem(item: T) => ReactNodeItem renderer
loadingbooleanLoading state
emptyMessagestringEmpty state message
searchablebooleanEnable search
onSearch(query: string) => voidSearch handler

QRScannerClean​

QR code scanner using the device camera.

import QRScannerClean from '@/components/common/QRScannerClean'

Props​

PropTypeDescription
onScan(data: string) => voidScan success handler
onError(error: Error) => voidError handler
onClose() => voidClose handler
facingMode'user' | 'environment'Camera selection

Example​

<QRScannerClean
onScan={(data) => {
const itemId = parseQRCode(data)
navigate(`/items/${itemId}`)
}}
onError={(error) => showError('Failed to scan QR code')}
onClose={() => setShowScanner(false)}
/>

OptimizedImage​

Lazy-loaded image with placeholder and error handling.

import OptimizedImage from '@/components/common/OptimizedImage'

Props​

PropTypeDescription
srcstringImage source URL
altstringAlt text (required)
widthnumberImage width
heightnumberImage height
placeholderstringPlaceholder image
onLoad() => voidLoad handler
onError() => voidError handler

SessionWarning​

Displays session timeout warning dialog.

import SessionWarning from '@/components/common/SessionWarning'

Props​

PropTypeDescription
openbooleanDialog visibility
timeRemainingnumberSeconds until logout
onExtend() => voidExtend session
onLogout() => voidManual logout

UsageLimit​

Shows usage limit progress and warnings.

import UsageLimit from '@/components/common/UsageLimit'

Props​

PropTypeDescription
currentnumberCurrent usage
limitnumberMaximum limit
labelstringDisplay label
showWarningbooleanShow warning at 80%

Accessibility skip link for keyboard navigation.

import SkipLink from '@/components/common/SkipLink'

Usage​

Add at the top of your app:

<SkipLink />
<main id="main-content" role="main">
{/* Page content */}
</main>

Form Components​

FormLayout​

Consistent form layout wrapper.

import { FormLayout } from '@/components/forms/FormLayout'

Props​

PropTypeDescription
titlestringForm title
subtitlestringOptional subtitle
loadingbooleanLoading state
onSubmit() => voidSubmit handler
onCancel() => voidCancel handler
submitLabelstringSubmit button text
cancelLabelstringCancel button text

FormField​

Form field wrapper with label, error, and helper text.

import { FormField } from '@/components/forms/FormField'

Props​

PropTypeDescription
labelstringField label
requiredbooleanMark as required
errorstringError message
helperTextstringHelper text
childrenReactNodeInput element

Example​

<FormField label="Name" required error={errors.name}>
<TextField
value={values.name}
onChange={(e) => handleChange('name', e.target.value)}
/>
</FormField>

Dashboard Widgets​

DashboardWidget​

Base widget component for dashboard cards.

import DashboardWidget from '@/components/dashboard/DashboardWidget'

Props​

PropTypeDescription
titlestringWidget title
subtitlestringOptional subtitle
loadingbooleanLoading state
errorErrorError state
actionsReactNodeHeader actions
childrenReactNodeWidget content

StatusSummaryCards​

Displays inspection status summary with smart conditional rendering. Shows actionable status cards (Overdue, Due Today, Due This Week) when there are inspections needing attention, or an "All OK" message when everything is up to date.

import { StatusSummaryCards } from '@/components/dashboard/widgets/StatusSummaryCards'

Behavior​

  • When inspections need attention: Shows non-zero status cards:
    • Overdue (red) - Past due date
    • Due Today (yellow) - Scheduled for today
    • Due This Week (green) - Next 7 days (excluding today)
  • When all caught up: Shows "All inspections up to date" message, plus optional "Due This Week" card if any upcoming

Clicking each card navigates to the filtered inspections list:

  • Overdue → /inspections?view=overdue
  • Due Today → /inspections?view=due-today
  • Due This Week → /inspections?view=due-this-week

InspectionTrendsWidget​

Shows inspection trends over time.

import InspectionTrendsWidget from '@/components/dashboard/InspectionTrendsWidget'

Props​

PropTypeDescription
dataTrendData[]Trend data points
period'week' | 'month' | 'year'Time period

NextInspectionsList​

Lists upcoming inspections.

import NextInspectionsList from '@/components/dashboard/NextInspectionsList'

Props​

PropTypeDescription
inspectionsInspection[]Upcoming inspections
limitnumberMax items to show
onViewAll() => voidView all handler

QuickActions​

Quick action buttons for common operations.

import QuickActions from '@/components/dashboard/QuickActions'

ScanButton​

QR scan action button.

import ScanButton from '@/components/dashboard/ScanButton'

Props​

PropTypeDescription
onScan(itemId: string) => voidScan handler
disabledbooleanDisabled state

Inspection Components​

InspectionList​

Displays list of inspections with actions.

import InspectionList from '@/components/inspections/InspectionList'

Props​

PropTypeDescription
inspectionsInspection[]Inspection list
loadingbooleanLoading state
onView(inspection: Inspection) => voidView handler
onStart(inspection: Inspection) => voidStart handler
onPause(inspection: Inspection) => voidPause handler
onResume(inspection: Inspection) => voidResume handler
onCancel(inspection: Inspection) => voidCancel handler

InspectionExecution​

Inspection execution interface.

import InspectionExecution from '@/components/inspections/InspectionExecution'

TemplateList​

Displays inspection templates.

import TemplateList from '@/components/inspections/TemplateList'

TemplateForm​

Template creation/editing form.

import TemplateForm from '@/components/inspections/TemplateForm'

Layout Components​

PageLayout​

Standard page layout with header and navigation.

import PageLayout from '@/components/layout/PageLayout'

Props​

PropTypeDescription
titlestringPage title
breadcrumbsBreadcrumb[]Breadcrumb trail
actionsReactNodeHeader actions
childrenReactNodePage content

Side navigation drawer.

import NavigationDrawer from '@/components/layout/NavigationDrawer'

ErrorBoundary​

React error boundary for graceful error handling.

import ErrorBoundary from '@/components/layout/ErrorBoundary'

Example​

<ErrorBoundary fallback={<ErrorPage />}>
<Dashboard />
</ErrorBoundary>

Trial & Onboarding Components​

OnboardingChecklist​

Dashboard widget that guides new organizations through 6 key setup actions with industry-aware labels and live usage tracking.

import { OnboardingChecklist } from '@/components/trial/OnboardingChecklist'

Behavior​

  • Renders on the dashboard for trial orgs and non-trial orgs created within the last 30 days
  • Shows 6 checklist items tied to live usage counters from OrganizationContext
  • Incomplete items are clickable, navigating to the relevant creation route
  • For trial orgs, displays days remaining with color-coded urgency (green >7d, amber ≤3d, red 1d)
  • Shows a celebration state with upgrade CTA when all 6 items complete
  • Collapsible — collapsed state shows only header and progress bar
  • Dismissible — sets organization.onboardingChecklist.dismissed = true in Firestore

Industry Awareness​

Labels and tips adapt based on organization.industry (climbing_gym, high_ropes, family_entertainment, other). Falls back to generic labels when no industry is set.


TrialExpiredWall​

Full-screen blocking modal that appears when subscriptionStatus = 'expired'.

import { TrialExpiredWall } from '@/components/trial/TrialExpiredWall'

Behavior​

  • Overlays all app routes; cannot be dismissed via click-outside or Escape
  • Appears immediately on auth and mid-session when TrialContext detects expiration (60-minute refresh)
  • Shows trial activity summary (items, inspections, templates, locations created)
  • Three actions:
    • Upgrade Now — navigates to /subscription/plans, wall dismissed during subscription flow
    • Request Extension — inline form (visible only when trialStatus.canExtend === true)
    • Export My Data — opens ExportDialog for CSV export
  • Exempt routes: /subscription/*, /admin/* remain accessible

Permission Components​

PermissionGuard​

Conditionally renders children based on permissions.

import PermissionGuard from '@/components/permissions/PermissionGuard'

Props​

PropTypeDescription
permissionPermissionRequired permission
permissionsPermission[]Any of these permissions
requireAllbooleanRequire all permissions
fallbackReactNodeFallback when denied
childrenReactNodeProtected content

Example​

<PermissionGuard permission="items.delete">
<Button onClick={handleDelete}>Delete Item</Button>
</PermissionGuard>

FeatureGate​

Conditionally renders based on subscription features.

import FeatureGate from '@/components/permissions/FeatureGate'

Props​

PropTypeDescription
featurestringFeature name
fallbackReactNodeUpgrade prompt
childrenReactNodeFeature content

Export Components​

ExportDialog​

Main export dialog that orchestrates the export workflow with templates and custom configuration.

import { ExportDialog } from '@/components/common/export/ExportDialog'

Props​

PropTypeDescription
openbooleanDialog visibility
onClose() => voidClose handler
entityTypeEntityTypeType of data to export
titlestringDialog title
selectedIdsstring[]Pre-selected record IDs
defaultConfigPartial<ExportConfig>Default configuration

Sub-Components​

The ExportDialog uses these modular sub-components for configuration:

ComponentDescription
ExportBasicConfigFormat selection and filename configuration
ExportFieldSelectionField picker with headers/metadata options
ExportFilterConfigDynamic filter rule management
ExportConditionalFormattingExcel-specific cell styling rules
ExportFormatOptionsDate/number formats, CSV delimiter, Excel multi-sheet

Example​

<ExportDialog
open={showExport}
onClose={() => setShowExport(false)}
entityType="items"
title="Items"
selectedIds={selectedItemIds}
/>

ExportTemplateManager​

Manages export templates with CRUD operations.

import { ExportTemplateManager } from '@/components/common/export/ExportTemplateManager'

Props​

PropTypeDescription
openbooleanDialog visibility
onClose() => voidClose handler
onTemplateSelect(template: ExportTemplate) => voidTemplate selection handler

Best Practices​

Accessibility​

All components follow WCAG AA guidelines:

  • Minimum 4.5:1 color contrast
  • Keyboard navigation support
  • ARIA attributes where needed
  • Focus management

Responsive Design​

Components use MUI breakpoints:

sx={{
width: { xs: '100%', sm: '50%', md: '33%' },
padding: { xs: 2, md: 3 },
}}

Touch Targets​

Minimum 44px touch targets on mobile:

sx={{
minWidth: { '@media (pointer: coarse)': 44 },
minHeight: { '@media (pointer: coarse)': 44 },
}}

Responsive Button Layout​

Button groups must be responsive. Use column layout on mobile with full-width buttons:

import { useTheme, useMediaQuery, Box, Button } from '@mui/material'

function MyComponent() {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))

return (
<Box
sx={{
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
gap: 1,
}}
>
<Button variant="outlined" fullWidth={isMobile}>
Cancel
</Button>
<Button variant="contained" fullWidth={isMobile}>
{isMobile ? 'Save' : 'Save Changes'}
</Button>
</Box>
)
}

Key patterns:

  • Use fullWidth={isMobile} on all action buttons
  • Use column layout on mobile, row on desktop: flexDirection: isMobile ? 'column' : 'row'
  • Shorten button text on mobile when needed (e.g., "Save" vs "Save Changes")
  • For dialogs, use fullScreen={isMobile} prop on the Dialog component
  • For DialogActions, apply custom styling to stack buttons:
    <DialogActions
    sx={{
    flexDirection: isMobile ? 'column' : 'row',
    gap: isMobile ? 1 : 0,
    p: isMobile ? 2 : undefined,
    '& > :not(:first-of-type)': { ml: isMobile ? 0 : undefined },
    }}
    >

Theme Integration​

Use theme tokens for consistency:

sx={{
color: 'primary.main',
backgroundColor: 'background.paper',
borderColor: 'divider',
}}

TypeScript​

All components are fully typed:

interface MyComponentProps {
title: string
onAction?: () => void
children: React.ReactNode
}

export default function MyComponent({ title, onAction, children }: MyComponentProps) {
// ...
}