Cursor AI Integration

Enable AI assistants to use PrettyUI effectively with our comprehensive Cursor rule. This guide covers the complete component library, theming system, and best practices.


Setup

Add the PrettyUI rule to your Cursor IDE for AI-assisted development:

1. Create the Rules Directory

mkdir -p .cursor/rules

2. Add the PrettyUI Rule

Create .cursor/rules/PrettyUI.mdc in your project with the content below, or copy it from the PrettyUI repository.


Complete Cursor Rule

.cursor/rules/PrettyUI.mdc

Copy the complete rule below into your project to enable AI-assisted PrettyUI development.

---
description: Comprehensive guide for using the PrettyUI SwiftUI component library effectively
globs: "**/*.swift"
alwaysApply: false
---

# PrettyUI - Complete Component & Theming Guide

PrettyUI is a comprehensive SwiftUI component library inspired by Family.co's design language. It provides 20+ customizable components with a powerful theming system supporting design tokens for colors, spacing, radius, shadows, and typography.

---

## Quick Start

### Installation

Add to `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/mecurylabs/PrettyUI.git", from: "1.0.0")
]
```

### Basic Usage

```swift
import PrettyUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 16) {
            PButton("Get Started", variant: .primary) {
                print("Tapped!")
            }
            
            PCard {
                PText("Welcome to PrettyUI", style: .headline)
            }
        }
        .prettyTheme(.sky) // Apply theme at root
    }
}
```

---

## Theme System

### Applying Themes

Apply themes at your app's root or any view hierarchy:

```swift
// Use built-in presets
.prettyTheme(.sky)      // Cyan-blue theme
.prettyTheme(.indigo)   // Deep purple theme  
.prettyTheme(.emerald)  // Green theme
.prettyTheme(.amber)    // Orange-yellow theme
.prettyTheme(.default)  // Neutral gray theme

// Or create custom themes
.prettyTheme(PrettyTheme(
    lightColors: ColorTokens(
        primary: Color(hex: "#FF6B6B"),
        primaryForeground: .white,
        // ... other colors
    ),
    darkColors: ColorTokens(/* dark mode colors */),
    spacing: SpacingTokens.default,
    radius: RadiusTokens.default,
    typography: TypographyTokens.default,
    shadows: ShadowTokens.default
))
```

### Accessing Theme Values

```swift
struct MyView: View {
    @Environment(\.prettyTheme) var theme
    @Environment(\.colorScheme) var colorScheme
    
    var body: some View {
        let colors = theme.colors(for: colorScheme)
        
        VStack(spacing: theme.spacing.md) {
            Text("Hello")
                .foregroundColor(colors.primary)
                .padding(theme.spacing.lg)
        }
        .background(colors.background)
        .cornerRadius(theme.radius.xl)
    }
}

// Using ThemeReader for cleaner access
ThemeReader { theme, colors in
    Text("Primary color")
        .foregroundColor(colors.primary)
        .padding(theme.spacing.md)
}
```

---

## Design Tokens

### Color Tokens

```swift
// Access colors via theme.colors(for: colorScheme)
colors.primary           // Main brand color (#1DA1F2 in Sky)
colors.primaryForeground // Text on primary color
colors.secondary         // Secondary actions
colors.background        // Page background
colors.foreground        // Primary text color
colors.card              // Card surfaces
colors.cardForeground    // Text on cards
colors.muted             // Muted backgrounds
colors.mutedForeground   // Secondary text
colors.border            // Border color
colors.input             // Input border
colors.ring              // Focus ring
colors.destructive       // Error/danger color
colors.success           // Success color
colors.warning           // Warning color
```

### Spacing Tokens (8pt Grid)

```swift
theme.spacing.xxs   // 2pt
theme.spacing.xs    // 4pt
theme.spacing.sm    // 8pt
theme.spacing.md    // 16pt (standard padding)
theme.spacing.lg    // 24pt
theme.spacing.xl    // 32pt
theme.spacing.xxl   // 48pt
theme.spacing.xxxl  // 64pt

// Usage
.padding(theme.spacing.md)
VStack(spacing: theme.spacing.sm) { ... }
```

### Radius Tokens

```swift
theme.radius.none   // 0pt
theme.radius.sm     // 6pt - chips, tags
theme.radius.md     // 10pt - inputs
theme.radius.lg     // 14pt - standard cards
theme.radius.xl     // 20pt - large cards
theme.radius.xxl    // 28pt - modals, sheets
theme.radius.full   // 9999pt - pills, avatars

// Or use enum
theme.radius[.sm]
theme.radius[.lg]
```

