Files
Primora/apps/frontend/MIGRATION_GUIDE.md
2026-04-10 12:03:31 +02:00

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! 🚀