本文へ移動
cccskills
無料GitHub で公開

openfoodjournal-ui

UI/UX design system and component patterns for OpenFoodJournal. Use when building new views, adding UI components, styling elements, presenting sheets, or modifying any view file. Contains the project's design tokens, component catalog, animation conventions, and layout patterns so new UI stays visually consistent with the existing app.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md12.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

OpenFoodJournal UI/UX Design System

Living reference for every visual and interaction pattern in the app. Consult before creating or modifying any SwiftUI view. Update whenever a new pattern is established or an existing one changes.

Quick Reference

TokenValueWhere Used
Card corner radius20 ptMacroSummaryBar, glass cards
Button/chip radius12 ptRemaining calorie pill, macro chips
Badge radius8 ptStatus badges, small pills
List row insets(8, 16, 8, 16)All List rows
Section gap12–20 ptBetween major sections
Min hit target44 ptAll tappable elements
Glass availabilityiOS 26+ (no guards)Entire codebase

Color System

Macro Colors (Canonical)

MacroColorUsage
Calories.orangeCalorie countdown, energy metrics
Protein.blueProtein goals, progress rings
Carbs.greenCarbohydrate goals, progress
Fat.yellowFat goals, progress

Progress Status Colors

StatusColorThreshold
Met.green95–105% of goal
Near.yellow50–80% of goal
Slightly over.orange105–120% of goal
Way over.purple>120% of goal
Under.red<50% of goal

Opacity Conventions

ElementOpacity
Glass tint0.35
Background highlight0.12
Disabled state0.5
Separator lines.secondary.opacity(0.4)
Secondary text.foregroundStyle(.secondary) (system)
Tertiary text.foregroundStyle(.tertiary) (system)

Typography

RoleFontWeightSize
Display macro total.system(size: 32, weight: .bold, design: .rounded)bold32
Section headline.headlinedefault~17
Row title.body.fontWeight(.medium)~17
Row subtitle.captiondefault~12
Form label.subheadlinedefault~15
Numeric alignment.monospacedDigit()—inherited

Convention: All numeric displays use .system(design: .rounded). Precise alignment uses .monospacedDigit().

Glassmorphism (iOS 26+)

The app targets iOS 26+ exclusively — no #available guards needed.

Standard Patterns

// Card surface
.glassEffect(in: .rect(cornerRadius: 20))

// Circular element (macro rings)
.glassEffect(in: .circle)

// Tinted interactive element
.glassEffect(
    .regular.tint(color.opacity(0.35)),
    in: .circle
)

// Multiple glass elements in proximity
GlassEffectContainer(spacing: 20) {
    HStack { /* glass children */ }
}

// Buttons
.buttonStyle(.glass)           // Standard
.buttonStyle(.glassProminent)  // Primary action

Glass Rules

  1. Apply .glassEffect() after layout and appearance modifiers
  2. Wrap multiple glass siblings in GlassEffectContainer
  3. Use .interactive() only on tappable/focusable elements
  4. Use .glassEffectID(_:in:) + @Namespace for morphing transitions
  5. Prefer .glassEffect() over .background(.ultraThinMaterial) for new UI

See the swiftui-liquid-glass skill for the complete Liquid Glass API reference.

Navigation Architecture

Tab Structure

4-tab TabView at root with .tabBarMinimizeBehavior(.never):

TabViewIcon
JournalDailyLogViewbook.pages
Food BankFoodBankViewrefrigerator
HistoryHistoryViewchart.xyaxis.line
SettingsSettingsViewgearshape

Each tab wraps its content in NavigationStack.

Sheet Management (Enum-Driven)

Always use a single enum for all sheets within a page:

enum DailyLogSheet: Identifiable {
    case scan, manualEntry, editEntry(NutritionEntry)
    case foodBank, containers
    
    var id: String { /* unique per case */ }
}

@State private var presentedSheet: DailyLogSheet?

// In body:
.sheet(item: $presentedSheet) { sheet in
    switch sheet {
    case .scan: ScanCaptureView(logDate: selectedDate)
    case .manualEntry: ManualEntryView(defaultDate: selectedDate)
    // ...
    }
}

Never use multiple @State booleans for sheet presentation.

Sheet Configuration

Every sheet must include:

  • NavigationStack wrapper for internal nav
  • .navigationTitle() + .navigationBarTitleDisplayMode(.inline)
  • Cancel button: ToolbarItem(placement: .cancellationAction)
  • Save button: ToolbarItem(placement: .confirmationAction) with .fontWeight(.semibold) and .disabled() guard
  • @Environment(\.dismiss) private var dismiss

Component Catalog

Shared Components (Views/Shared/)

