Add comprehensive iOS design system documentation to CLAUDE.md

Document the 5-color semantic design system, including color palette reference,
usage guidelines, and complete patterns for creating new views, cards, buttons,
and UI components. Add critical styling rules for Form/List views with proper
background colors and row styling requirements.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-21 08:01:10 -06:00
parent a2b81a244b
commit 050a486d6f

385
CLAUDE.md
View File

@@ -124,6 +124,391 @@ Task {
}
```
### iOS Design System
**CRITICAL**: Always use the custom design system colors defined in `iosApp/iosApp/Design/DesignSystem.swift` and Xcode Asset Catalog. Never use system colors directly.
#### Color Palette
The app uses a 5-color semantic design system:
```swift
// Primary Colors
Color.appPrimary // #07A0C3 (BlueGreen) - Primary actions, important icons
Color.appSecondary // #0055A5 (Cerulean) - Secondary actions
Color.appAccent // #F5A623 (BrightAmber) - Highlights, notifications, accents
// Status Colors
Color.appError // #DD1C1A (PrimaryScarlet) - Errors, destructive actions
// Background Colors
Color.appBackgroundPrimary // #FFF1D0 (cream) light / #0A1929 dark - Screen backgrounds
Color.appBackgroundSecondary // Blue-gray - Cards, list rows, elevated surfaces
// Text Colors
Color.appTextPrimary // Primary text (dark mode aware)
Color.appTextSecondary // Secondary text (less emphasis)
Color.appTextOnPrimary // Text on primary colored backgrounds (white)
```
**Color Usage Guidelines:**
- **Buttons**: Primary buttons use `Color.appPrimary`, destructive buttons use `Color.appError`
- **Icons**: Use `Color.appPrimary` for main actions, `Color.appAccent` for secondary/info icons
- **Cards**: Always use `Color.appBackgroundSecondary` for card backgrounds
- **Screens**: Always use `Color.appBackgroundPrimary` for main view backgrounds
- **Text**: Use `Color.appTextPrimary` for body text, `Color.appTextSecondary` for captions/subtitles
#### Creating New Views
**Standard Form/List View Pattern:**
```swift
import SwiftUI
import ComposeApp
struct MyNewView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel = MyViewModel()
@FocusState private var focusedField: Field?
enum Field {
case fieldOne, fieldTwo
}
var body: some View {
NavigationStack {
Form {
// Header Section (optional, with clear background)
Section {
VStack(spacing: 16) {
Image(systemName: "icon.name")
.font(.system(size: 60))
.foregroundStyle(Color.appPrimary.gradient)
Text("View Title")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(Color.appTextPrimary)
Text("Subtitle description")
.font(.subheadline)
.foregroundColor(Color.appTextSecondary)
}
.frame(maxWidth: .infinity)
.padding(.vertical)
}
.listRowBackground(Color.clear)
// Data Section
Section {
TextField("Field One", text: $viewModel.fieldOne)
.focused($focusedField, equals: .fieldOne)
TextField("Field Two", text: $viewModel.fieldTwo)
.focused($focusedField, equals: .fieldTwo)
} header: {
Text("Section Header")
} footer: {
Text("Helper text here")
}
.listRowBackground(Color.appBackgroundSecondary)
// Error Section (conditional)
if let error = viewModel.errorMessage {
Section {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(Color.appError)
Text(error)
.foregroundColor(Color.appError)
.font(.subheadline)
}
}
.listRowBackground(Color.appBackgroundSecondary)
}
// Action Button Section
Section {
Button(action: { /* action */ }) {
HStack {
Spacer()
if viewModel.isLoading {
ProgressView()
} else {
Text("Submit")
.fontWeight(.semibold)
}
Spacer()
}
}
.disabled(viewModel.isLoading)
}
.listRowBackground(Color.appBackgroundSecondary)
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.appBackgroundPrimary)
.navigationTitle("Title")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
}
}
}
}
```
**CRITICAL Form/List Styling Rules:**
1. **Always add these three modifiers to Form/List:**
```swift
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(Color.appBackgroundPrimary)
```
2. **Always add `.listRowBackground()` to EVERY Section:**
```swift
Section {
// content
}
.listRowBackground(Color.appBackgroundSecondary) // ← REQUIRED
```
3. **Exception for header sections:** Use `.listRowBackground(Color.clear)` for decorative headers
#### Creating Custom Cards
**Standard Card Pattern:**
```swift
struct MyCard: View {
let item: MyModel
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// Header
HStack {
Image(systemName: "icon.name")
.font(.title2)
.foregroundColor(Color.appPrimary)
Text(item.title)
.font(.headline)
.foregroundColor(Color.appTextPrimary)
Spacer()
// Badge or status indicator
Text("Status")
.font(.caption)
.foregroundColor(Color.appTextOnPrimary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.appPrimary)
.clipShape(Capsule())
}
// Content
Text(item.description)
.font(.subheadline)
.foregroundColor(Color.appTextSecondary)
.lineLimit(2)
// Footer
HStack {
Label("Info", systemImage: "info.circle")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(Color.appTextSecondary)
}
}
.padding()
.background(Color.appBackgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(color: Color.black.opacity(0.1), radius: 2, x: 0, y: 1)
}
}
```
**Card Design Guidelines:**
- Background: `Color.appBackgroundSecondary`
- Corner radius: 12pt
- Padding: 16pt (standard) or 12pt (compact)
- Shadow: `Color.black.opacity(0.1), radius: 2, x: 0, y: 1`
- Use `VStack` for vertical layout, `HStack` for horizontal
#### Creating Buttons
**Primary Button:**
```swift
Button(action: { /* action */ }) {
Text("Primary Action")
.fontWeight(.semibold)
.frame(maxWidth: .infinity)
.foregroundColor(Color.appTextOnPrimary)
.padding()
.background(Color.appPrimary)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
```
**Destructive Button:**
```swift
Button(action: { /* action */ }) {
Label("Delete", systemImage: "trash")
.foregroundColor(Color.appError)
}
```
**Secondary Button (bordered):**
```swift
Button(action: { /* action */ }) {
Text("Secondary")
.foregroundColor(Color.appPrimary)
}
.buttonStyle(.bordered)
```
#### Icons and SF Symbols
**Icon Coloring:**
- Primary actions: `Color.appPrimary` (e.g., add, edit)
- Secondary info: `Color.appAccent` (e.g., info, notification)
- Destructive: `Color.appError` (e.g., delete, warning)
- Neutral: `Color.appTextSecondary` (e.g., chevrons, decorative)
**Common Icon Patterns:**
```swift
// Large decorative icon
Image(systemName: "house.fill")
.font(.system(size: 60))
.foregroundStyle(Color.appPrimary.gradient)
// Inline icon with label
Label("Title", systemImage: "folder")
.foregroundColor(Color.appPrimary)
// Status indicator icon
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color.appPrimary)
```
#### Spacing and Layout
Use constants from `DesignSystem.swift`:
```swift
// Standard spacing
AppSpacing.xs // 4pt
AppSpacing.sm // 8pt
AppSpacing.md // 12pt
AppSpacing.lg // 16pt
AppSpacing.xl // 24pt
// Example usage
VStack(spacing: AppSpacing.md) {
// content
}
```
#### Adding New Colors to Asset Catalog
If you need to add a new semantic color:
1. Open `iosApp/iosApp/Assets.xcassets/Colors/Semantic/`
2. Create new `.colorset` folder
3. Add `Contents.json`:
```json
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xHH",
"green" : "0xHH",
"red" : "0xHH"
}
},
"idiom" : "universal"
},
{
"appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xHH",
"green" : "0xHH",
"red" : "0xHH"
}
},
"idiom" : "universal"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
```
4. Add extension in `DesignSystem.swift`:
```swift
extension Color {
static let appNewColor = Color("NewColor")
}
```
#### View Modifiers and Helpers
**Error Handling Modifier:**
```swift
.handleErrors(
error: viewModel.errorMessage,
onRetry: { viewModel.retryAction() }
)
```
**Loading State:**
```swift
if viewModel.isLoading {
ProgressView()
.tint(Color.appPrimary)
} else {
// content
}
```
**Empty States:**
```swift
if items.isEmpty {
VStack(spacing: 16) {
Image(systemName: "tray")
.font(.system(size: 60))
.foregroundColor(Color.appTextSecondary.opacity(0.5))
Text("No Items")
.font(.headline)
.foregroundColor(Color.appTextPrimary)
Text("Get started by adding your first item")
.font(.subheadline)
.foregroundColor(Color.appTextSecondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
```
### Android Layer
Android uses Compose UI directly from `composeApp` with shared ViewModels. Navigation via Jetpack Compose Navigation in `App.kt`.