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:
385
CLAUDE.md
385
CLAUDE.md
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user