@mr.dj2u/knowledge 0.1.7 → 0.1.9
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/dist/content/checklists/push-merge-loop.md +17 -17
- package/dist/content/checklists/unified-agent-bundle-validation.md +9 -9
- package/dist/content/examples/push-merge-loop.md +14 -14
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -8
- package/dist/content/guides/post-create-onboarding.md +150 -140
- package/dist/content/patterns/api/api-routes.md +313 -313
- package/dist/content/patterns/api/error-handling.md +310 -310
- package/dist/content/patterns/database/drizzle-schema.md +279 -279
- package/dist/content/patterns/database/migrations.md +364 -364
- package/dist/content/patterns/database/query-organization.md +536 -536
- package/dist/content/patterns/database/relations.md +449 -449
- package/dist/content/patterns/deployment/build-configuration.md +440 -440
- package/dist/content/patterns/deployment/ci-cd-patterns.md +447 -447
- package/dist/content/patterns/deployment/environment-config.md +379 -379
- package/dist/content/patterns/deployment/hosting-setup.md +424 -424
- package/dist/content/patterns/project/configuration-patterns.md +459 -459
- package/dist/content/patterns/project/documentation-org.md +506 -506
- package/dist/content/patterns/project/folder-structure.md +397 -397
- package/dist/content/patterns/project/library-exports.md +464 -464
- package/dist/content/patterns/project/monorepo-structure.md +500 -500
- package/dist/content/patterns/routing/dynamic-routes.md +220 -220
- package/dist/content/patterns/routing/file-based-routing.md +185 -185
- package/dist/content/patterns/routing/route-groups.md +428 -428
- package/dist/content/patterns/state/persistence-middleware.md +520 -520
- package/dist/content/patterns/state/selector-hooks.md +537 -537
- package/dist/content/patterns/state/store-organization.md +538 -538
- package/dist/content/patterns/state/zustand-patterns.md +347 -347
- package/dist/content/patterns/styling/component-styling.md +467 -467
- package/dist/content/patterns/styling/responsive-patterns.md +397 -397
- package/dist/content/patterns/styling/theme-configuration.md +425 -425
- package/dist/content/patterns/styling/uniwind-setup.md +411 -411
- package/dist/content/prompts/continue-development.md +41 -35
- package/dist/content/prompts/create-expo-super-stack.md +4 -1
- package/dist/content/prompts/fix-seo.md +29 -29
- package/dist/content/prompts/onboard-new-expo-app.md +11 -11
- package/dist/content/prompts/prepare-deploy.md +29 -29
- package/dist/content/prompts/project-research-plan.md +29 -29
- package/dist/content/prompts/push-merge-loop.md +25 -25
- package/dist/content/prompts/retrospective-project-onboarding.md +23 -0
- package/dist/content/prompts/review-expo-project.md +29 -29
- package/dist/content/prompts/run-doctor.md +38 -38
- package/dist/content/prompts/wrap-up.md +70 -67
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -29
- package/dist/content/reference/doctor-dogfood.md +1 -1
- package/dist/content/reference/mcp-sdk-transport.md +30 -30
- package/dist/content/reference/reference-repo-evacuation.md +31 -31
- package/dist/content/resource-index.json +1 -0
- package/dist/content/rules/env-hygiene.md +10 -0
- package/dist/content/rules/ssr-safety.md +7 -0
- package/dist/content/skills/api-routes.md +34 -33
- package/dist/content/skills/continue-development.md +49 -32
- package/dist/content/skills/debugging.md +32 -32
- package/dist/content/skills/deployment.md +32 -32
- package/dist/content/skills/dev-server-management.md +32 -32
- package/dist/content/skills/env-vars.md +32 -32
- package/dist/content/skills/expo-router-architecture.md +34 -33
- package/dist/content/skills/expo-ssr-safety.md +32 -32
- package/dist/content/skills/plugin-creation.md +41 -41
- package/dist/content/skills/production-server-patterns.md +31 -31
- package/dist/content/skills/project-onboarding.md +35 -31
- package/dist/content/skills/research-plan-intake.md +32 -32
- package/dist/content/skills/seo-metadata.md +31 -31
- package/dist/content/skills/super-stack-startup.md +38 -34
- package/dist/content/skills/uniwind-theming.md +32 -32
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +11 -0
- package/dist/prompts/index.js.map +1 -1
- package/package.json +7 -1
|
@@ -1,412 +1,412 @@
|
|
|
1
|
-
# Uniwind & Tailwind v4 Setup
|
|
2
|
-
|
|
3
|
-
## Description
|
|
4
|
-
|
|
5
|
-
Uniwind provides Tailwind v4 CSS integration for React Native with build-time style computation and Metro bundler configuration. Design tokens (colors, spacing, typography) live in CSS using `@layer theme` with `@variant` for light/dark modes, enabling cross-platform responsive styling without separate theme providers.
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
**Use Uniwind** for:
|
|
10
|
-
- ✅ React Native projects (iOS, Android, Web)
|
|
11
|
-
- ✅ Responsive design with breakpoints
|
|
12
|
-
- ✅ Dark mode support with CSS variables
|
|
13
|
-
- ✅ Tailwind utility-first styling approach
|
|
14
|
-
- ✅ Projects using Tailwind v4+
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
### 1. Install Dependencies
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install uniwind
|
|
22
|
-
npm install --save-dev tailwindcss@^4 @tailwindcss/postcss
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### 2. Configure Metro Bundler
|
|
26
|
-
|
|
27
|
-
```javascript
|
|
28
|
-
// File: metro.config.js
|
|
29
|
-
const { getDefaultConfig } = require('@react-native/metro-config');
|
|
30
|
-
const { withUniwindConfig } = require('uniwind/metro');
|
|
31
|
-
|
|
32
|
-
const config = getDefaultConfig(__dirname);
|
|
33
|
-
|
|
34
|
-
module.exports = withUniwindConfig(config, {
|
|
35
|
-
cssEntryFile: './src/global.css',
|
|
36
|
-
// Optional: set rem polyfill if needed (default 16px)
|
|
37
|
-
// polyfills: { rem: 16 }
|
|
38
|
-
});
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### 3. Add PostCSS Configuration
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
// File: postcss.config.js
|
|
45
|
-
module.exports = {
|
|
46
|
-
plugins: {
|
|
47
|
-
'postcss-import': {},
|
|
48
|
-
'tailwindcss/nesting': {},
|
|
49
|
-
tailwindcss: {},
|
|
50
|
-
autoprefixer: {},
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Code Example
|
|
56
|
-
|
|
57
|
-
### Global CSS with Theme Tokens
|
|
58
|
-
|
|
59
|
-
```css
|
|
60
|
-
/* File: src/global.css */
|
|
61
|
-
@import 'tailwindcss';
|
|
62
|
-
@import 'uniwind';
|
|
63
|
-
|
|
64
|
-
/* Define theme tokens as CSS variables */
|
|
65
|
-
@layer theme {
|
|
66
|
-
:root {
|
|
67
|
-
/* Light mode (default) */
|
|
68
|
-
@variant light {
|
|
69
|
-
/* Primary colors */
|
|
70
|
-
--color-primary: #ef5350;
|
|
71
|
-
--color-primary-50: #ffebee;
|
|
72
|
-
--color-primary-100: #ffcdd2;
|
|
73
|
-
--color-primary-500: #ef5350;
|
|
74
|
-
--color-primary-700: #c62828;
|
|
75
|
-
--color-primary-900: #b71c1c;
|
|
76
|
-
|
|
77
|
-
/* Secondary colors */
|
|
78
|
-
--color-secondary: #29b6f6;
|
|
79
|
-
--color-secondary-500: #29b6f6;
|
|
80
|
-
--color-secondary-700: #0277bd;
|
|
81
|
-
|
|
82
|
-
/* Neutral colors */
|
|
83
|
-
--color-background: #fafafa;
|
|
84
|
-
--color-surface: #ffffff;
|
|
85
|
-
--color-typography: #212121;
|
|
86
|
-
--color-border: #e0e0e0;
|
|
87
|
-
--color-divider: #bdbdbd;
|
|
88
|
-
|
|
89
|
-
/* Status colors */
|
|
90
|
-
--color-success: #4caf50;
|
|
91
|
-
--color-warning: #ff9800;
|
|
92
|
-
--color-error: #f44336;
|
|
93
|
-
--color-info: #2196f3;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* Dark mode */
|
|
97
|
-
@variant dark {
|
|
98
|
-
--color-primary: #ef5350;
|
|
99
|
-
--color-primary-50: #ffebee;
|
|
100
|
-
--color-primary-100: #ffcdd2;
|
|
101
|
-
--color-primary-500: #ef5350;
|
|
102
|
-
--color-primary-700: #d32f2f;
|
|
103
|
-
--color-primary-900: #b71c1c;
|
|
104
|
-
|
|
105
|
-
--color-secondary: #29b6f6;
|
|
106
|
-
--color-secondary-500: #29b6f6;
|
|
107
|
-
--color-secondary-700: #0288d1;
|
|
108
|
-
|
|
109
|
-
--color-background: #121212;
|
|
110
|
-
--color-surface: #1e1e1e;
|
|
111
|
-
--color-typography: #ffffff;
|
|
112
|
-
--color-border: #424242;
|
|
113
|
-
--color-divider: #616161;
|
|
114
|
-
|
|
115
|
-
--color-success: #66bb6a;
|
|
116
|
-
--color-warning: #ffb74d;
|
|
117
|
-
--color-error: #ef5350;
|
|
118
|
-
--color-info: #64b5f6;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* Spacing scale */
|
|
122
|
-
--spacing-0: 0;
|
|
123
|
-
--spacing-1: 4px;
|
|
124
|
-
--spacing-2: 8px;
|
|
125
|
-
--spacing-3: 12px;
|
|
126
|
-
--spacing-4: 16px;
|
|
127
|
-
--spacing-6: 24px;
|
|
128
|
-
--spacing-8: 32px;
|
|
129
|
-
|
|
130
|
-
/* Typography */
|
|
131
|
-
--font-sans: 'Roboto', sans-serif;
|
|
132
|
-
--font-mono: 'RobotoMono', monospace;
|
|
133
|
-
--font-display: 'Modak', serif;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* Define typography utilities */
|
|
138
|
-
@layer utilities {
|
|
139
|
-
.typography-header {
|
|
140
|
-
@apply text-4xl font-bold tracking-tight;
|
|
141
|
-
font-family: var(--font-display);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.typography-subheader {
|
|
145
|
-
@apply text-2xl font-bold;
|
|
146
|
-
font-family: var(--font-sans);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.typography-body {
|
|
150
|
-
@apply text-base leading-6;
|
|
151
|
-
font-family: var(--font-sans);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.typography-caption {
|
|
155
|
-
@apply text-sm text-gray-500;
|
|
156
|
-
font-family: var(--font-sans);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/* Global style reset */
|
|
161
|
-
@layer base {
|
|
162
|
-
* {
|
|
163
|
-
@apply margin-0 padding-0;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
body {
|
|
167
|
-
@apply bg-[var(--color-background)] text-[var(--color-typography)];
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
**From:** expo-super-template/global.css, DJsPortfolio/global.css
|
|
173
|
-
|
|
174
|
-
### Component Styling with Uniwind
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
// File: src/components/Button.tsx
|
|
178
|
-
import { Pressable, Text } from 'react-native';
|
|
179
|
-
import { cn } from '@/utils/cn'; // tailwind-merge helper
|
|
180
|
-
|
|
181
|
-
interface ButtonProps {
|
|
182
|
-
title: string;
|
|
183
|
-
onPress: () => void;
|
|
184
|
-
variant?: 'primary' | 'secondary' | 'ghost';
|
|
185
|
-
disabled?: boolean;
|
|
186
|
-
fullWidth?: boolean;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function Button({
|
|
190
|
-
title,
|
|
191
|
-
onPress,
|
|
192
|
-
variant = 'primary',
|
|
193
|
-
disabled = false,
|
|
194
|
-
fullWidth = false,
|
|
195
|
-
}: ButtonProps) {
|
|
196
|
-
const baseStyles = 'px-4 py-3 rounded-lg active:opacity-80 disabled:opacity-50';
|
|
197
|
-
|
|
198
|
-
const variantStyles = {
|
|
199
|
-
primary: 'bg-[var(--color-primary)] text-white',
|
|
200
|
-
secondary: 'bg-[var(--color-secondary)] text-white',
|
|
201
|
-
ghost: 'bg-transparent border border-[var(--color-border)]',
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const containerStyles = cn(
|
|
205
|
-
baseStyles,
|
|
206
|
-
variantStyles[variant],
|
|
207
|
-
fullWidth && 'w-full'
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<Pressable
|
|
212
|
-
className={containerStyles}
|
|
213
|
-
onPress={onPress}
|
|
214
|
-
disabled={disabled}
|
|
215
|
-
>
|
|
216
|
-
<Text className="text-center font-semibold">
|
|
217
|
-
{title}
|
|
218
|
-
</Text>
|
|
219
|
-
</Pressable>
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Dark Mode Support
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
// File: src/hooks/useColorScheme.ts
|
|
228
|
-
import { useColorScheme as useNativeColorScheme } from 'react-native';
|
|
229
|
-
|
|
230
|
-
export function useColorScheme() {
|
|
231
|
-
const scheme = useNativeColorScheme();
|
|
232
|
-
return scheme || 'light';
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// File: src/components/ThemedView.tsx
|
|
236
|
-
import { View, ViewProps } from 'react-native';
|
|
237
|
-
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
238
|
-
|
|
239
|
-
interface ThemedViewProps extends ViewProps {
|
|
240
|
-
lightBg?: string;
|
|
241
|
-
darkBg?: string;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export function ThemedView({
|
|
245
|
-
lightBg = 'bg-white',
|
|
246
|
-
darkBg = 'dark:bg-black',
|
|
247
|
-
className,
|
|
248
|
-
...props
|
|
249
|
-
}: ThemedViewProps) {
|
|
250
|
-
const colorScheme = useColorScheme();
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<View
|
|
254
|
-
className={`${lightBg} ${darkBg} ${className || ''}`}
|
|
255
|
-
{...props}
|
|
256
|
-
/>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Usage
|
|
261
|
-
function Screen() {
|
|
262
|
-
return (
|
|
263
|
-
<ThemedView
|
|
264
|
-
lightBg="bg-white"
|
|
265
|
-
darkBg="dark:bg-gray-900"
|
|
266
|
-
className="flex-1"
|
|
267
|
-
>
|
|
268
|
-
{/* Content */}
|
|
269
|
-
</ThemedView>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Responsive Breakpoints
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
// File: src/components/ResponsiveGrid.tsx
|
|
278
|
-
import { View } from 'react-native';
|
|
279
|
-
|
|
280
|
-
export function ResponsiveGrid({ children }) {
|
|
281
|
-
return (
|
|
282
|
-
<View className="flex-row flex-wrap gap-4 w-full">
|
|
283
|
-
{/*
|
|
284
|
-
Mobile (default): 1 column
|
|
285
|
-
md (768px): 2 columns
|
|
286
|
-
lg (1024px): 3 columns
|
|
287
|
-
*/}
|
|
288
|
-
<View className="w-full md:w-1/2 lg:w-1/3">
|
|
289
|
-
{children}
|
|
290
|
-
</View>
|
|
291
|
-
</View>
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## Configuration
|
|
297
|
-
|
|
298
|
-
### Tailwind Config
|
|
299
|
-
|
|
300
|
-
```javascript
|
|
301
|
-
// File: tailwind.config.js
|
|
302
|
-
/** @type {import('tailwindcss').Config} */
|
|
303
|
-
module.exports = {
|
|
304
|
-
content: [
|
|
305
|
-
'./src/**/*.{js,jsx,ts,tsx}',
|
|
306
|
-
'./App.{js,jsx,ts,tsx}',
|
|
307
|
-
],
|
|
308
|
-
theme: {
|
|
309
|
-
extend: {
|
|
310
|
-
fontFamily: {
|
|
311
|
-
sans: ['Roboto', 'system-ui', 'sans-serif'],
|
|
312
|
-
mono: ['RobotoMono', 'monospace'],
|
|
313
|
-
display: ['Modak', 'serif'],
|
|
314
|
-
},
|
|
315
|
-
spacing: {
|
|
316
|
-
safe: 'env(safe-area-inset-bottom)',
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
plugins: [],
|
|
321
|
-
};
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
## Best Practices
|
|
325
|
-
|
|
326
|
-
### ✅ DO
|
|
327
|
-
|
|
328
|
-
1. **Define all tokens in CSS** using `@layer theme`
|
|
329
|
-
```css
|
|
330
|
-
@layer theme {
|
|
331
|
-
:root {
|
|
332
|
-
--color-primary: #ef5350;
|
|
333
|
-
--color-surface: #ffffff;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
2. **Use CSS variables** in components
|
|
339
|
-
```tsx
|
|
340
|
-
<View className="bg-[var(--color-primary)]" />
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
3. **Support dark mode** with `@variant dark`
|
|
344
|
-
```css
|
|
345
|
-
@variant dark {
|
|
346
|
-
--color-background: #121212;
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
4. **Use `cn()` with tailwind-merge** for conditional classes
|
|
351
|
-
```tsx
|
|
352
|
-
className={cn('bg-red-500', isActive && 'bg-blue-500')}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
5. **Keep Tailwind config minimal** — use CSS variables instead
|
|
356
|
-
```javascript
|
|
357
|
-
// ✅ MINIMAL config
|
|
358
|
-
module.exports = {
|
|
359
|
-
content: ['./src/**/*.tsx'],
|
|
360
|
-
theme: { extend: { fontFamily: { ... } } }
|
|
361
|
-
};
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### ❌ DON'T
|
|
365
|
-
|
|
366
|
-
1. **Don't define colors in tailwind.config.js**
|
|
367
|
-
```javascript
|
|
368
|
-
// ❌ BAD - hard to theme dynamically
|
|
369
|
-
colors: { primary: '#ef5350' }
|
|
370
|
-
|
|
371
|
-
// ✅ GOOD - use CSS variables
|
|
372
|
-
// In global.css: --color-primary: #ef5350
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
2. **Don't forget `prepare: false`** in Metro config
|
|
376
|
-
```javascript
|
|
377
|
-
// ❌ BAD - required for Uniwind + Metro
|
|
378
|
-
// No withUniwindConfig
|
|
379
|
-
|
|
380
|
-
// ✅ GOOD
|
|
381
|
-
module.exports = withUniwindConfig(config, {
|
|
382
|
-
cssEntryFile: './src/global.css',
|
|
383
|
-
});
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
3. **Don't use `StyleSheet.create()`** for layout/spacing
|
|
387
|
-
```typescript
|
|
388
|
-
// ❌ BAD - verbose and hard to maintain
|
|
389
|
-
const styles = StyleSheet.create({
|
|
390
|
-
container: { flex: 1, padding: 16, backgroundColor: '#fff' }
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// ✅ GOOD - use Tailwind classes
|
|
394
|
-
<View className="flex-1 p-4 bg-white" />
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
4. **Don't forget to import global.css** in app root
|
|
398
|
-
```typescript
|
|
399
|
-
// File: src/app/_layout.tsx
|
|
400
|
-
import '@/global.css'; // ← Required for Uniwind to work
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
## Related Patterns
|
|
404
|
-
|
|
405
|
-
- [Theme Configuration](./theme-configuration.md) — Advanced theming patterns
|
|
406
|
-
- [Responsive Patterns](./responsive-patterns.md) — Mobile-first responsive design
|
|
407
|
-
- [Component Styling](./component-styling.md) — Component-specific styling
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
*Pattern extracted from production repositories: expo-super-template, DJsPortfolio, not-hot-dog*
|
|
1
|
+
# Uniwind & Tailwind v4 Setup
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Uniwind provides Tailwind v4 CSS integration for React Native with build-time style computation and Metro bundler configuration. Design tokens (colors, spacing, typography) live in CSS using `@layer theme` with `@variant` for light/dark modes, enabling cross-platform responsive styling without separate theme providers.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Use Uniwind** for:
|
|
10
|
+
- ✅ React Native projects (iOS, Android, Web)
|
|
11
|
+
- ✅ Responsive design with breakpoints
|
|
12
|
+
- ✅ Dark mode support with CSS variables
|
|
13
|
+
- ✅ Tailwind utility-first styling approach
|
|
14
|
+
- ✅ Projects using Tailwind v4+
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### 1. Install Dependencies
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install uniwind
|
|
22
|
+
npm install --save-dev tailwindcss@^4 @tailwindcss/postcss
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Configure Metro Bundler
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// File: metro.config.js
|
|
29
|
+
const { getDefaultConfig } = require('@react-native/metro-config');
|
|
30
|
+
const { withUniwindConfig } = require('uniwind/metro');
|
|
31
|
+
|
|
32
|
+
const config = getDefaultConfig(__dirname);
|
|
33
|
+
|
|
34
|
+
module.exports = withUniwindConfig(config, {
|
|
35
|
+
cssEntryFile: './src/global.css',
|
|
36
|
+
// Optional: set rem polyfill if needed (default 16px)
|
|
37
|
+
// polyfills: { rem: 16 }
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Add PostCSS Configuration
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// File: postcss.config.js
|
|
45
|
+
module.exports = {
|
|
46
|
+
plugins: {
|
|
47
|
+
'postcss-import': {},
|
|
48
|
+
'tailwindcss/nesting': {},
|
|
49
|
+
tailwindcss: {},
|
|
50
|
+
autoprefixer: {},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Code Example
|
|
56
|
+
|
|
57
|
+
### Global CSS with Theme Tokens
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
/* File: src/global.css */
|
|
61
|
+
@import 'tailwindcss';
|
|
62
|
+
@import 'uniwind';
|
|
63
|
+
|
|
64
|
+
/* Define theme tokens as CSS variables */
|
|
65
|
+
@layer theme {
|
|
66
|
+
:root {
|
|
67
|
+
/* Light mode (default) */
|
|
68
|
+
@variant light {
|
|
69
|
+
/* Primary colors */
|
|
70
|
+
--color-primary: #ef5350;
|
|
71
|
+
--color-primary-50: #ffebee;
|
|
72
|
+
--color-primary-100: #ffcdd2;
|
|
73
|
+
--color-primary-500: #ef5350;
|
|
74
|
+
--color-primary-700: #c62828;
|
|
75
|
+
--color-primary-900: #b71c1c;
|
|
76
|
+
|
|
77
|
+
/* Secondary colors */
|
|
78
|
+
--color-secondary: #29b6f6;
|
|
79
|
+
--color-secondary-500: #29b6f6;
|
|
80
|
+
--color-secondary-700: #0277bd;
|
|
81
|
+
|
|
82
|
+
/* Neutral colors */
|
|
83
|
+
--color-background: #fafafa;
|
|
84
|
+
--color-surface: #ffffff;
|
|
85
|
+
--color-typography: #212121;
|
|
86
|
+
--color-border: #e0e0e0;
|
|
87
|
+
--color-divider: #bdbdbd;
|
|
88
|
+
|
|
89
|
+
/* Status colors */
|
|
90
|
+
--color-success: #4caf50;
|
|
91
|
+
--color-warning: #ff9800;
|
|
92
|
+
--color-error: #f44336;
|
|
93
|
+
--color-info: #2196f3;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Dark mode */
|
|
97
|
+
@variant dark {
|
|
98
|
+
--color-primary: #ef5350;
|
|
99
|
+
--color-primary-50: #ffebee;
|
|
100
|
+
--color-primary-100: #ffcdd2;
|
|
101
|
+
--color-primary-500: #ef5350;
|
|
102
|
+
--color-primary-700: #d32f2f;
|
|
103
|
+
--color-primary-900: #b71c1c;
|
|
104
|
+
|
|
105
|
+
--color-secondary: #29b6f6;
|
|
106
|
+
--color-secondary-500: #29b6f6;
|
|
107
|
+
--color-secondary-700: #0288d1;
|
|
108
|
+
|
|
109
|
+
--color-background: #121212;
|
|
110
|
+
--color-surface: #1e1e1e;
|
|
111
|
+
--color-typography: #ffffff;
|
|
112
|
+
--color-border: #424242;
|
|
113
|
+
--color-divider: #616161;
|
|
114
|
+
|
|
115
|
+
--color-success: #66bb6a;
|
|
116
|
+
--color-warning: #ffb74d;
|
|
117
|
+
--color-error: #ef5350;
|
|
118
|
+
--color-info: #64b5f6;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Spacing scale */
|
|
122
|
+
--spacing-0: 0;
|
|
123
|
+
--spacing-1: 4px;
|
|
124
|
+
--spacing-2: 8px;
|
|
125
|
+
--spacing-3: 12px;
|
|
126
|
+
--spacing-4: 16px;
|
|
127
|
+
--spacing-6: 24px;
|
|
128
|
+
--spacing-8: 32px;
|
|
129
|
+
|
|
130
|
+
/* Typography */
|
|
131
|
+
--font-sans: 'Roboto', sans-serif;
|
|
132
|
+
--font-mono: 'RobotoMono', monospace;
|
|
133
|
+
--font-display: 'Modak', serif;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Define typography utilities */
|
|
138
|
+
@layer utilities {
|
|
139
|
+
.typography-header {
|
|
140
|
+
@apply text-4xl font-bold tracking-tight;
|
|
141
|
+
font-family: var(--font-display);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.typography-subheader {
|
|
145
|
+
@apply text-2xl font-bold;
|
|
146
|
+
font-family: var(--font-sans);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.typography-body {
|
|
150
|
+
@apply text-base leading-6;
|
|
151
|
+
font-family: var(--font-sans);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.typography-caption {
|
|
155
|
+
@apply text-sm text-gray-500;
|
|
156
|
+
font-family: var(--font-sans);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Global style reset */
|
|
161
|
+
@layer base {
|
|
162
|
+
* {
|
|
163
|
+
@apply margin-0 padding-0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
body {
|
|
167
|
+
@apply bg-[var(--color-background)] text-[var(--color-typography)];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**From:** expo-super-template/global.css, DJsPortfolio/global.css
|
|
173
|
+
|
|
174
|
+
### Component Styling with Uniwind
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// File: src/components/Button.tsx
|
|
178
|
+
import { Pressable, Text } from 'react-native';
|
|
179
|
+
import { cn } from '@/utils/cn'; // tailwind-merge helper
|
|
180
|
+
|
|
181
|
+
interface ButtonProps {
|
|
182
|
+
title: string;
|
|
183
|
+
onPress: () => void;
|
|
184
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
185
|
+
disabled?: boolean;
|
|
186
|
+
fullWidth?: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function Button({
|
|
190
|
+
title,
|
|
191
|
+
onPress,
|
|
192
|
+
variant = 'primary',
|
|
193
|
+
disabled = false,
|
|
194
|
+
fullWidth = false,
|
|
195
|
+
}: ButtonProps) {
|
|
196
|
+
const baseStyles = 'px-4 py-3 rounded-lg active:opacity-80 disabled:opacity-50';
|
|
197
|
+
|
|
198
|
+
const variantStyles = {
|
|
199
|
+
primary: 'bg-[var(--color-primary)] text-white',
|
|
200
|
+
secondary: 'bg-[var(--color-secondary)] text-white',
|
|
201
|
+
ghost: 'bg-transparent border border-[var(--color-border)]',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const containerStyles = cn(
|
|
205
|
+
baseStyles,
|
|
206
|
+
variantStyles[variant],
|
|
207
|
+
fullWidth && 'w-full'
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Pressable
|
|
212
|
+
className={containerStyles}
|
|
213
|
+
onPress={onPress}
|
|
214
|
+
disabled={disabled}
|
|
215
|
+
>
|
|
216
|
+
<Text className="text-center font-semibold">
|
|
217
|
+
{title}
|
|
218
|
+
</Text>
|
|
219
|
+
</Pressable>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Dark Mode Support
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// File: src/hooks/useColorScheme.ts
|
|
228
|
+
import { useColorScheme as useNativeColorScheme } from 'react-native';
|
|
229
|
+
|
|
230
|
+
export function useColorScheme() {
|
|
231
|
+
const scheme = useNativeColorScheme();
|
|
232
|
+
return scheme || 'light';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// File: src/components/ThemedView.tsx
|
|
236
|
+
import { View, ViewProps } from 'react-native';
|
|
237
|
+
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
238
|
+
|
|
239
|
+
interface ThemedViewProps extends ViewProps {
|
|
240
|
+
lightBg?: string;
|
|
241
|
+
darkBg?: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function ThemedView({
|
|
245
|
+
lightBg = 'bg-white',
|
|
246
|
+
darkBg = 'dark:bg-black',
|
|
247
|
+
className,
|
|
248
|
+
...props
|
|
249
|
+
}: ThemedViewProps) {
|
|
250
|
+
const colorScheme = useColorScheme();
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<View
|
|
254
|
+
className={`${lightBg} ${darkBg} ${className || ''}`}
|
|
255
|
+
{...props}
|
|
256
|
+
/>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Usage
|
|
261
|
+
function Screen() {
|
|
262
|
+
return (
|
|
263
|
+
<ThemedView
|
|
264
|
+
lightBg="bg-white"
|
|
265
|
+
darkBg="dark:bg-gray-900"
|
|
266
|
+
className="flex-1"
|
|
267
|
+
>
|
|
268
|
+
{/* Content */}
|
|
269
|
+
</ThemedView>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Responsive Breakpoints
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// File: src/components/ResponsiveGrid.tsx
|
|
278
|
+
import { View } from 'react-native';
|
|
279
|
+
|
|
280
|
+
export function ResponsiveGrid({ children }) {
|
|
281
|
+
return (
|
|
282
|
+
<View className="flex-row flex-wrap gap-4 w-full">
|
|
283
|
+
{/*
|
|
284
|
+
Mobile (default): 1 column
|
|
285
|
+
md (768px): 2 columns
|
|
286
|
+
lg (1024px): 3 columns
|
|
287
|
+
*/}
|
|
288
|
+
<View className="w-full md:w-1/2 lg:w-1/3">
|
|
289
|
+
{children}
|
|
290
|
+
</View>
|
|
291
|
+
</View>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Configuration
|
|
297
|
+
|
|
298
|
+
### Tailwind Config
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// File: tailwind.config.js
|
|
302
|
+
/** @type {import('tailwindcss').Config} */
|
|
303
|
+
module.exports = {
|
|
304
|
+
content: [
|
|
305
|
+
'./src/**/*.{js,jsx,ts,tsx}',
|
|
306
|
+
'./App.{js,jsx,ts,tsx}',
|
|
307
|
+
],
|
|
308
|
+
theme: {
|
|
309
|
+
extend: {
|
|
310
|
+
fontFamily: {
|
|
311
|
+
sans: ['Roboto', 'system-ui', 'sans-serif'],
|
|
312
|
+
mono: ['RobotoMono', 'monospace'],
|
|
313
|
+
display: ['Modak', 'serif'],
|
|
314
|
+
},
|
|
315
|
+
spacing: {
|
|
316
|
+
safe: 'env(safe-area-inset-bottom)',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
plugins: [],
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Best Practices
|
|
325
|
+
|
|
326
|
+
### ✅ DO
|
|
327
|
+
|
|
328
|
+
1. **Define all tokens in CSS** using `@layer theme`
|
|
329
|
+
```css
|
|
330
|
+
@layer theme {
|
|
331
|
+
:root {
|
|
332
|
+
--color-primary: #ef5350;
|
|
333
|
+
--color-surface: #ffffff;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
2. **Use CSS variables** in components
|
|
339
|
+
```tsx
|
|
340
|
+
<View className="bg-[var(--color-primary)]" />
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
3. **Support dark mode** with `@variant dark`
|
|
344
|
+
```css
|
|
345
|
+
@variant dark {
|
|
346
|
+
--color-background: #121212;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
4. **Use `cn()` with tailwind-merge** for conditional classes
|
|
351
|
+
```tsx
|
|
352
|
+
className={cn('bg-red-500', isActive && 'bg-blue-500')}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
5. **Keep Tailwind config minimal** — use CSS variables instead
|
|
356
|
+
```javascript
|
|
357
|
+
// ✅ MINIMAL config
|
|
358
|
+
module.exports = {
|
|
359
|
+
content: ['./src/**/*.tsx'],
|
|
360
|
+
theme: { extend: { fontFamily: { ... } } }
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### ❌ DON'T
|
|
365
|
+
|
|
366
|
+
1. **Don't define colors in tailwind.config.js**
|
|
367
|
+
```javascript
|
|
368
|
+
// ❌ BAD - hard to theme dynamically
|
|
369
|
+
colors: { primary: '#ef5350' }
|
|
370
|
+
|
|
371
|
+
// ✅ GOOD - use CSS variables
|
|
372
|
+
// In global.css: --color-primary: #ef5350
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
2. **Don't forget `prepare: false`** in Metro config
|
|
376
|
+
```javascript
|
|
377
|
+
// ❌ BAD - required for Uniwind + Metro
|
|
378
|
+
// No withUniwindConfig
|
|
379
|
+
|
|
380
|
+
// ✅ GOOD
|
|
381
|
+
module.exports = withUniwindConfig(config, {
|
|
382
|
+
cssEntryFile: './src/global.css',
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
3. **Don't use `StyleSheet.create()`** for layout/spacing
|
|
387
|
+
```typescript
|
|
388
|
+
// ❌ BAD - verbose and hard to maintain
|
|
389
|
+
const styles = StyleSheet.create({
|
|
390
|
+
container: { flex: 1, padding: 16, backgroundColor: '#fff' }
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ✅ GOOD - use Tailwind classes
|
|
394
|
+
<View className="flex-1 p-4 bg-white" />
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
4. **Don't forget to import global.css** in app root
|
|
398
|
+
```typescript
|
|
399
|
+
// File: src/app/_layout.tsx
|
|
400
|
+
import '@/global.css'; // ← Required for Uniwind to work
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Related Patterns
|
|
404
|
+
|
|
405
|
+
- [Theme Configuration](./theme-configuration.md) — Advanced theming patterns
|
|
406
|
+
- [Responsive Patterns](./responsive-patterns.md) — Mobile-first responsive design
|
|
407
|
+
- [Component Styling](./component-styling.md) — Component-specific styling
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
*Pattern extracted from production repositories: expo-super-template, DJsPortfolio, not-hot-dog*
|
|
412
412
|
*Files: expo-super-template/global.css, metro.config.js*
|