ComponentPurposeSize/Shape
MacroRingViewCircular progress for one macro56×56 pt, circle
MacroSummaryBar3-column macro cards + calorie headlineFull width, glass card
RadialMenuButtonFloating "+" FAB with radial menuCircular, bottom-aligned
MicronutrientSummaryViewProgress bars for all microsFull width section
NutrientBreakdownViewDonut chart + per-food barsNavigationDestination
NutritionDetailViewPeriod picker + macro cards + microsFull screen section
ServingMappingSectionReusable Form section for unit mapsForm section
CursorEndModifierUITextField cursor fixApplied at app root

Row Components

ComponentContextKey Elements
EntryRowViewList row in DailyLogName + brand + calories + macro chips + swipe
SavedFoodRowViewList row in FoodBankSource icon + name + serving + calories
MealSectionViewSection wrapper in ListHeader with meal icon + calorie total

Inline Component Pattern

Extract reusable card builders as private @ViewBuilder functions:

@ViewBuilder
private func macroCard(_ macro: NutrientKind.MacroType, value: Double, 
                       goal: Double, color: Color) -> some View {
    Button { selectedMacro = macro } label: {
        VStack(spacing: 4) { /* content */ }
    }
    .buttonStyle(.plain)
}

Layout Patterns

List (Preferred for Swipeable Content)

List {
    MealSectionView(...)  // Returns Section{}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)

Gotcha: .swipeActions is silently ignored outside List. Never use ScrollView + LazyVStack for rows that need swipe actions.

LazyVGrid (2-Column Cards)

LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 10) {
    macroCard(...)  // 4 cards in 2×2
}

List Row Customization

.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))

Empty States

ContentUnavailableView {
    Label("No Saved Foods", systemImage: "refrigerator")
} description: {
    Text("Save a food from a scan first...")
}

Animation Conventions

ContextAnimationValue
Menu open/close.spring(duration: 0.4, bounce: 0.3)RadialMenuButton
Micro-interaction.spring(duration: 0.2)Option highlight
Disclosure toggle.spring(duration: 0.3)Section expand
Progress rings.easeInOutValue transitions
View transitions.opacity.combined(with: .move(edge: .top))Expanding sections
Glass morphing.glassEffectTransition(.matchedGeometry)RadialMenu items
Haptic feedback.sensoryFeedback(.impact(flexibility: .soft))Menu open/close
Sheet chain delayasyncAfter(deadline: .now() + 0.15)Before next sheet

Convention: Use withAnimation(.spring(...)) for user-initiated actions. Use .animation(.easeInOut, value:) for data-driven transitions.

Form/Input Patterns

Numeric Text Fields

// Always use text-backed fields to avoid cursor-jump artifacts
@State private var quantityText: String
let quantity = Double(quantityText) ?? 0

TextField("Weight", text: $quantityText)
    .keyboardType(.decimalPad)

Focus Chain

fileprivate enum FormField: Hashable {
    case name, calories, protein, carbs, fat
    case micronutrient(String)
}
@FocusState private var focusedField: FormField?

// Chain fields:
TextField("Calories", text: $caloriesText)
    .focused($focusedField, equals: .calories)
    .submitLabel(.next)
    .onSubmit { focusedField = .protein }

Picker Styles

StyleUse Case
.pickerStyle(.menu)Compact inline (meal type in cards)
.pickerStyle(.segmented)3–4 options (period picker)
Default wheelIn-form selection

Confirmation Dialogs

.confirmationDialog(
    "Delete \(food.name)?",
    isPresented: $showDeleteConfirm,
    titleVisibility: .visible
) {
    Button("Delete", role: .destructive) { /* action */ }
}

State Management Patterns

Service Injection

// App creates @Observable services, injects via .environment()
@Environment(NutritionStore.self) private var store
@Environment(UserGoals.self) private var goals

SwiftData Bindings

@Bindable var entry: NutritionEntry  // Two-way binding to @Model
TextField("Name", text: $entry.name)  // Auto-persists

Derived State

// Compute from primary sources — never duplicate as @State
private var filteredFoods: [SavedFood] {
    searchText.isEmpty ? allFoods : allFoods.filter { ... }
}

Persistence

// UserGoals uses AppStorage with @ObservationIgnored to avoid conflicts
@ObservationIgnored @AppStorage("goals.calories")
var dailyCalories: Double = 2000

SF Symbols Reference

ActionSymbol
Scancamera.fill
Manual entrypencil
Food Bankrefrigerator
Containersscalemass
Historychart.xyaxis.line
Settingsgearshape
Deletetrash
Addplus / plus.circle
Breakfastsunrise
Lunchsun.max
Dinnermoon.stars
Snackleaf
Estimate modewand.and.sparkles
Label modebarcode.viewfinder

Checklist for New Views

