stitch-kit 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +139 -0
- package/CHANGELOG.md +86 -0
- package/README.md +167 -0
- package/agents/stitch-kit.md +93 -0
- package/bin/stitch-kit.mjs +430 -0
- package/docs/architecture.md +118 -0
- package/docs/color-prompt-guide.md +119 -0
- package/docs/mcp-naming-convention.md +64 -0
- package/docs/mcp-schemas/README.md +130 -0
- package/docs/mcp-schemas/apply_design_system.json +36 -0
- package/docs/mcp-schemas/create_design_system.json +78 -0
- package/docs/mcp-schemas/create_project.json +290 -0
- package/docs/mcp-schemas/delete_project.json +20 -0
- package/docs/mcp-schemas/edit_screens.json +46 -0
- package/docs/mcp-schemas/generate_screen_from_text.json +242 -0
- package/docs/mcp-schemas/generate_variants.json +77 -0
- package/docs/mcp-schemas/get_project.json +137 -0
- package/docs/mcp-schemas/get_screen.json +92 -0
- package/docs/mcp-schemas/list_design_systems.json +32 -0
- package/docs/mcp-schemas/list_projects.json +136 -0
- package/docs/mcp-schemas/list_screens.json +56 -0
- package/docs/mcp-schemas/update_design_system.json +32 -0
- package/docs/mcp-schemas/upload_screens_from_images.json +52 -0
- package/docs/prd-to-stitch-workflow.md +137 -0
- package/docs/skills-index.md +108 -0
- package/docs/tailwind-reference.md +207 -0
- package/package.json +41 -0
- package/skills/stitch-a11y/SKILL.md +428 -0
- package/skills/stitch-a11y/resources/audit-checklist.md +102 -0
- package/skills/stitch-animate/SKILL.md +371 -0
- package/skills/stitch-animate/resources/animation-patterns.md +248 -0
- package/skills/stitch-design-md/SKILL.md +215 -0
- package/skills/stitch-design-md/examples/DESIGN.md +54 -0
- package/skills/stitch-design-md/examples/usage.md +67 -0
- package/skills/stitch-design-md/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-design-system/SKILL.md +314 -0
- package/skills/stitch-design-system/resources/tokens-template.css +237 -0
- package/skills/stitch-html-components/SKILL.md +344 -0
- package/skills/stitch-html-components/resources/architecture-checklist.md +74 -0
- package/skills/stitch-html-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-loop/SKILL.md +183 -0
- package/skills/stitch-loop/examples/SITE.md +39 -0
- package/skills/stitch-loop/examples/next-prompt.md +24 -0
- package/skills/stitch-loop/examples/usage.md +77 -0
- package/skills/stitch-mcp-apply-design-system/SKILL.md +82 -0
- package/skills/stitch-mcp-create-design-system/SKILL.md +117 -0
- package/skills/stitch-mcp-create-project/SKILL.md +77 -0
- package/skills/stitch-mcp-delete-project/SKILL.md +62 -0
- package/skills/stitch-mcp-edit-screens/SKILL.md +121 -0
- package/skills/stitch-mcp-generate-screen-from-text/SKILL.md +102 -0
- package/skills/stitch-mcp-generate-screen-from-text/examples/desktop.md +53 -0
- package/skills/stitch-mcp-generate-screen-from-text/examples/mobile.md +71 -0
- package/skills/stitch-mcp-generate-variants/SKILL.md +124 -0
- package/skills/stitch-mcp-get-project/SKILL.md +67 -0
- package/skills/stitch-mcp-get-screen/SKILL.md +117 -0
- package/skills/stitch-mcp-get-screen/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-mcp-list-design-systems/SKILL.md +84 -0
- package/skills/stitch-mcp-list-projects/SKILL.md +77 -0
- package/skills/stitch-mcp-list-screens/SKILL.md +69 -0
- package/skills/stitch-mcp-update-design-system/SKILL.md +82 -0
- package/skills/stitch-mcp-upload-screens-from-images/SKILL.md +95 -0
- package/skills/stitch-mcp-upload-screens-from-images/scripts/encode-image.sh +43 -0
- package/skills/stitch-nextjs-components/SKILL.md +181 -0
- package/skills/stitch-nextjs-components/resources/architecture-checklist.md +65 -0
- package/skills/stitch-nextjs-components/resources/component-template.tsx +101 -0
- package/skills/stitch-nextjs-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-orchestrator/SKILL.md +337 -0
- package/skills/stitch-orchestrator/examples/workflow.md +173 -0
- package/skills/stitch-react-components/SKILL.md +236 -0
- package/skills/stitch-react-components/references/tailwind-to-react.md +117 -0
- package/skills/stitch-react-components/resources/architecture-checklist.md +34 -0
- package/skills/stitch-react-components/resources/component-template.tsx +35 -0
- package/skills/stitch-react-components/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-react-native-components/SKILL.md +333 -0
- package/skills/stitch-react-native-components/resources/architecture-checklist.md +74 -0
- package/skills/stitch-react-native-components/resources/component-template.tsx +104 -0
- package/skills/stitch-react-native-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-remotion/SKILL.md +280 -0
- package/skills/stitch-setup/SKILL.md +183 -0
- package/skills/stitch-shadcn-ui/SKILL.md +255 -0
- package/skills/stitch-skill-creator/SKILL.md +257 -0
- package/skills/stitch-skill-creator/references/output-patterns.md +71 -0
- package/skills/stitch-skill-creator/scripts/init_stitch_skill.py +229 -0
- package/skills/stitch-svelte-components/SKILL.md +282 -0
- package/skills/stitch-svelte-components/resources/architecture-checklist.md +62 -0
- package/skills/stitch-svelte-components/resources/component-template.svelte +193 -0
- package/skills/stitch-svelte-components/scripts/fetch-stitch.sh +36 -0
- package/skills/stitch-swiftui-components/SKILL.md +424 -0
- package/skills/stitch-swiftui-components/resources/architecture-checklist.md +83 -0
- package/skills/stitch-swiftui-components/resources/component-template.swift +131 -0
- package/skills/stitch-swiftui-components/resources/layout-mapping.md +155 -0
- package/skills/stitch-swiftui-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-ued-guide/SKILL.md +124 -0
- package/skills/stitch-ui-design-spec-generator/SKILL.md +115 -0
- package/skills/stitch-ui-design-spec-generator/examples/usage.md +79 -0
- package/skills/stitch-ui-design-variants/SKILL.md +127 -0
- package/skills/stitch-ui-prompt-architect/SKILL.md +196 -0
- package/skills/stitch-ui-prompt-architect/references/KEYWORDS.md +170 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
StitchComponent.svelte
|
|
3
|
+
|
|
4
|
+
Generated from Stitch design via stitch-svelte-components skill.
|
|
5
|
+
Replace "StitchComponent" with the actual component name.
|
|
6
|
+
Import path: $lib/components/StitchComponent.svelte
|
|
7
|
+
|
|
8
|
+
Svelte 5 runes API. No legacy Options API patterns.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { fade } from 'svelte/transition'
|
|
13
|
+
|
|
14
|
+
// ----------------------------------------------------------
|
|
15
|
+
// Types & Props
|
|
16
|
+
// ----------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Props for this component.
|
|
20
|
+
* All data comes through $props() — never imported directly from a store or module.
|
|
21
|
+
*/
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Primary heading text */
|
|
24
|
+
title: string
|
|
25
|
+
/** Supporting description — optional */
|
|
26
|
+
description?: string
|
|
27
|
+
/** Callback for primary action */
|
|
28
|
+
onAction?: () => void
|
|
29
|
+
/** Render slot content */
|
|
30
|
+
children?: import('svelte').Snippet
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* $props() is the Svelte 5 replacement for `export let`.
|
|
35
|
+
* Destructure with defaults for optional props.
|
|
36
|
+
*/
|
|
37
|
+
const { title, description = '', onAction, children }: Props = $props()
|
|
38
|
+
|
|
39
|
+
// ----------------------------------------------------------
|
|
40
|
+
// State
|
|
41
|
+
// ----------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** $state() is the Svelte 5 replacement for reactive `let`. */
|
|
44
|
+
let isExpanded = $state(false)
|
|
45
|
+
|
|
46
|
+
// ----------------------------------------------------------
|
|
47
|
+
// Derived values
|
|
48
|
+
// ----------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/** $derived() is the Svelte 5 replacement for $: reactive statements. */
|
|
51
|
+
const buttonLabel = $derived(isExpanded ? 'Collapse' : 'Expand')
|
|
52
|
+
|
|
53
|
+
// ----------------------------------------------------------
|
|
54
|
+
// Effects (use sparingly — prefer derived state when possible)
|
|
55
|
+
// ----------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
// $effect(() => {
|
|
58
|
+
// // Runs when dependencies change. Cleanup by returning a function.
|
|
59
|
+
// console.log('title changed:', title)
|
|
60
|
+
// return () => { /* cleanup */ }
|
|
61
|
+
// })
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<!--
|
|
65
|
+
Semantic landmark element.
|
|
66
|
+
aria-labelledby connects the section to its visible heading for screen readers.
|
|
67
|
+
-->
|
|
68
|
+
<section
|
|
69
|
+
aria-labelledby="stitch-component-title"
|
|
70
|
+
in:fade={{ duration: 200 }}
|
|
71
|
+
>
|
|
72
|
+
<!-- Heading — maintain hierarchy (h1 → h2 → h3, never skip) -->
|
|
73
|
+
<h2 id="stitch-component-title">{title}</h2>
|
|
74
|
+
|
|
75
|
+
<!-- Description — only render when provided -->
|
|
76
|
+
{#if description}
|
|
77
|
+
<p class="description">{description}</p>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
<!-- Named slot for nested content -->
|
|
81
|
+
{#if children}
|
|
82
|
+
<div class="content">
|
|
83
|
+
{@render children()}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
|
|
87
|
+
<!-- Example interactive element -->
|
|
88
|
+
{#if onAction}
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onclick={onAction}
|
|
92
|
+
class="btn-primary"
|
|
93
|
+
>
|
|
94
|
+
{buttonLabel}
|
|
95
|
+
</button>
|
|
96
|
+
{/if}
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<!--
|
|
100
|
+
Styles are SCOPED to this component by default.
|
|
101
|
+
Use CSS custom properties (var(--token)) for theming — never hardcode hex values.
|
|
102
|
+
The :global() selector escapes scoping when needed for child components.
|
|
103
|
+
-->
|
|
104
|
+
<style>
|
|
105
|
+
section {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: var(--space-4);
|
|
109
|
+
padding: var(--space-6);
|
|
110
|
+
background-color: var(--color-surface);
|
|
111
|
+
color: var(--color-text);
|
|
112
|
+
border: 1px solid var(--color-border);
|
|
113
|
+
border-radius: var(--radius-lg);
|
|
114
|
+
box-shadow: var(--shadow-sm);
|
|
115
|
+
width: 100%;
|
|
116
|
+
max-width: 42rem;
|
|
117
|
+
|
|
118
|
+
/* Responsive padding */
|
|
119
|
+
@media (min-width: 768px) {
|
|
120
|
+
padding: var(--space-8);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
h2 {
|
|
125
|
+
font-size: var(--text-2xl);
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
font-family: var(--font-sans);
|
|
128
|
+
color: var(--color-text);
|
|
129
|
+
line-height: 1.25;
|
|
130
|
+
margin: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.description {
|
|
134
|
+
font-size: var(--text-base);
|
|
135
|
+
color: var(--color-text-muted);
|
|
136
|
+
line-height: 1.6;
|
|
137
|
+
margin: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.content {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: var(--space-3);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Button — micro-interaction with CSS transitions */
|
|
147
|
+
.btn-primary {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
gap: var(--space-2);
|
|
152
|
+
padding: var(--space-2) var(--space-4);
|
|
153
|
+
background-color: var(--color-primary);
|
|
154
|
+
color: var(--color-primary-fg);
|
|
155
|
+
border: none;
|
|
156
|
+
border-radius: var(--radius-md);
|
|
157
|
+
font-size: var(--text-sm);
|
|
158
|
+
font-weight: 600;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
align-self: flex-start;
|
|
161
|
+
|
|
162
|
+
/* Smooth micro-transition */
|
|
163
|
+
transition:
|
|
164
|
+
background-color var(--motion-duration-fast) var(--motion-ease-default),
|
|
165
|
+
transform var(--motion-duration-fast) var(--motion-ease-default),
|
|
166
|
+
box-shadow var(--motion-duration-fast) var(--motion-ease-default);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.btn-primary:hover {
|
|
170
|
+
background-color: var(--color-primary-hover);
|
|
171
|
+
transform: translateY(-1px);
|
|
172
|
+
box-shadow: var(--shadow-sm);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.btn-primary:active {
|
|
176
|
+
transform: translateY(0);
|
|
177
|
+
box-shadow: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Focus ring — never outline: none without a replacement */
|
|
181
|
+
.btn-primary:focus-visible {
|
|
182
|
+
outline: 2px solid var(--color-border-focus);
|
|
183
|
+
outline-offset: 2px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Reduced motion — disable all transitions in this component */
|
|
187
|
+
@media (prefers-reduced-motion: reduce) {
|
|
188
|
+
section,
|
|
189
|
+
.btn-primary {
|
|
190
|
+
transition: none;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fetch-stitch.sh
|
|
3
|
+
# Reliably downloads Stitch HTML from Google Cloud Storage URLs.
|
|
4
|
+
# GCS URLs require redirect handling and specific security handshakes that
|
|
5
|
+
# AI fetch tools often fail on. This script handles both.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/fetch-stitch.sh "<url>" "<output-path>"
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# bash scripts/fetch-stitch.sh "$htmlCode_downloadUrl" "temp/source.html"
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
URL="${1:?Usage: fetch-stitch.sh <url> <output-path>}"
|
|
16
|
+
OUTPUT="${2:?Usage: fetch-stitch.sh <url> <output-path>}"
|
|
17
|
+
|
|
18
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
19
|
+
|
|
20
|
+
curl -L \
|
|
21
|
+
-A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
|
|
22
|
+
--compressed \
|
|
23
|
+
--retry 3 \
|
|
24
|
+
--retry-delay 1 \
|
|
25
|
+
--max-time 30 \
|
|
26
|
+
--silent \
|
|
27
|
+
--show-error \
|
|
28
|
+
--output "$OUTPUT" \
|
|
29
|
+
"$URL"
|
|
30
|
+
|
|
31
|
+
if [ ! -s "$OUTPUT" ]; then
|
|
32
|
+
echo "Error: Downloaded file is empty. URL may be expired or invalid." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "Downloaded to: $OUTPUT ($(wc -c < "$OUTPUT") bytes)"
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stitch-swiftui-components
|
|
3
|
+
description: Converts Stitch mobile designs into SwiftUI views for native iOS apps — VStack/HStack/ZStack layout mapping, Color asset tokens with dark mode, NavigationStack/TabView routing, and Xcode project structure.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- "stitch*:*"
|
|
6
|
+
- "Bash"
|
|
7
|
+
- "Read"
|
|
8
|
+
- "Write"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Stitch → SwiftUI (Native iOS)
|
|
12
|
+
|
|
13
|
+
You are a Swift/SwiftUI engineer. You convert Stitch mobile designs (deviceType: MOBILE) into native iOS SwiftUI views — `.swift` files that build and run in Xcode. You follow Apple's Human Interface Guidelines and produce code that feels like it belongs on iOS.
|
|
14
|
+
|
|
15
|
+
## When to use this skill
|
|
16
|
+
|
|
17
|
+
Use this skill when:
|
|
18
|
+
- The user wants **native iOS** output from a Stitch design
|
|
19
|
+
- The user mentions "SwiftUI", "Xcode", "iOS", "native iOS app"
|
|
20
|
+
- The design was generated with `deviceType: MOBILE`
|
|
21
|
+
|
|
22
|
+
**Note:** This skill targets iOS 16+ with SwiftUI. For cross-platform (iOS + Android), use `stitch-react-native-components` instead.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- Stitch design with `deviceType: MOBILE`
|
|
27
|
+
- Xcode 15+ on macOS
|
|
28
|
+
- Swift 5.9+
|
|
29
|
+
|
|
30
|
+
## Step 1: Retrieve the design
|
|
31
|
+
|
|
32
|
+
1. `list_tools` → find Stitch MCP prefix
|
|
33
|
+
2. `[prefix]:get_screen` → fetch metadata
|
|
34
|
+
3. Download HTML: `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"`
|
|
35
|
+
4. Check `screenshot.downloadUrl` — confirm mobile layout before converting
|
|
36
|
+
|
|
37
|
+
Only convert **MOBILE** designs. Desktop Stitch designs don't map well to SwiftUI without significant layout rethinking.
|
|
38
|
+
|
|
39
|
+
## Step 2: Xcode project structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
MyApp/
|
|
43
|
+
├── MyApp.swift ← @main entry point
|
|
44
|
+
├── ContentView.swift ← Root view (TabView or NavigationStack)
|
|
45
|
+
├── Theme/
|
|
46
|
+
│ ├── ThemeTokens.swift ← Design token constants
|
|
47
|
+
│ └── Color+App.swift ← Color extension with semantic names
|
|
48
|
+
├── Views/
|
|
49
|
+
│ ├── [ScreenName]View.swift ← One file per Stitch screen
|
|
50
|
+
│ └── Components/
|
|
51
|
+
│ └── [Name]View.swift ← Reusable component views
|
|
52
|
+
├── Models/
|
|
53
|
+
│ └── MockData.swift ← Static preview data
|
|
54
|
+
└── Assets.xcassets/
|
|
55
|
+
└── Colors/ ← Color assets for light/dark mode
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Step 3: The HTML/CSS → SwiftUI layout mapping
|
|
59
|
+
|
|
60
|
+
This is the core translation. Apply these rules to every element in the Stitch HTML:
|
|
61
|
+
|
|
62
|
+
### Layout containers
|
|
63
|
+
|
|
64
|
+
| HTML/CSS pattern | → SwiftUI |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `display:flex; flex-direction:column` | `VStack(alignment: .leading, spacing: 16)` |
|
|
67
|
+
| `display:flex; flex-direction:row` | `HStack(alignment: .center, spacing: 12)` |
|
|
68
|
+
| `display:flex; justify-content:space-between` | `HStack { Spacer() }` pattern |
|
|
69
|
+
| `position:absolute` overlay | `ZStack` with layered views |
|
|
70
|
+
| `display:grid` (2-column) | `LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16)` |
|
|
71
|
+
| `overflow-y: scroll` | `ScrollView(.vertical, showsIndicators: false)` |
|
|
72
|
+
| Repeated list of items | `List` or `ForEach` inside `ScrollView + LazyVStack` |
|
|
73
|
+
| `position:fixed` bottom nav | `TabView` (preferred) or explicit `VStack` with `Spacer()` |
|
|
74
|
+
|
|
75
|
+
### Spacing mapping
|
|
76
|
+
|
|
77
|
+
SwiftUI uses points (1pt ≈ 1dp on non-retina, 2px on Retina @2x):
|
|
78
|
+
|
|
79
|
+
```swift
|
|
80
|
+
// Spacing from Tailwind → SwiftUI points
|
|
81
|
+
// p-1(4px)→4 p-2(8px)→8 p-3(12px)→12 p-4(16px)→16
|
|
82
|
+
// p-6(24px)→24 p-8(32px)→32 p-12(48px)→48 p-16(64px)→64
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Geometry mapping
|
|
86
|
+
|
|
87
|
+
```swift
|
|
88
|
+
// Tailwind rounded- → SwiftUI cornerRadius
|
|
89
|
+
// rounded-sm → .cornerRadius(4)
|
|
90
|
+
// rounded-md → .cornerRadius(8)
|
|
91
|
+
// rounded-lg → .cornerRadius(12)
|
|
92
|
+
// rounded-xl → .cornerRadius(16)
|
|
93
|
+
// rounded-full → .clipShape(Capsule()) or .cornerRadius(9999)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Content elements
|
|
97
|
+
|
|
98
|
+
| HTML | → SwiftUI |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `<p>`, `<span>`, text | `Text("content")` |
|
|
101
|
+
| `<h1>` | `Text("title").font(.largeTitle).fontWeight(.bold)` |
|
|
102
|
+
| `<h2>` | `Text("title").font(.title2).fontWeight(.semibold)` |
|
|
103
|
+
| `<h3>` | `Text("title").font(.headline)` |
|
|
104
|
+
| `<p>` body | `Text("body").font(.body)` |
|
|
105
|
+
| `<small>` / caption | `Text("caption").font(.caption).foregroundStyle(.secondary)` |
|
|
106
|
+
| `<img>` | `AsyncImage(url: URL(string: "..."))` for remote, `Image("name")` for asset |
|
|
107
|
+
| `<button>` primary | `Button("Label") { action() }.buttonStyle(.borderedProminent)` |
|
|
108
|
+
| `<button>` secondary | `Button("Label") { action() }.buttonStyle(.bordered)` |
|
|
109
|
+
| `<button>` ghost/text | `Button("Label") { action() }.buttonStyle(.plain)` |
|
|
110
|
+
| `<input type="text">` | `TextField("Placeholder", text: $binding)` |
|
|
111
|
+
| `<input type="password">` | `SecureField("Password", text: $binding)` |
|
|
112
|
+
| `<select>` | `Picker("Label", selection: $binding) { ForEach(...) }` |
|
|
113
|
+
| `<toggle>` / checkbox | `Toggle("Label", isOn: $binding)` |
|
|
114
|
+
| Icon-only button | `Button { action() } label: { Image(systemName: "xmark") }` |
|
|
115
|
+
|
|
116
|
+
### Navigation patterns
|
|
117
|
+
|
|
118
|
+
| Pattern | SwiftUI implementation |
|
|
119
|
+
|---------|----------------------|
|
|
120
|
+
| Bottom tab bar | `TabView` with `tabItem { Label("Home", systemImage: "house") }` |
|
|
121
|
+
| Stack navigation | `NavigationStack { ... NavigationLink(destination: ...) }` |
|
|
122
|
+
| Modal / sheet | `.sheet(isPresented: $showModal) { ModalView() }` |
|
|
123
|
+
| Full screen modal | `.fullScreenCover(isPresented: $show) { FullView() }` |
|
|
124
|
+
| Back navigation | Automatic with `NavigationStack` |
|
|
125
|
+
| Action sheet | `.confirmationDialog("Title", isPresented: $show) { ... }` |
|
|
126
|
+
|
|
127
|
+
## Step 4: Design tokens in SwiftUI
|
|
128
|
+
|
|
129
|
+
### Color extension (semantic tokens)
|
|
130
|
+
|
|
131
|
+
```swift
|
|
132
|
+
// Theme/Color+App.swift
|
|
133
|
+
|
|
134
|
+
import SwiftUI
|
|
135
|
+
|
|
136
|
+
extension Color {
|
|
137
|
+
// Extract these hex values from the Stitch HTML's tailwind.config
|
|
138
|
+
|
|
139
|
+
// Backgrounds
|
|
140
|
+
static let appBackground = Color("AppBackground") // Asset catalog
|
|
141
|
+
static let appSurface = Color("AppSurface")
|
|
142
|
+
|
|
143
|
+
// Brand
|
|
144
|
+
static let appPrimary = Color("AppPrimary")
|
|
145
|
+
static let appPrimaryFg = Color("AppPrimaryForeground")
|
|
146
|
+
|
|
147
|
+
// Text
|
|
148
|
+
static let appText = Color("AppText")
|
|
149
|
+
static let appTextMuted = Color("AppTextMuted")
|
|
150
|
+
|
|
151
|
+
// Borders
|
|
152
|
+
static let appBorder = Color("AppBorder")
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Color asset catalog (light + dark)
|
|
157
|
+
|
|
158
|
+
Create named Color Sets in `Assets.xcassets/Colors/`:
|
|
159
|
+
|
|
160
|
+
For each color (e.g., `AppPrimary`):
|
|
161
|
+
- **Any Appearance:** `#6366F1` (the light mode value)
|
|
162
|
+
- **Dark Appearance:** `#818CF8` (lighter shade for dark bg)
|
|
163
|
+
|
|
164
|
+
Alternatively, define programmatically (no asset catalog needed):
|
|
165
|
+
|
|
166
|
+
```swift
|
|
167
|
+
// Theme/ThemeTokens.swift
|
|
168
|
+
|
|
169
|
+
import SwiftUI
|
|
170
|
+
|
|
171
|
+
struct ThemeTokens {
|
|
172
|
+
let background: Color
|
|
173
|
+
let surface: Color
|
|
174
|
+
let primary: Color
|
|
175
|
+
let primaryFg: Color
|
|
176
|
+
let text: Color
|
|
177
|
+
let textMuted: Color
|
|
178
|
+
let border: Color
|
|
179
|
+
|
|
180
|
+
static let light = ThemeTokens(
|
|
181
|
+
background: Color(hex: "#FFFFFF"),
|
|
182
|
+
surface: Color(hex: "#F4F4F5"),
|
|
183
|
+
primary: Color(hex: "#6366F1"),
|
|
184
|
+
primaryFg: Color(hex: "#FFFFFF"),
|
|
185
|
+
text: Color(hex: "#09090B"),
|
|
186
|
+
textMuted: Color(hex: "#71717A"),
|
|
187
|
+
border: Color(hex: "#E4E4E7")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
static let dark = ThemeTokens(
|
|
191
|
+
background: Color(hex: "#09090B"),
|
|
192
|
+
surface: Color(hex: "#18181B"),
|
|
193
|
+
primary: Color(hex: "#818CF8"), // Lightened for dark bg
|
|
194
|
+
primaryFg: Color(hex: "#09090B"),
|
|
195
|
+
text: Color(hex: "#FAFAFA"),
|
|
196
|
+
textMuted: Color(hex: "#A1A1AA"),
|
|
197
|
+
border: Color(hex: "#27272A")
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Convenience: Color from hex string
|
|
202
|
+
extension Color {
|
|
203
|
+
init(hex: String) {
|
|
204
|
+
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
|
205
|
+
var int: UInt64 = 0
|
|
206
|
+
Scanner(string: hex).scanHexInt64(&int)
|
|
207
|
+
let r = Double((int & 0xFF0000) >> 16) / 255
|
|
208
|
+
let g = Double((int & 0x00FF00) >> 8) / 255
|
|
209
|
+
let b = Double(int & 0x0000FF) / 255
|
|
210
|
+
self.init(red: r, green: g, blue: b)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Environment-based theme access
|
|
216
|
+
|
|
217
|
+
```swift
|
|
218
|
+
// Anywhere in a view — automatic dark mode
|
|
219
|
+
@Environment(\.colorScheme) var colorScheme
|
|
220
|
+
|
|
221
|
+
var theme: ThemeTokens {
|
|
222
|
+
colorScheme == .dark ? .dark : .light
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Usage
|
|
226
|
+
Text("Hello")
|
|
227
|
+
.foregroundStyle(theme.text)
|
|
228
|
+
.background(theme.surface)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Step 5: Component template
|
|
232
|
+
|
|
233
|
+
```swift
|
|
234
|
+
// Views/Components/StitchComponentView.swift
|
|
235
|
+
|
|
236
|
+
import SwiftUI
|
|
237
|
+
|
|
238
|
+
/// StitchComponent — [describe purpose in one sentence]
|
|
239
|
+
struct StitchComponentView: View {
|
|
240
|
+
// MARK: - Properties (equivalent to props)
|
|
241
|
+
let title: String
|
|
242
|
+
var description: String = ""
|
|
243
|
+
var onAction: (() -> Void)? = nil
|
|
244
|
+
|
|
245
|
+
// MARK: - Environment
|
|
246
|
+
@Environment(\.colorScheme) private var colorScheme
|
|
247
|
+
|
|
248
|
+
private var theme: ThemeTokens {
|
|
249
|
+
colorScheme == .dark ? .dark : .light
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// MARK: - State
|
|
253
|
+
@State private var isPressed = false
|
|
254
|
+
|
|
255
|
+
// MARK: - Body
|
|
256
|
+
var body: some View {
|
|
257
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
258
|
+
Text(title)
|
|
259
|
+
.font(.headline)
|
|
260
|
+
.foregroundStyle(theme.text)
|
|
261
|
+
|
|
262
|
+
if !description.isEmpty {
|
|
263
|
+
Text(description)
|
|
264
|
+
.font(.subheadline)
|
|
265
|
+
.foregroundStyle(theme.textMuted)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if let action = onAction {
|
|
269
|
+
Button("Action", action: action)
|
|
270
|
+
.buttonStyle(.borderedProminent)
|
|
271
|
+
.tint(theme.primary)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
.padding(16)
|
|
275
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
276
|
+
.background(theme.surface)
|
|
277
|
+
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
278
|
+
.overlay(
|
|
279
|
+
RoundedRectangle(cornerRadius: 12)
|
|
280
|
+
.stroke(theme.border, lineWidth: 1)
|
|
281
|
+
)
|
|
282
|
+
// Minimum touch target — 44pt Apple HIG requirement
|
|
283
|
+
.frame(minHeight: 44)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// MARK: - Preview
|
|
288
|
+
#Preview {
|
|
289
|
+
VStack(spacing: 16) {
|
|
290
|
+
StitchComponentView(title: "Card Title", description: "Supporting text")
|
|
291
|
+
StitchComponentView(title: "With Action", description: "Tap the button", onAction: {})
|
|
292
|
+
}
|
|
293
|
+
.padding()
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Step 6: Main app entry point
|
|
298
|
+
|
|
299
|
+
```swift
|
|
300
|
+
// MyApp.swift
|
|
301
|
+
import SwiftUI
|
|
302
|
+
|
|
303
|
+
@main
|
|
304
|
+
struct MyApp: App {
|
|
305
|
+
var body: some Scene {
|
|
306
|
+
WindowGroup {
|
|
307
|
+
ContentView()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ContentView.swift — root with TabView
|
|
313
|
+
struct ContentView: View {
|
|
314
|
+
var body: some View {
|
|
315
|
+
TabView {
|
|
316
|
+
HomeView()
|
|
317
|
+
.tabItem {
|
|
318
|
+
Label("Home", systemImage: "house")
|
|
319
|
+
}
|
|
320
|
+
ProfileView()
|
|
321
|
+
.tabItem {
|
|
322
|
+
Label("Profile", systemImage: "person")
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Step 7: Accessibility in SwiftUI
|
|
330
|
+
|
|
331
|
+
SwiftUI handles much of this automatically, but always verify:
|
|
332
|
+
|
|
333
|
+
```swift
|
|
334
|
+
// Image accessibility
|
|
335
|
+
Image("hero-photo")
|
|
336
|
+
.accessibilityLabel("Team collaborating in a modern office")
|
|
337
|
+
|
|
338
|
+
// Decorative images (screen reader skips)
|
|
339
|
+
Image(decorative: "background-pattern")
|
|
340
|
+
|
|
341
|
+
// Buttons — label is automatic if using Text inside
|
|
342
|
+
Button("Sign In") { ... } // VoiceOver reads "Sign In, button"
|
|
343
|
+
|
|
344
|
+
// Custom accessibility label when button label is ambiguous
|
|
345
|
+
Button { deleteItem() } label: {
|
|
346
|
+
Image(systemName: "trash")
|
|
347
|
+
}
|
|
348
|
+
.accessibilityLabel("Delete item")
|
|
349
|
+
|
|
350
|
+
// Group elements (treats as single unit)
|
|
351
|
+
VStack {
|
|
352
|
+
Text("Sarah Johnson")
|
|
353
|
+
Text("Product Designer")
|
|
354
|
+
}
|
|
355
|
+
.accessibilityElement(children: .combine)
|
|
356
|
+
|
|
357
|
+
// Dynamic type support — always use semantic fonts
|
|
358
|
+
Text("Headline")
|
|
359
|
+
.font(.headline) // ✅ Scales with user's text size
|
|
360
|
+
// NOT .font(.system(size: 17, weight: .semibold)) // ❌ Fixed size
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Step 8: SwiftUI animations
|
|
364
|
+
|
|
365
|
+
SwiftUI has excellent built-in animations — use them for the micro-interactions:
|
|
366
|
+
|
|
367
|
+
```swift
|
|
368
|
+
// Button press spring
|
|
369
|
+
Button(action: primaryAction) {
|
|
370
|
+
Text("Get Started")
|
|
371
|
+
.padding(.horizontal, 24)
|
|
372
|
+
.padding(.vertical, 14)
|
|
373
|
+
.background(theme.primary)
|
|
374
|
+
.foregroundStyle(theme.primaryFg)
|
|
375
|
+
.clipShape(Capsule())
|
|
376
|
+
.scaleEffect(isPressed ? 0.96 : 1.0)
|
|
377
|
+
.animation(.spring(response: 0.2, dampingFraction: 0.6), value: isPressed)
|
|
378
|
+
}
|
|
379
|
+
.simultaneousGesture(
|
|
380
|
+
DragGesture(minimumDistance: 0)
|
|
381
|
+
.onChanged { _ in isPressed = true }
|
|
382
|
+
.onEnded { _ in isPressed = false }
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
// Card appear transition
|
|
386
|
+
VStack { /* card content */ }
|
|
387
|
+
.transition(.move(edge: .bottom).combined(with: .opacity))
|
|
388
|
+
|
|
389
|
+
// Respect reduced motion
|
|
390
|
+
@Environment(\.accessibilityReduceMotion) var reduceMotion
|
|
391
|
+
|
|
392
|
+
var animation: Animation {
|
|
393
|
+
reduceMotion ? .none : .spring(response: 0.3, dampingFraction: 0.7)
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Execution steps
|
|
398
|
+
|
|
399
|
+
1. **Verify** the Stitch design uses `deviceType: MOBILE`
|
|
400
|
+
2. **Create Xcode project** — File → New → App, SwiftUI interface, Swift language
|
|
401
|
+
3. **Data layer** — create `Models/MockData.swift` from static content in the design
|
|
402
|
+
4. **Theme** — create `Theme/ThemeTokens.swift` with extracted hex values, and `Color+App.swift`
|
|
403
|
+
5. **Components** — convert the Stitch HTML sections to SwiftUI views, file by file
|
|
404
|
+
6. **Navigation** — wire up `TabView` (tab bar) or `NavigationStack` (stack)
|
|
405
|
+
7. **Build and run** — in Xcode, Cmd+R. Test on both light and dark mode (⌃⌘A toggles appearance in Simulator)
|
|
406
|
+
|
|
407
|
+
## Troubleshooting
|
|
408
|
+
|
|
409
|
+
| Issue | Fix |
|
|
410
|
+
|-------|-----|
|
|
411
|
+
| View overflows screen | Add `.frame(maxWidth: .infinity)` + parent `ScrollView` |
|
|
412
|
+
| Text truncates unexpectedly | Add `.lineLimit(nil)` or `.fixedSize(horizontal: false, vertical: true)` |
|
|
413
|
+
| Color looks wrong in dark mode | Ensure the Color Set in Assets.xcassets has a Dark appearance set |
|
|
414
|
+
| Image not loading | For `AsyncImage`, check URL is valid. For local images, file must be in Assets.xcassets |
|
|
415
|
+
| TabView items don't show label | Content must be directly inside `.tabItem { }` — no wrapping views |
|
|
416
|
+
| Sheet not dismissible | Add `@Environment(\.dismiss) var dismiss` and call `dismiss()` in the sheet |
|
|
417
|
+
| Preview crashes | Check `#Preview` has valid mock data — never optional-unwrap without fallback |
|
|
418
|
+
|
|
419
|
+
## References
|
|
420
|
+
|
|
421
|
+
- `resources/component-template.swift` — Boilerplate SwiftUI view
|
|
422
|
+
- `resources/layout-mapping.md` — Full HTML/CSS → SwiftUI reference
|
|
423
|
+
- `resources/architecture-checklist.md` — Pre-ship checklist
|
|
424
|
+
- `scripts/fetch-stitch.sh` — Reliable GCS HTML downloader
|