### Shadow Tokens

```swift
theme.shadows.none
theme.shadows.sm    // Subtle: 4px blur, 2px y-offset
theme.shadows.md    // Medium: 12px blur, 4px y-offset
theme.shadows.lg    // Large: 20px blur, 8px y-offset
theme.shadows.xl    // Extra large: 32px blur
theme.shadows.xxl   // Maximum: 48px blur

// Apply shadow
.prettyShadow(theme.shadows.md)

// Or directly
.prettyShadow(theme.shadows[.lg])
```

### Typography Tokens

```swift
theme.typography.fontFamily      // "SF Pro Display"
theme.typography.monoFontFamily  // "SF Mono"

theme.typography.sizes.xxs  // 10pt
theme.typography.sizes.xs   // 12pt
theme.typography.sizes.sm   // 14pt
theme.typography.sizes.base // 16pt
theme.typography.sizes.lg   // 18pt
theme.typography.sizes.xl   // 20pt
theme.typography.sizes.xxl  // 24pt
theme.typography.sizes.xxxl // 30pt
theme.typography.sizes.display // 36pt
```

---

## Components Reference

### PButton

Customizable button with variants, sizes, loading states, and icons.

```swift
// Basic usage
PButton("Get Started") { action() }

// Variants
PButton("Primary", variant: .primary) { }
PButton("Secondary", variant: .secondary) { }
PButton("Outline", variant: .outline) { }
PButton("Ghost", variant: .ghost) { }
PButton("Destructive", variant: .destructive) { }
PButton("Link", variant: .link) { }

// Sizes
PButton("Small", size: .sm) { }
PButton("Medium", size: .md) { }  // default
PButton("Large", size: .lg) { }

// Full width
PButton("Full Width", fullWidth: true) { }

// Custom radius
PButton("Pill Button", radius: .full) { }
PButton("Rounded", radius: .lg) { }

// With icons
PButton("Continue", icon: "arrow.right", iconPosition: .trailing) { }
PButton("Add Item", icon: "plus", iconPosition: .leading) { }

// Loading state
@State var isLoading = false
PButton("Submit", isLoading: isLoading, loadingPosition: .replace) { }

// Disabled
PButton("Disabled", isDisabled: true) { }

// Haptic feedback
PButton("Tap Me")
    .hapticFeedback(true)
    .hapticStyle(.medium)
    .action { }

// Fluent API
PButton("Custom")
    .variant(.primary)
    .size(.lg)
    .radius(.full)
    .fullWidth(true)
    .icon("star.fill", position: .leading)
    .loading(isLoading, position: .trailing)
    .disabled(false)
    .haptics(enabled: true, style: .light)
    .action { print("Tapped") }
```

**Variants:** `.primary`, `.secondary`, `.outline`, `.ghost`, `.destructive`, `.link`
**Sizes:** `.sm` (36pt), `.md` (44pt), `.lg` (52pt)
**Icon Positions:** `.leading`, `.trailing`
**Loading Positions:** `.leading`, `.trailing`, `.replace`

---

### PCard

Content container with variants and interactive states.

```swift
// Basic card
PCard {
    Text("Card content")
}

// Variants
PCard(variant: .elevated) { content }  // With shadow
PCard(variant: .outlined) { content }  // With border
PCard(variant: .filled) { content }    // Muted background
PCard(variant: .ghost) { content }     // Transparent

// Custom styling
PCard(radius: .xl, shadow: .md, padding: .lg) {
    VStack { ... }
}

// Interactive (pressable)
PCard(pressable: true) {
    Text("Tap me")
}
.onTap { print("Card tapped") }

// Selectable
@State var isSelected = false
PCard(selected: isSelected) {
    Text("Selectable card")
}
.onTap { isSelected.toggle() }

// With header and footer
VStack(spacing: 0) {
    PCardHeader {
        PCardTitle("Settings", subtitle: "Manage your preferences")
    }
    
    // Card content
    VStack { ... }
    
    PCardFooter {
        PButton("Save") { }
    }
}
```

**Variants:** `.elevated`, `.outlined`, `.filled`, `.ghost`

---

### PTextField

Text input with floating labels, validation, and icons.

