12 KiB
Primora Frontend Migration Guide
Overview
This guide helps you migrate from the previous component patterns to the new enhanced component library.
No Breaking Changes! 🎉
Good news: All enhancements are backward compatible. Your existing code will continue to work without modifications. This guide shows you how to leverage the new features.
New Components Available
1. Replace Custom Modals with Modal Component
Before:
<Show when={showDialog()}>
<div class="fixed inset-0 bg-black/50 flex items-center justify-center">
<div class="bg-surface-1 rounded-lg p-6 max-w-md">
<h2>Confirm Action</h2>
<p>Are you sure?</p>
<div class="flex gap-3 mt-4">
<button onClick={() => setShowDialog(false)}>Cancel</button>
<button onClick={handleConfirm}>Confirm</button>
</div>
</div>
</div>
</Show>
After:
<Modal
open={showDialog()}
onClose={() => setShowDialog(false)}
title="Confirm Action"
description="Are you sure?"
size="md"
>
<ModalFooter align="right">
<Button variant="secondary" onClick={() => setShowDialog(false)}>
Cancel
</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</ModalFooter>
</Modal>
Benefits:
- Automatic backdrop blur
- ESC key support
- Click outside to close
- Proper z-index management
- Smooth animations
- Accessibility built-in
2. Add Tooltips to Icon Buttons
Before:
<Button variant="ghost" icon={<Icons.Trash />} />
After:
<Tooltip content="Delete this item" placement="top">
<Button variant="ghost" icon={<Icons.Trash />} />
</Tooltip>
Benefits:
- Better UX with contextual help
- Smart positioning
- Keyboard accessible
- Configurable delay
3. Replace Custom Dropdowns with Dropdown Component
Before:
<Show when={showMenu()}>
<div class="absolute bg-surface-1 rounded-lg shadow-lg">
<button onClick={handleEdit}>Edit</button>
<button onClick={handleDelete}>Delete</button>
</div>
</Show>
After:
<Dropdown
trigger={<Button variant="secondary">Actions</Button>}
items={[
{ id: "edit", label: "Edit", icon: <Icons.Edit />, onClick: handleEdit },
{ id: "divider", divider: true },
{ id: "delete", label: "Delete", icon: <Icons.Trash />, danger: true, onClick: handleDelete },
]}
/>
Benefits:
- Smart positioning
- Click outside to close
- Keyboard navigation
- Icon support
- Danger states
- Dividers
4. Use Toast Instead of Custom Alerts
Before:
<Show when={message()}>
<div class="fixed bottom-4 right-4 bg-success-muted p-4 rounded-lg">
{message()}
</div>
</Show>
After:
// Add once to app root
<ToastContainer />
// Use anywhere
toast.success("Operation successful!");
toast.error("Something went wrong");
toast.info("New update available");
Benefits:
- Global API
- Auto-dismiss
- Stacked notifications
- Multiple variants
- Smooth animations
5. Add Loading States with Progress
Before:
<Show when={loading()}>
<div class="spinner" />
</Show>
After:
// Spinner
<Spinner size="lg" variant="primary" />
// Progress bar
<Progress value={uploadProgress()} showLabel label="Uploading..." />
// Circular progress
<CircularProgress value={60} showLabel />
Benefits:
- Multiple variants
- Size options
- Label support
- Smooth animations
6. Use Tabs for Multi-Section Views
Before:
<div class="flex gap-2 border-b">
<button
class={activeTab() === "overview" ? "border-b-2 border-accent" : ""}
onClick={() => setActiveTab("overview")}
>
Overview
</button>
<button
class={activeTab() === "settings" ? "border-b-2 border-accent" : ""}
onClick={() => setActiveTab("settings")}
>
Settings
</button>
</div>
<Show when={activeTab() === "overview"}>
<OverviewPanel />
</Show>
<Show when={activeTab() === "settings"}>
<SettingsPanel />
</Show>
After:
<Tabs
variant="underline"
defaultTab="overview"
tabs={[
{ id: "overview", label: "Overview", icon: <Icons.Dashboard />, content: <OverviewPanel /> },
{ id: "settings", label: "Settings", badge: "3", content: <SettingsPanel /> },
]}
onChange={(tabId) => console.log(tabId)}
/>
Benefits:
- Multiple variants (default, pills, underline)
- Icon and badge support
- Disabled states
- Smooth transitions
- Keyboard navigation
Enhanced Components
Button Enhancements
New Features:
// Loading state
<Button variant="primary" loading={submitting()}>
Saving...
</Button>
// Icon positioning
<Button icon={<Icons.Plus />} iconPosition="left">
Create
</Button>
// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
Card Enhancements
New Features:
// Elevated variant
<Card variant="elevated">
<CardHeader eyebrow="Overview" title="Dashboard" description="Monitor metrics" />
<CardContent>...</CardContent>
<CardFooter align="right">
<Button>Action</Button>
</CardFooter>
</Card>
// Interactive card
<Card variant="interactive" onClick={handleClick}>
Clickable card
</Card>
// Stat card
<StatCard
label="Total Users"
value={1234}
icon={<Icons.Users />}
trend="up"
trendValue="+12%"
/>
// Hover effects
<Card class="card-hover-lift spotlight">
Interactive card with effects
</Card>
Input Enhancements
New Features:
// Error states
<Input
label="Email"
value={email()}
onInput={(e) => setEmail(e.currentTarget.value)}
error={errors().email}
/>
// Sizes
<Input size="sm" placeholder="Small input" />
<Input size="md" placeholder="Medium input" />
<Input size="lg" placeholder="Large input" />
New CSS Utilities
Animations
// Fade in
<div class="animate-fade-in">Content</div>
// Slide animations
<div class="animate-slide-up">Slide from bottom</div>
<div class="animate-slide-down">Slide from top</div>
<div class="animate-slide-left">Slide from right</div>
<div class="animate-slide-right">Slide from left</div>
// Scale and bounce
<div class="animate-scale-in">Scale in</div>
<div class="animate-bounce-in">Bounce in</div>
// Stagger children
<div class="stagger-fade-in">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
Visual Effects
// Card effects
<Card class="card-hover-lift">Lifts on hover</Card>
<Card class="spotlight">Shine effect</Card>
// Glass effect
<div class="glass">Frosted glass background</div>
// Text effects
<span class="text-shimmer">Shimmer text</span>
<span class="neon-glow">Neon glow</span>
<span class="gradient-text">Gradient text</span>
// Loading states
<div class="skeleton h-4 w-32" />
<div class="skeleton-wave h-20 w-full" />
<SkeletonCard lines={3} />
Common Migration Patterns
1. Confirmation Dialogs
Before:
const [showConfirm, setShowConfirm] = createSignal(false);
<Show when={showConfirm()}>
<div class="fixed inset-0 bg-black/50 flex items-center justify-center">
<div class="bg-surface-1 rounded-lg p-6">
<h3>Confirm Deletion</h3>
<p>This action cannot be undone.</p>
<div class="flex gap-3 mt-4">
<button onClick={() => setShowConfirm(false)}>Cancel</button>
<button onClick={handleDelete}>Delete</button>
</div>
</div>
</div>
</Show>
After:
const [showConfirm, setShowConfirm] = createSignal(false);
<Modal
open={showConfirm()}
onClose={() => setShowConfirm(false)}
title="Confirm Deletion"
description="This action cannot be undone."
size="sm"
>
<ModalFooter align="right">
<Button variant="secondary" onClick={() => setShowConfirm(false)}>
Cancel
</Button>
<Button variant="danger" onClick={handleDelete}>
Delete
</Button>
</ModalFooter>
</Modal>
2. Action Menus
Before:
<button onClick={() => setShowMenu(!showMenu())}>•••</button>
<Show when={showMenu()}>
<div class="absolute bg-surface-1 rounded-lg shadow-lg">
<button onClick={handleEdit}>Edit</button>
<button onClick={handleDelete}>Delete</button>
</div>
</Show>
After:
<Dropdown
trigger={<Button variant="ghost" size="sm">•••</Button>}
items={[
{ id: "view", label: "View Details", icon: <Icons.Eye /> },
{ id: "edit", label: "Edit", icon: <Icons.Edit /> },
{ id: "divider", divider: true },
{ id: "delete", label: "Delete", icon: <Icons.Trash />, danger: true },
]}
/>
3. Loading States
Before:
<Show when={!loading()} fallback={<div class="spinner" />}>
{/* Content */}
</Show>
After:
<Show when={!loading()} fallback={<Spinner size="lg" />}>
{/* Content */}
</Show>
// Or with progress
<Show when={!loading()} fallback={
<div class="flex flex-col items-center gap-4">
<Spinner size="lg" />
<Progress value={progress()} showLabel />
</div>
}>
{/* Content */}
</Show>
4. Form Submissions
Before:
<form onSubmit={handleSubmit}>
<input
type="text"
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
/>
<button type="submit" disabled={submitting()}>
{submitting() ? "Saving..." : "Save"}
</button>
</form>
After:
<form onSubmit={handleSubmit} class="space-y-4">
<Input
label="Name"
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
error={errors().name}
/>
<Button type="submit" variant="primary" loading={submitting()}>
Save
</Button>
</form>
5. Success/Error Messages
Before:
<Show when={successMessage()}>
<div class="bg-success-muted text-success p-4 rounded-lg">
{successMessage()}
</div>
</Show>
After:
// Option 1: Message component
<Show when={successMessage()}>
<Message variant="success" dismissible onDismiss={() => setSuccessMessage("")}>
{successMessage()}
</Message>
</Show>
// Option 2: Toast (recommended)
toast.success(successMessage());
Step-by-Step Migration
Phase 1: Add ToastContainer
// In your App.tsx or main component
import { ToastContainer } from "./components";
export default function App() {
return (
<>
<ToastContainer />
{/* Rest of your app */}
</>
);
}
Phase 2: Replace Alerts with Toasts
Replace all custom alert/message displays with toast notifications.
Phase 3: Add Tooltips
Add tooltips to icon-only buttons for better UX.
Phase 4: Replace Custom Modals
Migrate custom modal implementations to the Modal component.
Phase 5: Add Dropdowns
Replace custom dropdown menus with the Dropdown component.
Phase 6: Enhance Forms
Add loading states, error handling, and progress indicators to forms.
Phase 7: Add Tabs
Replace custom tab implementations with the Tabs component.
Best Practices
1. Use Semantic Components
// Good
<Button variant="primary" onClick={handleSubmit}>Submit</Button>
// Avoid
<button class="btn btn-primary" onClick={handleSubmit}>Submit</button>
2. Leverage Loading States
// Good
<Button loading={submitting()}>Save</Button>
// Avoid
<Button disabled={submitting()}>
{submitting() ? "Saving..." : "Save"}
</Button>
3. Use Toast for Notifications
// Good
toast.success("Project created!");
// Avoid
setMessage("Project created!");
setTimeout(() => setMessage(""), 3000);
4. Add Tooltips to Icons
// Good
<Tooltip content="Delete">
<Button icon={<Icons.Trash />} />
</Tooltip>
// Avoid
<Button icon={<Icons.Trash />} />
5. Use Proper Variants
// Good
<Button variant="danger" onClick={handleDelete}>Delete</Button>
// Avoid
<Button class="bg-error" onClick={handleDelete}>Delete</Button>
Troubleshooting
Modal Not Showing
Make sure the Modal is rendered and open prop is true:
<Modal open={show()} onClose={() => setShow(false)}>
Tooltip Not Appearing
Check that the tooltip has a trigger element:
<Tooltip content="Help text">
<button>Hover me</button>
</Tooltip>
Toast Not Working
Ensure ToastContainer is added to your app root:
<ToastContainer />
Dropdown Not Positioning Correctly
The dropdown uses Portal rendering. Make sure your app has proper z-index management.
Need Help?
- Quick Reference: See
COMPONENT_GUIDE.md - Detailed Docs: See
FRONTEND_ENHANCEMENTS.md - Design System: See
project_frontend.md
Happy migrating! 🚀