mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-13 17:44:44 -05:00
feat: polish invoice editor and viewer UI with custom NumberInput
component - Create custom NumberInput component with increment/decrement buttons - Add 0.25 step increments for hours and rates in invoice forms - Implement emerald-themed styling with hover states and accessibility - Add keyboard navigation (arrow keys) and proper ARIA support - Condense invoice editor tax/totals section into efficient grid layout - Update client dropdown to single-line format (name + email) - Add fixed footer with floating action bar pattern matching business forms - Redesign invoice viewer with better space utilization and visual hierarchy - Maintain professional appearance and consistent design system - Fix Next.js 15 params Promise handling across all invoice pages - Resolve TypeScript compilation errors and type-only imports
This commit is contained in:
138
docs/RESPONSIVE_TABLE_EXAMPLES.md
Normal file
138
docs/RESPONSIVE_TABLE_EXAMPLES.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Responsive Table Examples
|
||||
|
||||
This document shows how tables adapt across different screen sizes in the beenvoice application.
|
||||
|
||||
## Mobile View (< 640px)
|
||||
|
||||
### Invoices Table
|
||||
- **Visible**: Invoice number, client name, amount, status, actions
|
||||
- **Hidden**: Issue date, due date (shown on detail view)
|
||||
- **Features**: Compact spacing, smaller buttons, simplified pagination
|
||||
|
||||
### Clients Table
|
||||
- **Visible**: Name with email, actions
|
||||
- **Hidden**: Phone, address, created date
|
||||
- **Icon**: Hidden on mobile to save space
|
||||
|
||||
### Businesses Table
|
||||
- **Visible**: Name with email, actions
|
||||
- **Hidden**: Phone, address, tax ID, website
|
||||
- **Icon**: Hidden on mobile to save space
|
||||
|
||||
## Tablet View (640px - 1024px)
|
||||
|
||||
### Invoices Table
|
||||
- **Added**: Issue date column
|
||||
- **Still Hidden**: Due date (less critical than issue date)
|
||||
- **Features**: Search bar expands, column visibility toggle appears
|
||||
|
||||
### Clients Table
|
||||
- **Added**: Phone column, client icon
|
||||
- **Still Hidden**: Address, created date
|
||||
- **Features**: Better spacing, full search functionality
|
||||
|
||||
### Businesses Table
|
||||
- **Added**: Phone column, business icon
|
||||
- **Still Hidden**: Address, tax ID
|
||||
- **Features**: Website links become visible
|
||||
|
||||
## Desktop View (> 1024px)
|
||||
|
||||
### All Tables
|
||||
- **Full Features**: All columns visible
|
||||
- **Enhanced**:
|
||||
- Full pagination controls with page size selector
|
||||
- Column visibility toggle
|
||||
- Advanced filters
|
||||
- Comfortable spacing
|
||||
- All metadata visible
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Responsive Column Definition
|
||||
```tsx
|
||||
// Hide on mobile, show on tablet and up
|
||||
{
|
||||
accessorKey: "phone",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Phone" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span className="hidden md:inline">{row.original.phone || "—"}</span>
|
||||
),
|
||||
}
|
||||
|
||||
// Hide on mobile and tablet, show on desktop
|
||||
{
|
||||
id: "address",
|
||||
header: "Address",
|
||||
cell: ({ row }) => (
|
||||
<span className="hidden lg:inline">{formatAddress(row.original)}</span>
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Cell Content
|
||||
```tsx
|
||||
// Icon hidden on mobile
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="hidden rounded-lg bg-status-info-muted p-2 sm:flex">
|
||||
<UserPlus className="h-4 w-4 text-status-info" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-medium">{client.name}</p>
|
||||
<p className="truncate text-sm text-muted-foreground">
|
||||
{client.email || "—"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Responsive Actions
|
||||
```tsx
|
||||
// Compact action buttons that work on all screen sizes
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Filter Bar Behavior
|
||||
|
||||
### Mobile
|
||||
- Search input takes full width
|
||||
- Filter dropdowns stack vertically
|
||||
- Column visibility hidden
|
||||
- Clear filters button visible when filters active
|
||||
|
||||
### Tablet+
|
||||
- Search input limited to max-width
|
||||
- Filter dropdowns in horizontal row
|
||||
- Column visibility toggle appears
|
||||
- All controls in single row
|
||||
|
||||
## Pagination Behavior
|
||||
|
||||
### Mobile
|
||||
- Simplified page indicator (1/5 format)
|
||||
- Compact button spacing
|
||||
- Page size selector with smaller text
|
||||
|
||||
### Desktop
|
||||
- Full "Page 1 of 5" text
|
||||
- Comfortable button spacing
|
||||
- First/Last page buttons visible
|
||||
- Entries count with detailed information
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Priority Content**: Always show the most important data on mobile
|
||||
2. **Progressive Enhancement**: Add columns as screen size increases
|
||||
3. **Touch Targets**: Maintain 44px minimum touch targets on mobile
|
||||
4. **Text Truncation**: Use `truncate` class for long text in narrow columns
|
||||
5. **Icon Usage**: Hide decorative icons on mobile, keep functional ones
|
||||
6. **Testing**: Always test at 375px (iPhone SE), 768px (iPad), and 1440px (Desktop)
|
||||
324
docs/UI_UNIFORMITY_GUIDE.md
Normal file
324
docs/UI_UNIFORMITY_GUIDE.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# UI Uniformity Guide for beenvoice
|
||||
|
||||
## Overview
|
||||
|
||||
This guide documents the unified component system implemented across the beenvoice application to ensure consistent UI/UX patterns. The system follows a hierarchical approach where:
|
||||
|
||||
1. **CSS Variables** (in `globals.css`) define the design tokens
|
||||
2. **UI Components** (in `components/ui`) consume these variables
|
||||
3. **Pages** use components with minimal additional styling
|
||||
|
||||
## Design System Principles
|
||||
|
||||
### 1. Variable-Based Theming
|
||||
All colors, spacing, and other design tokens are defined as CSS variables in `globals.css`:
|
||||
- Brand colors: `--brand-primary`, `--brand-secondary`
|
||||
- Status colors: `--status-success`, `--status-warning`, `--status-error`, `--status-info`
|
||||
- Semantic colors: `--background`, `--foreground`, `--muted`, etc.
|
||||
|
||||
### 2. Component Composition
|
||||
Complex UI patterns are built from smaller, reusable components rather than duplicating code.
|
||||
|
||||
### 3. Minimal Page-Level Styling
|
||||
Pages should primarily compose pre-built components and avoid custom Tailwind classes where possible.
|
||||
|
||||
## Core Unified Components
|
||||
|
||||
### Page Layout Components
|
||||
|
||||
#### `PageContent`
|
||||
Wraps page content with consistent spacing:
|
||||
```tsx
|
||||
<PageContent spacing="default">
|
||||
{/* Page sections */}
|
||||
</PageContent>
|
||||
```
|
||||
|
||||
#### `PageSection`
|
||||
Groups related content with optional title and actions:
|
||||
```tsx
|
||||
<PageSection
|
||||
title="Section Title"
|
||||
description="Optional description"
|
||||
actions={<Button>Action</Button>}
|
||||
>
|
||||
{/* Section content */}
|
||||
</PageSection>
|
||||
```
|
||||
|
||||
#### `PageGrid`
|
||||
Responsive grid layout with preset column options:
|
||||
```tsx
|
||||
<PageGrid columns={3} gap="default">
|
||||
{/* Grid items */}
|
||||
</PageGrid>
|
||||
```
|
||||
|
||||
### Data Display Components
|
||||
|
||||
#### `DataTable`
|
||||
Unified table component using @tanstack/react-table with floating card design:
|
||||
```tsx
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { DataTable, DataTableColumnHeader } from "~/components/ui/data-table";
|
||||
import { PageSection } from "~/components/ui/page-layout";
|
||||
|
||||
const columns: ColumnDef<DataType>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const name = row.getValue("name") as string;
|
||||
return <div className="font-medium">{name}</div>;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
const item = row.original;
|
||||
return (
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||
<Edit className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const filterableColumns = [
|
||||
{
|
||||
id: "status",
|
||||
title: "Status",
|
||||
options: [
|
||||
{ label: "Active", value: "active" },
|
||||
{ label: "Inactive", value: "inactive" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Wrap in PageSection for title/description
|
||||
<PageSection
|
||||
title="Table Title"
|
||||
description="Optional description"
|
||||
>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
searchPlaceholder="Search by name..."
|
||||
filterableColumns={filterableColumns}
|
||||
/>
|
||||
</PageSection>
|
||||
```
|
||||
|
||||
Features:
|
||||
- **Floating Card Design**: Three separate cards for filter bar, table content, and pagination
|
||||
- **Filter Bar Card**: Minimal padding (p-3) with global search and column filters
|
||||
- **Table Content Card**: Clean borders with overflow handling
|
||||
- **Pagination Card**: Compact controls with page size selector
|
||||
- **Responsive Design**: Mobile-optimized with hidden columns on smaller screens
|
||||
- **Tight Appearance**: Compact spacing with smaller action buttons
|
||||
- **Sorting**: Visual indicators with proper arrow directions
|
||||
- **Column Visibility**: Toggle columns (hidden on mobile)
|
||||
- **Dark Mode**: Consistent styling across light/dark themes
|
||||
- **Loading States**: DataTableSkeleton component with matching card structure
|
||||
|
||||
#### `StatsCard`
|
||||
Displays statistics with consistent styling:
|
||||
```tsx
|
||||
<StatsCard
|
||||
title="Total Revenue"
|
||||
value="$10,000"
|
||||
icon={DollarSign}
|
||||
description="From 50 invoices"
|
||||
variant="success"
|
||||
/>
|
||||
```
|
||||
|
||||
#### `QuickActionCard`
|
||||
Interactive cards for navigation or actions:
|
||||
```tsx
|
||||
<QuickActionCard
|
||||
title="Create Invoice"
|
||||
description="Start a new invoice"
|
||||
icon={Plus}
|
||||
variant="success"
|
||||
>
|
||||
<Link href="/invoices/new">
|
||||
<div className="h-full w-full" />
|
||||
</Link>
|
||||
</QuickActionCard>
|
||||
```
|
||||
|
||||
### Feedback Components
|
||||
|
||||
#### `EmptyState`
|
||||
Consistent empty state displays:
|
||||
```tsx
|
||||
<EmptyState
|
||||
icon={<FileText className="h-8 w-8" />}
|
||||
title="No invoices yet"
|
||||
description="Create your first invoice to get started"
|
||||
action={<Button>Create Invoice</Button>}
|
||||
/>
|
||||
```
|
||||
|
||||
## Component Variants
|
||||
|
||||
### Color Variants
|
||||
Most components support these variants:
|
||||
- `default` - Uses default theme colors
|
||||
- `success` - Green color scheme for positive states
|
||||
- `warning` - Orange/amber for warnings
|
||||
- `error` - Red for errors or destructive actions
|
||||
- `info` - Blue for informational content
|
||||
|
||||
### Size Variants
|
||||
- `sm` - Small size
|
||||
- `default` - Normal size
|
||||
- `lg` - Large size
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Standard Page Structure
|
||||
```tsx
|
||||
export default function ExamplePage() {
|
||||
return (
|
||||
<PageContent>
|
||||
<PageHeader
|
||||
title="Page Title"
|
||||
description="Page description"
|
||||
variant="gradient"
|
||||
>
|
||||
<Button variant="brand">
|
||||
Primary Action
|
||||
</Button>
|
||||
</PageHeader>
|
||||
|
||||
<PageSection>
|
||||
<PageGrid columns={4}>
|
||||
<StatsCard {...statsProps} />
|
||||
</PageGrid>
|
||||
</PageSection>
|
||||
|
||||
<PageSection
|
||||
title="Data Table Title"
|
||||
description="Table description"
|
||||
>
|
||||
<DataTable {...tableProps} />
|
||||
</PageSection>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Consistent Button Usage
|
||||
```tsx
|
||||
// Primary actions
|
||||
<Button variant="brand">Create New</Button>
|
||||
|
||||
// Secondary actions
|
||||
<Button variant="outline">Cancel</Button>
|
||||
|
||||
// Destructive actions
|
||||
<Button variant="destructive">Delete</Button>
|
||||
|
||||
// Icon-only actions
|
||||
<Button variant="ghost" size="icon">
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Styling Guidelines
|
||||
|
||||
### Do's
|
||||
- ✅ Use predefined color variables from globals.css
|
||||
- ✅ Compose existing UI components
|
||||
- ✅ Use semantic variant names (success, error, etc.)
|
||||
- ✅ Follow the established spacing patterns
|
||||
- ✅ Use the PageLayout components for structure
|
||||
|
||||
### Don'ts
|
||||
- ❌ Add custom colors directly in components
|
||||
- ❌ Create one-off table or card implementations
|
||||
- ❌ Override component styles with important flags
|
||||
- ❌ Use arbitrary spacing values
|
||||
- ❌ Mix different UI patterns on the same page
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
When updating a page to use the unified system:
|
||||
|
||||
1. Replace custom tables with `DataTable` using @tanstack/react-table ColumnDef
|
||||
2. Replace statistics displays with `StatsCard`
|
||||
3. Replace action cards with `QuickActionCard`
|
||||
4. Wrap content in `PageContent` and `PageSection`
|
||||
5. Use `PageGrid` for responsive layouts
|
||||
6. Replace custom empty states with `EmptyState`
|
||||
7. Update buttons to use the `brand` variant for primary actions
|
||||
8. Remove page-specific color classes
|
||||
9. Use `DataTableColumnHeader` for sortable column headers
|
||||
10. Use `DataTableSkeleton` for loading states
|
||||
|
||||
## Color System Reference
|
||||
|
||||
### Brand Colors
|
||||
- Primary: Green (`#16a34a` / `oklch(0.646 0.222 164.25)`)
|
||||
- Secondary: Teal/cyan shades
|
||||
- Gradients: Use `bg-brand-gradient` class
|
||||
|
||||
### Status Colors
|
||||
- Success: Green shades
|
||||
- Warning: Amber/orange shades
|
||||
- Error: Red shades
|
||||
- Info: Blue shades
|
||||
|
||||
### Semantic Colors
|
||||
- Background: White/dark gray
|
||||
- Foreground: Black/white text
|
||||
- Muted: Gray shades for secondary content
|
||||
- Border: Light gray borders
|
||||
|
||||
## Component Documentation
|
||||
|
||||
For detailed component APIs and props, refer to:
|
||||
- `/src/components/ui/data-table.tsx` - TanStack Table-based data table with sorting, filtering, and pagination
|
||||
- `/src/components/ui/stats-card.tsx` - Statistics display cards
|
||||
- `/src/components/ui/quick-action-card.tsx` - Interactive action cards
|
||||
- `/src/components/ui/page-layout.tsx` - Page structure components
|
||||
|
||||
### DataTable Props
|
||||
- `columns`: ColumnDef array from @tanstack/react-table
|
||||
- `data`: Array of data to display
|
||||
- `searchPlaceholder?`: Placeholder text for search input
|
||||
- `showColumnVisibility?`: Show/hide column visibility toggle (default: true)
|
||||
- `showPagination?`: Show/hide pagination controls (default: true)
|
||||
- `showSearch?`: Show/hide search input (default: true)
|
||||
- `pageSize?`: Number of items per page (default: 10)
|
||||
- `filterableColumns?`: Array of column filters with options
|
||||
|
||||
Note: `title` and `description` should be provided via the wrapping `PageSection` component for consistent spacing and typography.
|
||||
|
||||
### Responsive Table Guidelines
|
||||
- Use `hidden sm:flex` classes for icons in table cells
|
||||
- Use `hidden md:inline` for less important columns on mobile
|
||||
- Use `min-w-0` and `truncate` for text that might overflow
|
||||
- Keep action buttons small with `h-8 w-8 p-0` sizing
|
||||
- Test tables at all breakpoints (mobile, tablet, desktop)
|
||||
|
||||
## Future Considerations
|
||||
|
||||
1. **Form Components**: Create unified form field components
|
||||
2. **Modal Patterns**: Standardize modal and dialog usage
|
||||
3. **Loading States**: Create consistent skeleton loaders
|
||||
4. **Animation**: Define standard transition patterns
|
||||
5. **Icons**: Establish icon usage guidelines
|
||||
|
||||
## Maintenance
|
||||
|
||||
To maintain UI consistency:
|
||||
1. Always check for existing components before creating new ones
|
||||
2. Update this guide when adding new unified components
|
||||
3. Review PRs for adherence to these patterns
|
||||
4. Refactor pages that deviate from the system
|
||||
198
docs/breadcrumbs-guide.md
Normal file
198
docs/breadcrumbs-guide.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Dynamic Breadcrumbs Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The breadcrumb system in beenvoice automatically generates navigation trails based on the current URL path. It features intelligent pluralization, proper capitalization, and dynamic resource name fetching.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Automatic Pluralization
|
||||
|
||||
The breadcrumb system intelligently handles singular and plural forms:
|
||||
|
||||
- **List pages** (e.g., `/dashboard/businesses`) → "Businesses"
|
||||
- **Detail pages** (e.g., `/dashboard/businesses/[id]`) → "Business"
|
||||
- **New pages** (e.g., `/dashboard/businesses/new`) → "Business" (singular context)
|
||||
|
||||
### 2. Smart Capitalization
|
||||
|
||||
All route segments are automatically capitalized:
|
||||
- `businesses` → "Businesses"
|
||||
- `clients` → "Clients"
|
||||
- `invoices` → "Invoices"
|
||||
|
||||
### 3. Dynamic Resource Names
|
||||
|
||||
Instead of showing UUIDs, breadcrumbs fetch and display actual resource names:
|
||||
- `/dashboard/clients/123e4567-e89b-12d3-a456-426614174000` → "Dashboard / Clients / John Doe"
|
||||
- `/dashboard/invoices/987fcdeb-51a2-43f1-b321-123456789abc` → "Dashboard / Invoices / INV-2024-001"
|
||||
|
||||
### 4. Context-Aware Labels
|
||||
|
||||
Special pages are handled intelligently:
|
||||
- **Edit pages**: Show the resource name instead of "Edit" as the last breadcrumb
|
||||
- **New pages**: Show "New" as the last breadcrumb
|
||||
- **Import/Export pages**: Show appropriate action labels
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Pluralization Rules
|
||||
|
||||
The system uses a comprehensive pluralization utility (`src/lib/pluralize.ts`) that handles:
|
||||
|
||||
```typescript
|
||||
// Common business terms
|
||||
business → businesses
|
||||
client → clients
|
||||
invoice → invoices
|
||||
category → categories
|
||||
company → companies
|
||||
|
||||
// General rules
|
||||
- Words ending in 's', 'ss', 'sh', 'ch', 'x', 'z' → add 'es'
|
||||
- Words ending in consonant + 'y' → change to 'ies'
|
||||
- Words ending in 'f' or 'fe' → change to 'ves'
|
||||
- Default → add 's'
|
||||
```
|
||||
|
||||
### Resource Fetching
|
||||
|
||||
The breadcrumbs automatically detect resource IDs and fetch the appropriate data:
|
||||
|
||||
```typescript
|
||||
// Detects UUID patterns in the URL
|
||||
const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
|
||||
|
||||
// Fetches data based on resource type
|
||||
- Clients: Shows client name
|
||||
- Invoices: Shows invoice number or formatted date
|
||||
- Businesses: Shows business name
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
While fetching resource data, breadcrumbs show loading skeletons:
|
||||
```tsx
|
||||
<Skeleton className="inline-block h-5 w-24 align-middle" />
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic List Page
|
||||
**URL**: `/dashboard/clients`
|
||||
**Breadcrumbs**: Dashboard / Clients
|
||||
|
||||
### Resource Detail Page
|
||||
**URL**: `/dashboard/clients/550e8400-e29b-41d4-a716-446655440000`
|
||||
**Breadcrumbs**: Dashboard / Clients / Jane Smith
|
||||
|
||||
### Resource Edit Page
|
||||
**URL**: `/dashboard/businesses/550e8400-e29b-41d4-a716-446655440000/edit`
|
||||
**Breadcrumbs**: Dashboard / Businesses / Acme Corp
|
||||
*(Note: "Edit" is hidden when showing the resource name)*
|
||||
|
||||
### New Resource Page
|
||||
**URL**: `/dashboard/invoices/new`
|
||||
**Breadcrumbs**: Dashboard / Invoices / New
|
||||
|
||||
### Nested Resources
|
||||
**URL**: `/dashboard/clients/550e8400-e29b-41d4-a716-446655440000/invoices`
|
||||
**Breadcrumbs**: Dashboard / Clients / John Doe / Invoices
|
||||
|
||||
## Customization
|
||||
|
||||
### Adding New Resource Types
|
||||
|
||||
To add a new resource type, update the pluralization rules:
|
||||
|
||||
```typescript
|
||||
// In src/lib/pluralize.ts
|
||||
const PLURALIZATION_RULES = {
|
||||
// ... existing rules
|
||||
product: { singular: "Product", plural: "Products" },
|
||||
service: { singular: "Service", plural: "Services" },
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Resource Labels
|
||||
|
||||
For resources that need custom display logic, add to the breadcrumb component:
|
||||
|
||||
```typescript
|
||||
// For invoices, show invoice number instead of ID
|
||||
if (prevSegment === "invoices") {
|
||||
label = invoice.invoiceNumber || format(new Date(invoice.issueDate), "MMM dd, yyyy");
|
||||
}
|
||||
```
|
||||
|
||||
### Special Segments
|
||||
|
||||
Add new special segments to the `SPECIAL_SEGMENTS` object:
|
||||
|
||||
```typescript
|
||||
const SPECIAL_SEGMENTS = {
|
||||
new: "New",
|
||||
edit: "Edit",
|
||||
import: "Import",
|
||||
export: "Export",
|
||||
duplicate: "Duplicate",
|
||||
archive: "Archive",
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistent Naming**: Use consistent URL patterns across your app
|
||||
- List pages: `/dashboard/[resource]`
|
||||
- Detail pages: `/dashboard/[resource]/[id]`
|
||||
- Actions: `/dashboard/[resource]/[id]/[action]`
|
||||
|
||||
2. **Resource Fetching**: Only fetch data when needed
|
||||
- Check resource type before enabling queries
|
||||
- Use proper loading states
|
||||
|
||||
3. **Error Handling**: Handle cases where resources don't exist
|
||||
- Show fallback text or maintain UUID display
|
||||
- Don't break the breadcrumb trail
|
||||
|
||||
4. **Performance**: Breadcrumb queries are lightweight
|
||||
- Only fetch minimal data (id, name)
|
||||
- Use React Query caching effectively
|
||||
|
||||
## API Integration
|
||||
|
||||
The breadcrumb component integrates with tRPC routers:
|
||||
|
||||
```typescript
|
||||
// Each resource router should have a getById method
|
||||
getById: protectedProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
// Return resource with at least id and name/title
|
||||
})
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Breadcrumbs use semantic HTML with proper ARIA labels
|
||||
- Each segment is a link except the current page
|
||||
- Proper keyboard navigation support
|
||||
- Screen reader friendly with role="navigation"
|
||||
|
||||
## Responsive Design
|
||||
|
||||
- Breadcrumbs wrap on smaller screens
|
||||
- Font sizes adjust: `text-sm sm:text-base`
|
||||
- Separators scale appropriately
|
||||
- Loading skeletons match text size
|
||||
|
||||
## Migration from Static Breadcrumbs
|
||||
|
||||
If migrating from hardcoded breadcrumbs:
|
||||
|
||||
1. Remove static breadcrumb definitions
|
||||
2. Ensure URLs follow consistent patterns
|
||||
3. Add getById methods to resource routers
|
||||
4. Update imports to use `DashboardBreadcrumbs`
|
||||
|
||||
The dynamic system will automatically generate appropriate breadcrumbs based on the URL structure.
|
||||
154
docs/data-table-improvements.md
Normal file
154
docs/data-table-improvements.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Data Table Improvements Summary
|
||||
|
||||
## Overview
|
||||
|
||||
The data table component has been significantly improved to address padding, scaling, and responsiveness issues. The tables now provide a cleaner, more compact appearance while maintaining excellent usability across all device sizes.
|
||||
|
||||
## Key Improvements Made
|
||||
|
||||
### 1. Tighter, More Consistent Padding
|
||||
|
||||
**Before:**
|
||||
- Inconsistent padding across different table sections
|
||||
- Excessive vertical padding making tables feel loose
|
||||
- Cards had default py-6 padding that was too spacious
|
||||
|
||||
**After:**
|
||||
- Table cells: `py-1.5` (mobile) / `py-2` (desktop) - reduced from `py-2.5` / `py-3`
|
||||
- Table headers: `h-9` (mobile) / `h-10` (desktop) - reduced from `h-10` / `h-12`
|
||||
- Filter/pagination cards: `py-2` with `px-3` horizontal padding
|
||||
- Table card: `p-0` to wrap content tightly
|
||||
|
||||
### 2. Improved Responsive Column Handling
|
||||
|
||||
**Before:**
|
||||
```tsx
|
||||
// Cells would hide but headers remained visible
|
||||
cell: ({ row }) => (
|
||||
<span className="hidden md:inline">{row.original.phone}</span>
|
||||
),
|
||||
```
|
||||
|
||||
**After:**
|
||||
```tsx
|
||||
// Both header and cell hide together
|
||||
cell: ({ row }) => row.original.phone || "—",
|
||||
meta: {
|
||||
headerClassName: "hidden md:table-cell",
|
||||
cellClassName: "hidden md:table-cell",
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Better Small Card Appearance
|
||||
|
||||
- Filter card: Compact `py-2` padding with proper horizontal spacing
|
||||
- Pagination card: Matching `py-2` padding for consistency
|
||||
- Content aligned properly within smaller card boundaries
|
||||
- Removed excessive gaps between elements
|
||||
- Search box now has consistent padding without extra bottom spacing on mobile
|
||||
|
||||
### 4. Responsive Font Sizing
|
||||
|
||||
- Base text: `text-xs` on mobile, `text-sm` on desktop
|
||||
- Consistent scaling across all table elements
|
||||
- Better readability on small screens without wasting space
|
||||
|
||||
## Visual Comparison
|
||||
|
||||
### Table Density
|
||||
- **Before**: ~60px per row with excessive padding
|
||||
- **After**: ~40px per row with comfortable but efficient spacing
|
||||
|
||||
### Card Heights
|
||||
- **Filter Card**: Reduced from ~80px to ~56px
|
||||
- **Pagination Card**: Reduced from ~72px to ~48px
|
||||
- **Table Card**: Now wraps content exactly with no extra space
|
||||
- **Pagination Layout**: Entry count and pagination controls now stay on the same line on mobile
|
||||
|
||||
## Implementation Examples
|
||||
|
||||
### Responsive Column Definition
|
||||
```tsx
|
||||
const columns: ColumnDef<DataType>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => row.original.name,
|
||||
// Always visible
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Email" />
|
||||
),
|
||||
cell: ({ row }) => row.original.email,
|
||||
meta: {
|
||||
// Hidden on mobile, visible on tablets and up
|
||||
headerClassName: "hidden md:table-cell",
|
||||
cellClassName: "hidden md:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({ row }) => formatDate(row.getValue("createdAt")),
|
||||
meta: {
|
||||
// Only visible on large screens
|
||||
headerClassName: "hidden lg:table-cell",
|
||||
cellClassName: "hidden lg:table-cell",
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Page Header Actions
|
||||
Page headers now properly position action buttons to the right on all screen sizes:
|
||||
|
||||
```tsx
|
||||
<PageHeader
|
||||
title="Invoices"
|
||||
description="Manage your invoices and track payments"
|
||||
variant="gradient"
|
||||
>
|
||||
<Button asChild variant="brand" size="lg">
|
||||
<Link href="/dashboard/invoices/new">
|
||||
<Plus className="mr-2 h-5 w-5" /> New Invoice
|
||||
</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
```
|
||||
|
||||
### Breakpoint Reference
|
||||
- `sm`: 640px and up
|
||||
- `md`: 768px and up
|
||||
- `lg`: 1024px and up
|
||||
- `xl`: 1280px and up
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **More Data Visible**: Tighter spacing allows more rows to be visible without scrolling
|
||||
2. **Professional Appearance**: Clean, compact design suitable for business applications
|
||||
3. **Better Mobile UX**: Properly hidden columns prevent layout breaking
|
||||
4. **Consistent Styling**: All table instances now follow the same spacing rules
|
||||
5. **Performance**: CSS-only solution with no JavaScript overhead
|
||||
6. **Improved Mobile Layout**: Pagination controls stay inline with entry count on mobile
|
||||
7. **Consistent Header Actions**: Action buttons properly positioned to the right
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
- [x] Update column definitions to use `meta` properties
|
||||
- [x] Remove inline responsive classes from cell content
|
||||
- [x] Test on actual mobile devices
|
||||
- [x] Verify touch targets remain accessible (min 44x44px)
|
||||
- [x] Check that critical data remains visible on small screens
|
||||
|
||||
## Best Practices Going Forward
|
||||
|
||||
1. **Column Priority**: Always keep the most important 2-3 columns visible on mobile
|
||||
2. **Content Density**: Use the tighter spacing for data tables, looser spacing for content lists
|
||||
3. **Responsive Testing**: Test at 320px, 768px, and 1024px minimum
|
||||
4. **Accessibility**: Ensure interactive elements maintain proper touch targets despite tighter spacing
|
||||
246
docs/data-table-responsive-guide.md
Normal file
246
docs/data-table-responsive-guide.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Data Table Responsive Design Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The data table component has been updated to provide better responsive behavior, consistent padding, and proper scaling across different screen sizes.
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Consistent Padding
|
||||
- Uniform padding across all table elements
|
||||
- Responsive padding that scales with screen size
|
||||
- Cards now have consistent spacing (p-3 on mobile, p-4 on desktop)
|
||||
|
||||
### 2. Proper Responsive Column Hiding
|
||||
- Columns now properly hide both headers and cells on smaller screens
|
||||
- Uses `meta` properties for clean column visibility control
|
||||
- No more orphaned headers on mobile devices
|
||||
|
||||
### 3. Better Scaling
|
||||
- Font sizes adapt to screen size (text-xs on mobile, text-sm on desktop)
|
||||
- Button sizes and spacing adjust appropriately
|
||||
- Pagination controls are optimized for touch devices
|
||||
|
||||
## Using Responsive Columns
|
||||
|
||||
### Basic Column Definition
|
||||
|
||||
```tsx
|
||||
const columns: ColumnDef<YourDataType>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => row.original.name,
|
||||
// Always visible on all screen sizes
|
||||
},
|
||||
{
|
||||
accessorKey: "phone",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Phone" />
|
||||
),
|
||||
cell: ({ row }) => row.original.phone || "—",
|
||||
meta: {
|
||||
// Hidden on mobile, visible on md screens and up
|
||||
headerClassName: "hidden md:table-cell",
|
||||
cellClassName: "hidden md:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "address",
|
||||
header: "Address",
|
||||
cell: ({ row }) => formatAddress(row.original),
|
||||
meta: {
|
||||
// Hidden on mobile and tablet, visible on lg screens and up
|
||||
headerClassName: "hidden lg:table-cell",
|
||||
cellClassName: "hidden lg:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({ row }) => formatDate(row.getValue("createdAt")),
|
||||
meta: {
|
||||
// Only visible on xl screens and up
|
||||
headerClassName: "hidden xl:table-cell",
|
||||
cellClassName: "hidden xl:table-cell",
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Responsive Breakpoints
|
||||
|
||||
- **Always visible**: No meta properties needed
|
||||
- **md and up** (768px+): `hidden md:table-cell`
|
||||
- **lg and up** (1024px+): `hidden lg:table-cell`
|
||||
- **xl and up** (1280px+): `hidden xl:table-cell`
|
||||
|
||||
## Complex Cell Content
|
||||
|
||||
For cells with complex content that should partially hide on mobile:
|
||||
|
||||
```tsx
|
||||
{
|
||||
accessorKey: "client",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Client" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const client = row.original;
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Icon hidden on mobile, shown on sm screens */}
|
||||
<div className="bg-status-info-muted hidden rounded-lg p-2 sm:flex">
|
||||
<UserIcon className="text-status-info h-4 w-4" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-medium">{client.name}</p>
|
||||
{/* Secondary info can be hidden on very small screens if needed */}
|
||||
<p className="text-muted-foreground truncate text-sm">
|
||||
{client.email || "—"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Priority-Based Column Hiding
|
||||
- Always show the most important columns (e.g., name, status, primary action)
|
||||
- Hide supplementary information first (e.g., dates, secondary details)
|
||||
- Consider hiding decorative elements (icons) on mobile while keeping text
|
||||
|
||||
### 2. Mobile-First Design
|
||||
- Ensure at least 2-3 columns are visible on mobile
|
||||
- Test on actual devices, not just browser dev tools
|
||||
- Consider the minimum viable information for each row
|
||||
|
||||
### 3. Touch-Friendly Actions
|
||||
- Action buttons should be at least 44x44px on mobile
|
||||
- Use appropriate spacing between interactive elements
|
||||
- Consider grouping actions in a dropdown on mobile
|
||||
|
||||
### 4. Performance
|
||||
- The responsive system uses CSS classes, so there's no JavaScript overhead
|
||||
- Column visibility is handled by Tailwind's responsive utilities
|
||||
- No re-renders needed when resizing
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you have existing data tables, update them as follows:
|
||||
|
||||
### Before:
|
||||
```tsx
|
||||
{
|
||||
accessorKey: "phone",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Phone" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span className="hidden md:inline">{row.original.phone || "—"}</span>
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
### After:
|
||||
```tsx
|
||||
{
|
||||
accessorKey: "phone",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Phone" />
|
||||
),
|
||||
cell: ({ row }) => row.original.phone || "—",
|
||||
meta: {
|
||||
headerClassName: "hidden md:table-cell",
|
||||
cellClassName: "hidden md:table-cell",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Status Columns
|
||||
Always visible, use color and icons to convey information efficiently:
|
||||
|
||||
```tsx
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Status" />
|
||||
),
|
||||
cell: ({ row }) => <StatusBadge status={row.original.status} />,
|
||||
}
|
||||
```
|
||||
|
||||
### Date Columns
|
||||
Often hidden on mobile, show relative dates when space is limited:
|
||||
|
||||
```tsx
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title="Created" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("createdAt") as Date;
|
||||
return (
|
||||
<>
|
||||
{/* Full date on larger screens */}
|
||||
<span className="hidden sm:inline">{formatDate(date)}</span>
|
||||
{/* Relative date on mobile */}
|
||||
<span className="sm:hidden">{formatRelativeDate(date)}</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Action Columns
|
||||
Keep actions accessible but space-efficient:
|
||||
|
||||
```tsx
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
const item = row.original;
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
{/* Show individual buttons on larger screens */}
|
||||
<div className="hidden sm:flex sm:gap-1">
|
||||
<EditButton item={item} />
|
||||
<DeleteButton item={item} />
|
||||
</div>
|
||||
{/* Dropdown menu on mobile */}
|
||||
<div className="sm:hidden">
|
||||
<ActionsDropdown item={item} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Table is readable on 320px wide screens
|
||||
- [ ] Headers and cells align properly at all breakpoints
|
||||
- [ ] Touch targets are at least 44x44px on mobile
|
||||
- [ ] Horizontal scrolling works smoothly when needed
|
||||
- [ ] Critical information is always visible
|
||||
- [ ] Loading states work correctly
|
||||
- [ ] Empty states are responsive
|
||||
- [ ] Pagination controls are touch-friendly
|
||||
|
||||
## Accessibility Notes
|
||||
|
||||
- Hidden columns are properly hidden from screen readers
|
||||
- Table remains navigable with keyboard at all screen sizes
|
||||
- Sort controls are accessible on mobile
|
||||
- Focus indicators are visible on all interactive elements
|
||||
279
docs/forms-guide.md
Normal file
279
docs/forms-guide.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# Forms Improvement Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The business and client creation/editing forms have been significantly improved with better organization, shared components, enhanced validation, and improved user experience.
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 1. Shared Components & Utilities
|
||||
|
||||
#### Address Form Component (`src/components/ui/address-form.tsx`)
|
||||
A reusable address form component that handles:
|
||||
- Country-aware formatting (US ZIP codes, Canadian postal codes)
|
||||
- State dropdown for US addresses, text input for other countries
|
||||
- Popular countries listed first in country dropdown
|
||||
- Automatic field adjustments based on country selection
|
||||
|
||||
```tsx
|
||||
<AddressForm
|
||||
addressLine1={formData.addressLine1}
|
||||
addressLine2={formData.addressLine2}
|
||||
city={formData.city}
|
||||
state={formData.state}
|
||||
postalCode={formData.postalCode}
|
||||
country={formData.country}
|
||||
onChange={handleInputChange}
|
||||
errors={errors}
|
||||
required={false}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Form Constants & Utilities (`src/lib/form-constants.ts`)
|
||||
Centralized location for:
|
||||
- US states list with proper formatting
|
||||
- All countries with ISO codes
|
||||
- Popular countries for quick selection
|
||||
- Format functions for phone, postal codes, tax IDs, and URLs
|
||||
- Validation utilities and messages
|
||||
|
||||
### 2. Enhanced Form Validation
|
||||
|
||||
#### Real-time Validation
|
||||
- Errors clear as soon as user starts typing
|
||||
- Field-specific validation messages
|
||||
- Visual feedback with red borders on invalid fields
|
||||
|
||||
#### Smart Validation Rules
|
||||
- Email: Proper email format checking
|
||||
- Phone: US phone number format validation
|
||||
- Address: Required fields only if any address field is filled
|
||||
- URL: Automatic https:// prefix addition
|
||||
|
||||
```typescript
|
||||
// Example validation
|
||||
if (formData.email && !isValidEmail(formData.email)) {
|
||||
newErrors.email = VALIDATION_MESSAGES.email;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Better Form Organization
|
||||
|
||||
#### Card-based Sections
|
||||
Forms are now organized into logical sections using cards:
|
||||
- **Basic Information**: Core fields like name, tax ID
|
||||
- **Contact Information**: Email, phone, website
|
||||
- **Address**: Complete address form with smart country handling
|
||||
- **Settings**: Business-specific settings like default business flag
|
||||
|
||||
#### Consistent Layout
|
||||
- Maximum width container for better readability
|
||||
- Responsive grid layouts that stack on mobile
|
||||
- Proper spacing between sections
|
||||
- Clear visual hierarchy
|
||||
|
||||
### 4. Improved User Experience
|
||||
|
||||
#### Loading States
|
||||
- Skeleton loader while fetching data in edit mode
|
||||
- Disabled form fields during submission
|
||||
- Loading spinner in submit button
|
||||
|
||||
#### Unsaved Changes Warning
|
||||
```typescript
|
||||
const handleCancel = () => {
|
||||
if (isDirty) {
|
||||
const confirmed = window.confirm(
|
||||
"You have unsaved changes. Are you sure you want to leave?"
|
||||
);
|
||||
if (!confirmed) return;
|
||||
}
|
||||
router.push("/dashboard/businesses");
|
||||
};
|
||||
```
|
||||
|
||||
#### Smart Field Formatting
|
||||
- Phone numbers: Auto-format as (555) 123-4567
|
||||
- Tax ID: Auto-format as 12-3456789
|
||||
- Postal codes: Format based on country (US vs Canadian)
|
||||
- Website URLs: Auto-add https:// if missing
|
||||
|
||||
### 5. Responsive Design
|
||||
|
||||
#### Mobile Optimizations
|
||||
- Form sections stack vertically on small screens
|
||||
- Touch-friendly input sizes
|
||||
- Proper button positioning
|
||||
- Readable font sizes
|
||||
|
||||
#### Desktop Enhancements
|
||||
- Two-column layouts for related fields
|
||||
- Optimal reading width
|
||||
- Side-by-side form actions
|
||||
|
||||
### 6. Code Reusability
|
||||
|
||||
#### Shared Between Business & Client Forms
|
||||
- Address form component
|
||||
- Validation logic
|
||||
- Format functions
|
||||
- Constants (states, countries)
|
||||
- Error handling patterns
|
||||
|
||||
#### TypeScript Interfaces
|
||||
```typescript
|
||||
interface FormData {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
// ... other fields
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
name?: string;
|
||||
email?: string;
|
||||
// ... validation errors
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Form Implementation
|
||||
```tsx
|
||||
export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
||||
const [formData, setFormData] = useState<FormData>(initialFormData);
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
|
||||
// Handle input changes
|
||||
const handleInputChange = (field: string, value: string | boolean) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
setIsDirty(true);
|
||||
|
||||
// Clear error when user types
|
||||
if (errors[field as keyof FormErrors]) {
|
||||
setErrors((prev) => ({ ...prev, [field]: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
// Validate and submit
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
toast.error("Please correct the errors in the form");
|
||||
return;
|
||||
}
|
||||
|
||||
// Submit logic...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Field with Icon and Validation
|
||||
```tsx
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">
|
||||
Email
|
||||
<span className="text-muted-foreground ml-1 text-xs">(Optional)</span>
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange("email", e.target.value)}
|
||||
placeholder={PLACEHOLDERS.email}
|
||||
className={`pl-10 ${errors.email ? "border-destructive" : ""}`}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
{errors.email && (
|
||||
<p className="text-sm text-destructive">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Form State Management
|
||||
- Use controlled components for all inputs
|
||||
- Track dirty state for unsaved changes warnings
|
||||
- Clear errors when user corrects them
|
||||
- Disable form during submission
|
||||
|
||||
### 2. Validation Strategy
|
||||
- Validate on submit, not on blur (less annoying)
|
||||
- Clear errors immediately when user starts fixing them
|
||||
- Show field-level errors below each input
|
||||
- Use consistent error message format
|
||||
|
||||
### 3. Accessibility
|
||||
- Proper label associations with htmlFor
|
||||
- Required field indicators
|
||||
- Error messages linked to fields
|
||||
- Keyboard navigation support
|
||||
- Focus management
|
||||
|
||||
### 4. Performance
|
||||
- Memoize expensive computations
|
||||
- Use debouncing for format functions if needed
|
||||
- Lazy load country lists
|
||||
- Optimize re-renders with proper state management
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Old Forms
|
||||
1. Replace inline state/country arrays with imported constants
|
||||
2. Use `AddressForm` component instead of individual address fields
|
||||
3. Apply format functions from `form-constants.ts`
|
||||
4. Update validation to use shared utilities
|
||||
5. Wrap sections in Card components
|
||||
6. Add loading and dirty state tracking
|
||||
|
||||
### Example Migration
|
||||
```tsx
|
||||
// Before
|
||||
const US_STATES = [
|
||||
{ value: "AL", label: "Alabama" },
|
||||
// ... duplicated in each form
|
||||
];
|
||||
|
||||
// After
|
||||
import { US_STATES, formatPhoneNumber } from "~/lib/form-constants";
|
||||
import { AddressForm } from "~/components/ui/address-form";
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Improvements
|
||||
1. **Field-level permissions**: Disable fields based on user role
|
||||
2. **Auto-save**: Save draft as user types
|
||||
3. **Multi-step forms**: Break long forms into steps
|
||||
4. **Conditional fields**: Show/hide fields based on other values
|
||||
5. **Bulk operations**: Create multiple records at once
|
||||
6. **Import from templates**: Pre-fill common business types
|
||||
|
||||
### Extensibility
|
||||
The form system is designed to be easily extended:
|
||||
- Add new format functions to `form-constants.ts`
|
||||
- Create additional shared form components
|
||||
- Extend validation rules as needed
|
||||
- Add new field types with consistent patterns
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Validation not working**: Ensure field names match FormErrors interface
|
||||
2. **Format function not applying**: Check that onChange uses the format function
|
||||
3. **Country dropdown not searching**: Verify SearchableSelect has search enabled
|
||||
4. **Address validation failing**: Check if country field affects validation rules
|
||||
|
||||
### Debug Tips
|
||||
- Use React DevTools to inspect form state
|
||||
- Check console for validation errors
|
||||
- Verify API responses match expected format
|
||||
- Test with different country selections
|
||||
Reference in New Issue
Block a user