```swift
// Basic usage
@State var text = ""
PTextField("Email", text: $text)

// With placeholder
PTextField("Username", text: $text, placeholder: "Enter username")

// Variants
PTextField("Default", text: $text, variant: .default)
PTextField("Filled", text: $text, variant: .filled)
PTextField("Outlined", text: $text, variant: .outlined)
PTextField("Underlined", text: $text, variant: .underlined)

// Secure input (password)
PTextField("Password", text: $password, isSecure: true)

// With icons
PTextField("Search", text: $search, leadingIcon: "magnifyingglass")
PTextField("Email", text: $email, trailingIcon: "envelope")

// Validation states
PTextField("Email", text: $email)
    .error("Invalid email address")

PTextField("Username", text: $username)
    .success("Username available!")

PTextField("Bio", text: $bio)
    .helper("Max 150 characters")

// Character limit (display counter only)
PTextField("Tweet", text: $tweet)
    .maxCharacters(280)

// Enforce max length (restricts input)
PTextField("PIN", text: $pin)
    .maxLength(4)                    // User cannot type more than 4 characters

// Character limit with enforcement
PTextField("Username", text: $username)
    .maxCharacters(20, enforced: true)  // Shows counter AND restricts input

// Disabled & required
PTextField("Locked", text: $locked)
    .disabled(true)
    .required(true)

// Keyboard types
PTextField("Phone", text: $phone)
    .keyboardType(.phonePad)

// Text alignment
PTextField("Amount", text: $amount)
    .textAlignment(.center)     // Center aligned text

PTextField("Price", text: $price)
    .textAlignment(.trailing)   // Right aligned text

// Convenience components
PLabeledTextField(label: "Full Name", text: $name)
PSearchField(text: $searchQuery, placeholder: "Search...")
```

**Variants:** `.default`, `.filled`, `.outlined`, `.underlined`
**Sizes:** `.sm`, `.md`, `.lg`
**Text Alignment:** `.leading` (default), `.center`, `.trailing`

---

### PText

Typography component with preset styles.

```swift
// Preset styles
PText("Display", style: .display)
PText("Headline", style: .headline)
PText("Title", style: .title)
PText("Body Large", style: .bodyLarge)
PText("Body", style: .body)
PText("Caption", style: .caption)
PText("Small", style: .small)

// Colors
PText("Primary", color: .primary)
PText("Muted", color: .muted)
PText("Success", color: .success)
PText("Warning", color: .warning)
PText("Destructive", color: .destructive)
PText("Custom", color: .custom(Color.purple))

// Weight & design
PText("Bold Text")
    .weight(.bold)
    .design(.rounded)

// Decorations
PText("Underlined")
    .underline(style: .single, color: .primary)

PText("Strikethrough")
    .strikethrough(style: .single, color: .destructive)

// Custom font
PText("Custom")
    .customFont("Helvetica Neue", size: 18)
```

**Styles:** `.display`, `.headline`, `.title`, `.bodyLarge`, `.body`, `.caption`, `.small`
**Colors:** `.primary`, `.foreground`, `.muted`, `.success`, `.warning`, `.destructive`, `.custom(Color)`

---

### PAvatar

User avatar with images, initials, and status indicators.

```swift
// From URL
PAvatar(url: URL(string: "https://example.com/avatar.jpg"))

// From local image
PAvatar(image: Image("profile"))

// From name (generates initials)
PAvatar(name: "John Doe")  // Shows "JD"

// Sizes
PAvatar(name: "Jane").size(.xs)  // 24pt
PAvatar(name: "Jane").size(.sm)  // 32pt
PAvatar(name: "Jane").size(.md)  // 40pt (default)
PAvatar(name: "Jane").size(.lg)  // 56pt
PAvatar(name: "Jane").size(.xl)  // 80pt
PAvatar(name: "Jane").size(.xxl) // 120pt

// Shapes
PAvatar(name: "User").shape(.circle)  // default
PAvatar(name: "User").shape(.rounded)
PAvatar(name: "User").shape(.square)

// Status indicator
PAvatar(name: "Online User")
    .status(.online)    // Green dot
    .status(.offline)   // Gray dot
    .status(.busy)      // Red dot
    .status(.away)      // Yellow dot

// Badge
PAvatar(name: "User")
    .badge(5)                           // Shows "5"
    .badge(100, maxCount: 99)           // Shows "99+"
    .badgePosition(.topTrailing)        // Position

// Icon overlay
PAvatar(name: "Admin")
    .iconBadge(.checkmark)
    .iconBadge(.plus)
    .iconBadge(.edit)
    .iconBadge(.custom("star.fill", Color.yellow))

// Avatar group
PAvatarGroup(overlap: 12) {
    PAvatar(name: "Alice")
    PAvatar(name: "Bob")
    PAvatar(name: "Charlie")
}
.moreIndicator("+3")
```