Before merging any new view:

  • Uses glass effects (not .background(.ultraThinMaterial))
  • Card corners use 20 pt radius
  • Colors match macro color table above
  • Sheets use enum-driven presentation
  • Sheets have Cancel/Save toolbar + dismiss
  • List rows use standard insets (8, 16, 8, 16)
  • Animations use spring for user actions, easeInOut for data
  • Numeric inputs use text-backed fields
  • Tappable elements have 44+ pt hit targets
  • Empty states use ContentUnavailableView
  • Progress indicators use the status color thresholds

UI Roadmap

Food Bank Improvements

  • Remove source icons (barcode/fork/pencil) from SavedFoodRowView — they add visual noise without value
  • Shift calorie count from right edge to where source icons were (left-center area)
  • Add macro chips (P/C/F) to the right of each food row — same MacroChip pattern as EntryRowView
  • Add "Last Used" sort option and make it the default — newly added foods count as "last used" even before being logged, so recently saved foods surface to the top for better glanceability
  • Search should match brand field in addition to name

Keyboard UX

  • Every keyboard popup must have a "Done" button to dismiss — use .toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer(); Button("Done") { focusedField = nil } } }

Calendar Strip → Continuous Scrollable Calendar

  • Replace fixed Sun–Sat WeeklyCalendarStrip with a continuously scrollable calendar
  • Interaction design modeled after iOS Calendar monthly view: vertical continuous scroll with smooth momentum snapping to month boundaries
  • Sticky month/year header that transitions smoothly as user scrolls between months
  • Days show the same progress ring colors as current day cells
  • Tapping a day selects it and scrolls the journal to that date

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use this skill whenever the user wants to create or improve a presentation for an academic context — conference papers, seminar talks, thesis defenses, grant briefings, lab meetings, invited lectures, or any presentation where the audience will evaluate reasoning and evidence. Triggers include: 'conference talk', 'seminar slides', 'thesis defense', 'research presentation', 'academic deck', 'academic presentation'. Also triggers when the user asks to 'make slides' in combination with academic content (e.g., 'make slides for my paper on X', 'create a presentation for my dissertation defense', 'build a deck for my grant proposal'). This skill governs CONTENT and STRUCTURE decisions. For the technical work of creating or editing the .pptx file itself, also read the pptx SKILL.md.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

adp

無料

Redpanda's Agentic Data Plane: governance infrastructure for building, running, and governing AI agents and MCP servers, plus a proxying AI Gateway for LLM providers, operated via `rpk ai` and the ADP API. Use when creating or managing AI agents (managed or self-managed) via `rpk ai agent` or `AgentRegistryService`; configuring MCP servers (remote or managed catalog, code mode, auth); setting up LLM providers or querying models via `rpk ai llm`/`rpk ai model` or the AI Gateway proxy; or configuring budgets, guardrails, or Cedar access-control policies through the governance APIs. Also covers reading agent transcripts and spending insights, and wiring OAuth clients or providers to the aigw Authorization Server. For the separate rpk cloud mcp control-plane MCP server, see `/redpanda:rpk-cloud`.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

ads

無料

Operate professional paid advertising across Google, Meta, YouTube, LinkedIn, TikTok, Microsoft, Apple, Amazon, Reddit, Pinterest, Snapchat, and X. Use for account intake, source-grounded audits, strategy, budget and measurement planning, creative production, experiments, reporting, monitoring, and explicitly approved campaign changes. Also trigger on PPC, paid social, retail media, attribution, tracking, landing pages, cross-platform conversion totals, negative keywords or search terms, beta-feature scoring, stale platform claims, API-token or credential setup, campaign deletion, and safe Claude Ads installation or uninstall.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

ads-apple

無料

Audit Apple Ads measurement, AdServices and AdAttributionKit, campaign and keyword structure, Search Match, App Store placements, custom product pages, bidding, budgets, MMP reconciliation, and policy. Use for Apple Ads, Apple Search Ads, App Store ads, Search Match, custom product pages, AdServices, or Apple app-install campaigns.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

Research competitor paid-ad presence, messaging, creative, formats, landing pages, keyword and auction signals, transparent ad libraries, and strategic gaps across supported platforms. Use for competitor ads, ad libraries, ad spy, competitive PPC analysis, competitor creative, Google Ads Transparency, Meta Ad Library, or paid-media competitor research.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

ads-dna

無料

Extract a public-safe brand and offer profile for paid advertising from an authorized website and operator input. Triggers on: brand DNA, brand profile, brand identity, brand style, brand colors, brand voice, visual identity, style guide, website brand analysis.

日本語の概要は準備中です。原文の説明を表示しています。

skillmds/skillmd1202026年10月9日 更新

skillmds のスキルをすべて見る

このスキルの問題を報告する