**Sizes:** `.xs`, `.sm`, `.md`, `.lg`, `.xl`, `.xxl`
**Shapes:** `.circle`, `.rounded`, `.square`
**Status:** `.online`, `.offline`, `.busy`, `.away`

---

### PAlert

Alert/notification banners with variants and actions.

```swift
// Basic usage
PAlert(variant: .info, title: "Information")

// Variants
PAlert(variant: .info, title: "Info message")
PAlert(variant: .success, title: "Success!", description: "Your changes were saved.")
PAlert(variant: .warning, title: "Warning", description: "Proceed with caution.")
PAlert(variant: .destructive, title: "Error", description: "Something went wrong.")

// Styles
PAlert(variant: .info, style: .filled, title: "Filled style")
PAlert(variant: .success, style: .outlined, title: "Outlined style")
PAlert(variant: .warning, style: .soft, title: "Soft style")

// Dismissible
@State var showAlert = true
PAlert(variant: .info, title: "Dismissible")
    .dismissible(true, action: { showAlert = false })

// With action button
PAlert(variant: .warning, title: "Session expiring")
    .action("Extend", action: { extendSession() })

// Banner presentation
.alertBanner(
    isPresented: $showAlert,
    variant: .success,
    title: "Saved!",
    position: .top
)
```

**Variants:** `.info`, `.success`, `.warning`, `.destructive`
**Styles:** `.filled`, `.outlined`, `.soft`

---

### PModal

Modal dialog with fluid animations.

```swift
// Basic modal
@State var showModal = false

Button("Open Modal") { showModal = true }
    .pModal(isPresented: $showModal) {
        PModalContent {
            Text("Modal content here")
        }
    }

// With configuration
.pModal(
    isPresented: $showModal,
    position: .center,           // .center, .bottom, .top
    overlayStyle: .dimmed,       // .dimmed, .blur, .none
    dismissOnBackgroundTap: true,
    enableSwipeToDismiss: true
) {
    VStack(spacing: 16) {
        Image(systemName: "checkmark.circle.fill")
            .font(.system(size: 48))
            .foregroundColor(.green)
        
        Text("Success!")
            .font(.title2.bold())
        
        Text("Your action was completed.")
        
        PButton("Continue", fullWidth: true) {
            showModal = false
        }
    }
    .padding(24)
}

// Alert-style modal
.pModalAlert(
    isPresented: $showDelete,
    icon: "trash.fill",
    iconColor: .red,
    title: "Delete Item?",
    message: "This action cannot be undone.",
    primaryButton: PModalButton(title: "Delete", role: .destructive) {
        deleteItem()
    },
    secondaryButton: PModalButton(title: "Cancel", role: .cancel) {
        showDelete = false
    }
)
```

**Positions:** `.center`, `.bottom`, `.top`
**Overlay Styles:** `.dimmed`, `.blur`, `.none`

---

### PToast

Ephemeral notifications with queue management.

```swift
// Setup container at root
YourView()
    .pToastContainer()

// Using ToastManager (recommended for multiple toasts)
@StateObject var toastManager = PToastManager()

Button("Show Toast") {
    toastManager.show(
        title: "Success!",
        description: "Your file was uploaded.",
        variant: .success,
        duration: 3.0
    )
}
.environmentObject(toastManager)

// Simple binding-based toast
@State var showToast = false

Button("Show") { showToast = true }
    .pToast(
        isPresented: $showToast,
        title: "Saved",
        variant: .success,
        position: .top
    )

// Toast variants
toastManager.show(title: "Info", variant: .info)
toastManager.show(title: "Success", variant: .success)
toastManager.show(title: "Warning", variant: .warning)
toastManager.show(title: "Error", variant: .error)

// With action
toastManager.show(
    title: "Deleted",
    description: "Item removed",
    variant: .info,
    action: PToastItem.ToastAction(title: "Undo") {
        undoDelete()
    }
)

// Positions
toastManager.show(title: "Top", position: .top)
toastManager.show(title: "Bottom", position: .bottom)
```

**Variants:** `.info`, `.success`, `.warning`, `.error`
**Positions:** `.top`, `.bottom`

---

### PSpinner

Loading spinner with multiple styles.

```swift
// Basic spinner
PSpinner()

// Sizes
PSpinner().size(.sm)   // 16pt
PSpinner().size(.md)   // 24pt (default)
PSpinner().size(.lg)   // 32pt
PSpinner().size(.xl)   // 48pt

// Styles
PSpinner().style(.circular)  // Default rotating arc
PSpinner().style(.dots)      // Animated dots
PSpinner().style(.track)     // Circular with track
PSpinner().style(.minimal)   // Simple thin spinner
PSpinner().style(.orbit)     // Orbiting dots

// Custom color
PSpinner().color(.purple)

// With label
PSpinner()
    .label("Loading...")
    .labelPlacement(.bottom)  // .bottom, .trailing

// Overlay on view
ContentView()
    .spinnerOverlay(isLoading: isLoading, style: .circular, label: "Please wait...")
```

**Sizes:** `.sm`, `.md`, `.lg`, `.xl`
**Styles:** `.circular`, `.dots`, `.track`, `.minimal`, `.orbit`

---

### PTooltip

Contextual information popups with arrows.

```swift
// Basic tooltip
Text("Hover me")
    .pTooltip(isPresented: $showTooltip, text: "This is a tooltip")

// On hover/long-press (automatic)
Button("Help") { }
    .pTooltipOnHover(text: "Click for more information")

// Positions
.pTooltip(isPresented: $show, text: "Top", position: .top)
.pTooltip(isPresented: $show, text: "Bottom", position: .bottom)
.pTooltip(isPresented: $show, text: "Leading", position: .leading)
.pTooltip(isPresented: $show, text: "Trailing", position: .trailing)

// Styles
.pTooltip(isPresented: $show, text: "Dark", style: .dark)
.pTooltip(isPresented: $show, text: "Light", style: .light)
.pTooltip(isPresented: $show, text: "Custom", style: .custom(TooltipColor(
    background: .purple,
    foreground: .white
)))

// Auto-dismiss
.pTooltip(
    isPresented: $show,
    text: "Auto-dismiss",
    autoDismiss: true,
    displayDuration: 2.0
)

// Rich content
.pTooltip(isPresented: $show, position: .bottom) {
    PTooltipRichContent(
        title: "Pro Tip",
        description: "You can customize this tooltip!",
        icon: "lightbulb.fill"
    )
}

// Suggestions list
.pTooltip(isPresented: $show) {
    PTooltipSuggestions(
        title: "Quick Actions",
        items: ["Copy", "Paste", "Delete"]
    ) { item in
        performAction(item)
    }
}
```

**Positions:** `.top`, `.bottom`, `.leading`, `.trailing`
**Styles:** `.dark`, `.light`, `.custom(TooltipColor)`

---

### PAccordion

Expandable content sections with animations.

```swift
// Basic accordion
PAccordion {
    PAccordionItem(title: "Section 1") {
        Text("Content for section 1")
    }
    PAccordionItem(title: "Section 2") {
        Text("Content for section 2")
    }
}

// Variants
PAccordion {
    // items...
}
.variant(.standard)   // No container styling
.variant(.bordered)   // With border
.variant(.separated)  // Cards for each item

// Expansion modes
.expansionMode(.single)    // Only one open at a time
.expansionMode(.multiple)  // Multiple can be open

// Item customization
PAccordionItem(
    title: "FAQ Item",
    subtitle: "Click to expand",
    icon: "questionmark.circle"
) {
    Text("Answer goes here")
}
.initiallyExpanded(true)
.showDivider(false)

// Standalone accordion item
@State var isExpanded = false

PStandaloneAccordionItem(
    title: "Manual Control",
    isExpanded: $isExpanded
) {
    Text("Controlled externally")
}
```

**Variants:** `.standard`, `.bordered`, `.separated`
**Expansion Modes:** `.single`, `.multiple`

---

### PPopover

Interactive content anchored to triggers.

```swift
// Setup root (required)
YourApp()
    .pPopoverRoot()

// Basic popover
@State var showPopover = false

Button("Show Menu") { showPopover = true }
    .pPopover(isPresented: $showPopover) { dismiss in
        VStack {
            Button("Option 1") { dismiss() }
            Button("Option 2") { dismiss() }
        }
        .padding()
    }

// Positions
.pPopover(isPresented: $show, position: .bottom) { ... }
.pPopover(isPresented: $show, position: .top) { ... }
.pPopover(isPresented: $show, position: .leading) { ... }
.pPopover(isPresented: $show, position: .trailing) { ... }

// Styles
.pPopover(isPresented: $show, style: .light) { ... }
.pPopover(isPresented: $show, style: .dark) { ... }

// Menu helper
Button("Actions") { showMenu = true }
    .pPopover(isPresented: $showMenu, position: .bottom) { dismiss in
        PPopoverMenu {
            PPopoverMenuItem(icon: "doc.on.doc", title: "Copy") {
                copyItem()
                dismiss()
            }
            PPopoverMenuDivider()
            PPopoverMenuItem(icon: "trash", title: "Delete", variant: .destructive) {
                deleteItem()
                dismiss()
            }
        }
    }
```

**Positions:** `.top`, `.bottom`, `.leading`, `.trailing`
**Styles:** `.light`, `.dark`, `.custom(PopoverColor)`

---

### PIconButton

Circular icon-only buttons.

```swift
// Basic usage
PIconButton(icon: "plus") { action() }

// Variants
PIconButton(icon: "heart", variant: .primary) { }
PIconButton(icon: "share", variant: .secondary) { }
PIconButton(icon: "bookmark", variant: .outline) { }
PIconButton(icon: "ellipsis", variant: .ghost) { }

// Sizes
PIconButton(icon: "gear").size(.sm)   // 32pt
PIconButton(icon: "gear").size(.md)   // 40pt
PIconButton(icon: "gear").size(.lg)   // 48pt
PIconButton(icon: "gear").size(.xl)   // 56pt

// Custom colors
PIconButton(icon: "star.fill")
    .foreground(.yellow)
    .background(.yellow.opacity(0.2))

// Disabled
PIconButton(icon: "trash")
    .disabled(true)

// Haptics
PIconButton(icon: "bell")
    .haptics(true)
```

**Variants:** `.primary`, `.secondary`, `.outline`, `.ghost`
**Sizes:** `.sm`, `.md`, `.lg`, `.xl`, `.custom(CGFloat)`

---

### PSidebar

Animated sidebar navigation panel.

```swift
@State var showSidebar = false

Button("Menu") { showSidebar = true }
    .pSidebar(isPresented: $showSidebar) { dismiss in
        PSidebarContent {
            PSidebarHeader(title: "Menu", subtitle: "Navigation")
                .avatar(url: URL(string: "..."))
            
            PSidebarSection("Main") {
                PSidebarItem(icon: "house", title: "Home") {
                    navigateTo(.home)
                }
                .badge(3)
                
                PSidebarItem(icon: "gear", title: "Settings") {
                    navigateTo(.settings)
                }
                
                PSidebarItem(icon: "trash", title: "Delete", variant: .destructive) {
                    showDeleteConfirm = true
                }
            }
            
            PSidebarDivider()
            
            PSidebarFooter {
                PButton("Logout", variant: .ghost) { logout() }
            }
        }
    }

// Configuration options
.pSidebar(
    isPresented: $show,
    position: .leading,          // .leading, .trailing
    style: .floating,            // .fullHeight, .floating
    overlayStyle: .dimmedBlur,   // .dimmed, .blurred, .dimmedBlur, .none
    width: 280,
    enableDragToDismiss: true
) { ... }
```

**Positions:** `.leading`, `.trailing`
**Styles:** `.fullHeight`, `.floating`
**Overlay Styles:** `.dimmed`, `.blurred`, `.dimmedBlur`, `.none`

---

### PTapGesture

Fluid press animations for any view.

```swift
// Basic tap gesture
Text("Tap me")
    .pTapGesture { print("Tapped") }

// Presets
View().pTapGestureBouncy { }    // More scale effect
View().pTapGestureSubtle { }    // Minimal feedback
View().pTapGestureProminent { } // Strong feedback
View().pTapGestureSnappy { }    // Quick response

// Custom configuration
View()
    .modifier(
        PTapGesture { print("Tapped") }
            .scaleEffect(0.95)
            .opacityEffect(0.8)
            .brightnessEffect(-0.05)
            .haptics(true)
            .hapticStyle(.medium)
            .pressAnimation(response: 0.2, damping: 0.7)
            .releaseAnimation(response: 0.3, damping: 0.8)
    )
```

---

### PList

Settings-style grouped lists.

```swift
PList("Account") {
    PListItem("Profile", subtitle: "John Doe", icon: "person.fill")
        .accessory(.chevron)
        .onTap { navigateToProfile() }
    
    PListItem("Email", subtitle: "john@example.com", icon: "envelope.fill")
        .accessory(.chevron)
    
    PListItem("Verified", icon: "checkmark.seal.fill")
        .accessory(.checkmark)
        .hideDivider()
}

// Toggle items
PList("Preferences") {
    PListToggleItem("Notifications", icon: "bell.fill", isOn: $notifications)
    PListToggleItem("Dark Mode", icon: "moon.fill", isOn: $darkMode)
}

// Styles
PList("Items", style: .grouped) { ... }      // Card background
PList("Items", style: .insetGrouped) { ... } // Rounded with margins
PList("Items", style: .plain) { ... }        // No styling

// With custom leading content
PListItem("User Name", subtitle: "Online") {
    PAvatar(name: "User")
        .size(.sm)
        .status(.online)
}
.accessory(.chevron)

// Destructive action
PListItem("Delete Account", icon: "trash.fill", iconColor: .red)
    .destructive()
    .onTap { deleteAccount() }

// With badges
PListItem("Messages")
    .accessory(.badge("5"))

// Sections
PList {
    PListSection("General") {
        PListItem("Language")
        PListItem("Region")
    }
    
    PListSection("Privacy", footer: "Manage your data") {
        PListItem("Clear History")
    }
}
```

**Styles:** `.grouped`, `.insetGrouped`, `.plain`
**Accessories:** `.chevron`, `.checkmark`, `.icon(String)`, `.badge(String)`, `.none`

---

### PExpandableFAB (Morphing FAB)

Floating action button that expands into a menu. (iOS 17+ only)

```swift
@State var isExpanded = false

PExpandableFAB(isExpanded: $isExpanded) {
    Image(systemName: "plus")
        .font(.title3.bold())
} menu: {
    VStack(spacing: 4) {
        PFABMenuItem(icon: "paperplane", title: "Send", description: "Transfer crypto") {
            // action
            isExpanded = true  // Expand to detail
        }
        PFABMenuItem(icon: "arrow.down", title: "Receive") {
            // action
        }
    }
    .padding(.vertical, 16)
} detail: {
    // Full-screen detail view when isExpanded = true
    DetailView()
}

// Menu only (no detail)
PExpandableFAB {
    Image(systemName: "plus")
} menu: {
    // Menu items
}

// Customization
PExpandableFAB(...)
    .size(60)
    .cornerRadius(30)
    .tint(.purple)
    .iconColor(.white)
    .shadow(.md)
    .haptics(true)
```

---

### PSkeleton

Loading placeholder with shimmer animation.

```swift
// Basic shapes
PSkeleton(shape: .rectangle(.md))
    .frame(height: 20)

PSkeleton(shape: .circle)
    .frame(width: 48, height: 48)

PSkeleton(shape: .capsule)
    .frame(width: 100, height: 32)

// Text skeleton
PSkeletonText(lines: 3)
PSkeletonText(lines: 2, lastLineWidth: 0.6)

// Avatar skeleton
PSkeletonAvatar(size: .lg)
PSkeletonAvatar(size: 48)

// Card skeleton
PSkeletonCard(showAvatar: true, lines: 2)
PSkeletonCard(showAvatar: false, lines: 3, showImage: true)

// Custom colors
PSkeleton()
    .baseColor(.gray.opacity(0.2))
    .highlightColor(.gray.opacity(0.4))
    .duration(1.5)

// Conditional skeleton
Text("Actual content")
    .skeleton(isLoading)

// With custom placeholder
View()
    .skeleton(isLoading) {
        PSkeletonCard(showAvatar: true)
    }
```

---

### PBottomTab

Bottom navigation bar with standard and floating styles.

```swift
@State var selected = "Home"

PBottomTab(selection: $selected) {
    VStack(spacing: 0) {
        // Content panes
        PBottomTabPane {
            PBottomTabContent("Home") { HomeView() }
            PBottomTabContent("Search") { SearchView() }
            PBottomTabContent("Profile") { ProfileView() }
        }
        
        // Tab bar
        PBottomTabList {
            PBottomTabTrigger("Home", icon: "house")
            PBottomTabTrigger("Search", icon: "magnifyingglass")
            PBottomTabTrigger("Profile", icon: "person", badge: 3)
        }
    }
}
.style(.standard)      // iOS-like tab bar
.style(.floating)      // Floating pill bar
.transition(.slide)    // Content transition

// Custom trigger with separate active icon
PBottomTabTrigger("Home", icon: "house", activeIcon: "house.fill")

// Customization
.bottomTabListBackground(.black.opacity(0.85))
.bottomTabIndicatorColor(.white)
.bottomTabActiveColor(.blue)
.bottomTabInactiveColor(.gray)
.bottomTabIconSize(24)
.bottomTabShowLabels(false)  // Icon-only
```

**Styles:** `.standard`, `.floating`
**Transitions:** `.none`, `.fade`, `.slide`, `.scale`, `.lift`, `.zoom`

---

### PTopTab

Top navigation tabs with segmented, pills, and underline styles.

```swift
@State var selected = "Account"

PTopTab(selection: $selected) {
    VStack(spacing: 0) {
        PTopTabList {
            PTopTabTrigger("Account")
            PTopTabTrigger("Password")
            PTopTabTrigger("Settings")
        }
        .padding(.horizontal)
        
        PTopTabPane {
            PTopTabContent("Account") { AccountView() }
            PTopTabContent("Password") { PasswordView() }
            PTopTabContent("Settings") { SettingsView() }
        }
    }
}
.style(.segmented)    // iOS segmented control
.style(.pills)        // Pill indicator
.style(.underline)    // Underline indicator

// With icons
PTopTabTrigger("Tokens", icon: "bitcoinsign.circle")
PTopTabTrigger("NFTs", icon: "photo.stack", activeIcon: "photo.stack.fill")

// Customization
.topTabFontSize(16)
.topTabFontWeight(.semibold)
.topTabActiveColor(.white)
.topTabInactiveColor(.gray)
.topTabIndicatorColor(.blue)
.topTabListBackground(.gray.opacity(0.2))
.topTabIconPosition(.leading)  // .leading, .trailing, .top
.topTabShowLabels(false)       // Icon-only mode
```

**Styles:** `.segmented`, `.pills`, `.underline`
**Sizes:** `.sm`, `.md`, `.lg`
**Transitions:** `.none`, `.fade`, `.slide`, `.scale`, `.lift`, `.zoom`

---

## Animation Guidelines

### Spring Animations (Recommended)

```swift
// Standard spring for most interactions
.animation(.spring(response: 0.35, dampingFraction: 0.7), value: state)

// Quick spring for micro-interactions
.animation(.spring(response: 0.25, dampingFraction: 0.8), value: state)

// Gentle spring for large transitions
.animation(.spring(response: 0.5, dampingFraction: 0.75), value: state)
```

### Press States

```swift
// Standard press effect
.scaleEffect(isPressed ? 0.97 : 1.0)
.animation(.spring(response: 0.25, dampingFraction: 0.7), value: isPressed)
```

### Respect Reduced Motion

```swift
@Environment(\.accessibilityReduceMotion) var reduceMotion

.animation(reduceMotion ? nil : .spring(response: 0.35, dampingFraction: 0.7), value: state)
```

---

## Accessibility

- All components support VoiceOver with proper labels
- Touch targets are minimum 44x44pt
- Color contrast meets WCAG AA standards
- Reduced motion is respected across all animations
- Haptic feedback can be disabled

---

## Platform Support

- **iOS 16.0+** - Full support
- **macOS 13.0+** - Full support (except PMorphingFAB)
- **tvOS 16.0+** - Full support (except PMorphingFAB)
- **watchOS 9.0+** - Full support (except PMorphingFAB)

---

## Best Practices

1. **Apply theme at root** - Use `.prettyTheme()` at your app's root view
2. **Use design tokens** - Access `theme.spacing`, `theme.radius`, etc. instead of hardcoded values
3. **Leverage fluent API** - Chain modifiers for readable configuration
4. **Respect dark mode** - Use `theme.colors(for: colorScheme)` for automatic color adaptation
5. **Consider accessibility** - Test with VoiceOver and reduced motion enabled
6. **Use spring animations** - They feel more natural than linear/ease animations
7. **Keep press scales subtle** - 0.97-0.98 scale feels responsive without being jarring

Benefits of Using the Cursor Rule

⚡ Faster Development

AI assistants can generate correct PrettyUI code instantly, with proper modifiers and theming.

✅ Consistent Code

The rule ensures AI follows PrettyUI conventions, design tokens, and best practices.

📚 Complete API Coverage

All 20+ components are documented with their full API, variants, and modifiers.

🎨 Theme-Aware

AI understands the theming system and generates properly themed components.


Example Prompts

Once you've added the Cursor rule, try these prompts:

"Create a login form with PrettyUI using email and password fields, a primary submit button, and show a success toast on submit"
"Build a settings screen with PList showing profile info, notification toggles, and a destructive logout option"
"Create a card grid showing user avatars with online status and notification badges"
"Add a confirmation modal that appears when deleting an item, with Cancel and Delete buttons"