@mr.dj2u/knowledge 0.1.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/dist/content/checklists/ship-test-loop.md +16 -0
- package/dist/content/checklists/unified-agent-bundle-validation.md +8 -0
- package/dist/content/examples/ship-test-loop.md +13 -0
- package/dist/content/examples/unified-agent-bundle-bootstrap.md +8 -0
- package/dist/content/guides/animation-performance.md +30 -0
- package/dist/content/guides/post-create-onboarding.md +113 -0
- package/dist/content/patterns/api/api-routes.md +314 -0
- package/dist/content/patterns/api/error-handling.md +311 -0
- package/dist/content/patterns/database/drizzle-schema.md +280 -0
- package/dist/content/patterns/database/migrations.md +365 -0
- package/dist/content/patterns/database/query-organization.md +537 -0
- package/dist/content/patterns/database/relations.md +450 -0
- package/dist/content/patterns/deployment/build-configuration.md +452 -0
- package/dist/content/patterns/deployment/ci-cd-patterns.md +448 -0
- package/dist/content/patterns/deployment/environment-config.md +380 -0
- package/dist/content/patterns/deployment/hosting-setup.md +425 -0
- package/dist/content/patterns/project/configuration-patterns.md +459 -0
- package/dist/content/patterns/project/documentation-org.md +506 -0
- package/dist/content/patterns/project/folder-structure.md +398 -0
- package/dist/content/patterns/project/library-exports.md +465 -0
- package/dist/content/patterns/project/monorepo-structure.md +500 -0
- package/dist/content/patterns/routing/dynamic-routes.md +221 -0
- package/dist/content/patterns/routing/file-based-routing.md +186 -0
- package/dist/content/patterns/routing/route-groups.md +429 -0
- package/dist/content/patterns/state/persistence-middleware.md +521 -0
- package/dist/content/patterns/state/selector-hooks.md +538 -0
- package/dist/content/patterns/state/store-organization.md +539 -0
- package/dist/content/patterns/state/zustand-patterns.md +348 -0
- package/dist/content/patterns/styling/component-styling.md +468 -0
- package/dist/content/patterns/styling/responsive-patterns.md +398 -0
- package/dist/content/patterns/styling/theme-configuration.md +426 -0
- package/dist/content/patterns/styling/uniwind-setup.md +412 -0
- package/dist/content/prompts/continue-development.md +27 -0
- package/dist/content/prompts/create-expo-super-stack.md +29 -0
- package/dist/content/prompts/fix-seo.md +29 -0
- package/dist/content/prompts/onboard-new-expo-app.md +11 -0
- package/dist/content/prompts/prepare-deploy.md +29 -0
- package/dist/content/prompts/project-research-plan.md +29 -0
- package/dist/content/prompts/review-expo-project.md +29 -0
- package/dist/content/prompts/run-doctor.md +30 -0
- package/dist/content/prompts/ship-test-loop.md +24 -0
- package/dist/content/reference/create-expo-stack-uniwind.md +29 -0
- package/dist/content/reference/doctor-dogfood.md +42 -0
- package/dist/content/reference/mcp-sdk-transport.md +30 -0
- package/dist/content/reference/package-ci-patterns.md +24 -0
- package/dist/content/reference/reference-repo-evacuation.md +31 -0
- package/dist/content/resource-index.json +67 -0
- package/dist/content/rules/app-folder-architecture.md +13 -0
- package/dist/content/rules/env-hygiene.md +9 -0
- package/dist/content/rules/seo-metadata.md +7 -0
- package/dist/content/rules/ssr-safety.md +9 -0
- package/dist/content/skills/api-routes.md +33 -0
- package/dist/content/skills/continue-development.md +31 -0
- package/dist/content/skills/debugging.md +31 -0
- package/dist/content/skills/deployment.md +32 -0
- package/dist/content/skills/dev-server-management.md +31 -0
- package/dist/content/skills/env-vars.md +32 -0
- package/dist/content/skills/expo-router-architecture.md +33 -0
- package/dist/content/skills/expo-ssr-safety.md +32 -0
- package/dist/content/skills/plugin-creation.md +41 -0
- package/dist/content/skills/production-server-patterns.md +31 -0
- package/dist/content/skills/project-onboarding.md +31 -0
- package/dist/content/skills/research-plan-intake.md +31 -0
- package/dist/content/skills/seo-metadata.md +31 -0
- package/dist/content/skills/super-stack-startup.md +31 -0
- package/dist/content/skills/uniwind-theming.md +32 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.d.ts +78 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +264 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/prompts/index.d.ts +35 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +270 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Component Styling Patterns
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Component styling encapsulates Uniwind/Tailwind utilities into reusable, typed React components. Components accept className props for customization, use `cn()` for conditional classes with tailwind-merge, and follow consistent naming conventions (`ThemedText`, `ThemedView`, variant suffixes).
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
**Use component styling patterns** for:
|
|
10
|
+
- ✅ Reusable styled primitives (Button, Card, Input)
|
|
11
|
+
- ✅ Theme-aware components with dark mode support
|
|
12
|
+
- ✅ Complex components with multiple variants
|
|
13
|
+
- ✅ Accessible components with proper ARIA attributes
|
|
14
|
+
- ✅ Cross-platform component libraries
|
|
15
|
+
|
|
16
|
+
## Code Example
|
|
17
|
+
|
|
18
|
+
### Base Themed Primitives
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// File: src/components/ThemedView.tsx
|
|
22
|
+
import { View, ViewProps } from 'react-native';
|
|
23
|
+
|
|
24
|
+
interface ThemedViewProps extends ViewProps {
|
|
25
|
+
variant?: 'default' | 'surface' | 'surface-2';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ThemedView({
|
|
29
|
+
variant = 'default',
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: ThemedViewProps) {
|
|
33
|
+
const variantStyles = {
|
|
34
|
+
default: 'bg-[var(--color-background)]',
|
|
35
|
+
surface: 'bg-[var(--color-surface)]',
|
|
36
|
+
'surface-2': 'bg-[var(--color-surface-2)]',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View
|
|
41
|
+
className={`${variantStyles[variant]} ${className || ''}`}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// File: src/components/ThemedText.tsx
|
|
50
|
+
import { Text, TextProps } from 'react-native';
|
|
51
|
+
|
|
52
|
+
interface ThemedTextProps extends TextProps {
|
|
53
|
+
type?: 'default' | 'title' | 'subtitle' | 'caption';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function ThemedText({
|
|
57
|
+
type = 'default',
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: ThemedTextProps) {
|
|
61
|
+
const typeStyles = {
|
|
62
|
+
default: 'typography-body text-[var(--color-typography)]',
|
|
63
|
+
title: 'typography-title text-[var(--color-typography)]',
|
|
64
|
+
subtitle: 'typography-subheader text-[var(--color-typography-60)]',
|
|
65
|
+
caption: 'typography-caption text-[var(--color-typography-38)]',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Text
|
|
70
|
+
className={`${typeStyles[type]} ${className || ''}`}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Button Component with Variants
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// File: src/components/Button.tsx
|
|
81
|
+
import { Pressable, Text, PressableProps } from 'react-native';
|
|
82
|
+
import { cn } from '@/utils/cn';
|
|
83
|
+
|
|
84
|
+
type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'danger';
|
|
85
|
+
type ButtonSize = 'small' | 'medium' | 'large';
|
|
86
|
+
|
|
87
|
+
interface ButtonProps extends PressableProps {
|
|
88
|
+
title: string;
|
|
89
|
+
variant?: ButtonVariant;
|
|
90
|
+
size?: ButtonSize;
|
|
91
|
+
fullWidth?: boolean;
|
|
92
|
+
isLoading?: boolean;
|
|
93
|
+
leftIcon?: React.ReactNode;
|
|
94
|
+
rightIcon?: React.ReactNode;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function Button({
|
|
98
|
+
title,
|
|
99
|
+
variant = 'primary',
|
|
100
|
+
size = 'medium',
|
|
101
|
+
fullWidth = false,
|
|
102
|
+
isLoading = false,
|
|
103
|
+
leftIcon,
|
|
104
|
+
rightIcon,
|
|
105
|
+
className,
|
|
106
|
+
disabled,
|
|
107
|
+
...props
|
|
108
|
+
}: ButtonProps) {
|
|
109
|
+
// Base styles for all variants
|
|
110
|
+
const baseStyles = 'flex-row items-center justify-center rounded-lg font-semibold transition-all active:opacity-80 disabled:opacity-50';
|
|
111
|
+
|
|
112
|
+
// Variant colors
|
|
113
|
+
const variantStyles = {
|
|
114
|
+
primary: 'bg-[var(--color-primary)] text-white',
|
|
115
|
+
secondary: 'bg-[var(--color-secondary)] text-white',
|
|
116
|
+
tertiary: 'bg-[var(--color-surface-2)] text-[var(--color-typography)]',
|
|
117
|
+
danger: 'bg-[var(--color-error)] text-white',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Size padding
|
|
121
|
+
const sizeStyles = {
|
|
122
|
+
small: 'px-3 py-2 text-sm',
|
|
123
|
+
medium: 'px-4 py-3 text-base',
|
|
124
|
+
large: 'px-6 py-4 text-lg',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Width
|
|
128
|
+
const widthClass = fullWidth ? 'w-full' : 'self-start';
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Pressable
|
|
132
|
+
className={cn(baseStyles, variantStyles[variant], sizeStyles[size], widthClass, className)}
|
|
133
|
+
disabled={disabled || isLoading}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
{leftIcon && <View className="mr-2">{leftIcon}</View>}
|
|
137
|
+
|
|
138
|
+
<Text className="text-white font-semibold">
|
|
139
|
+
{isLoading ? 'Loading...' : title}
|
|
140
|
+
</Text>
|
|
141
|
+
|
|
142
|
+
{rightIcon && <View className="ml-2">{rightIcon}</View>}
|
|
143
|
+
</Pressable>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Usage:
|
|
148
|
+
<Button
|
|
149
|
+
title="Submit"
|
|
150
|
+
variant="primary"
|
|
151
|
+
size="large"
|
|
152
|
+
fullWidth
|
|
153
|
+
onPress={handleSubmit}
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
<Button
|
|
157
|
+
title="Delete"
|
|
158
|
+
variant="danger"
|
|
159
|
+
size="medium"
|
|
160
|
+
leftIcon={<TrashIcon />}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Card Component
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// File: src/components/Card.tsx
|
|
168
|
+
import { View, ViewProps } from 'react-native';
|
|
169
|
+
import { cn } from '@/utils/cn';
|
|
170
|
+
|
|
171
|
+
interface CardProps extends ViewProps {
|
|
172
|
+
elevation?: 'none' | 'small' | 'medium' | 'large';
|
|
173
|
+
rounded?: 'none' | 'small' | 'medium' | 'large';
|
|
174
|
+
padding?: 'none' | 'small' | 'medium' | 'large';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function Card({
|
|
178
|
+
elevation = 'medium',
|
|
179
|
+
rounded = 'medium',
|
|
180
|
+
padding = 'medium',
|
|
181
|
+
className,
|
|
182
|
+
...props
|
|
183
|
+
}: CardProps) {
|
|
184
|
+
const elevationStyles = {
|
|
185
|
+
none: '',
|
|
186
|
+
small: 'shadow-sm',
|
|
187
|
+
medium: 'shadow-md',
|
|
188
|
+
large: 'shadow-lg',
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const roundedStyles = {
|
|
192
|
+
none: 'rounded-none',
|
|
193
|
+
small: 'rounded-md',
|
|
194
|
+
medium: 'rounded-lg',
|
|
195
|
+
large: 'rounded-xl',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const paddingStyles = {
|
|
199
|
+
none: 'p-0',
|
|
200
|
+
small: 'p-2',
|
|
201
|
+
medium: 'p-4',
|
|
202
|
+
large: 'p-6',
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<View
|
|
207
|
+
className={cn(
|
|
208
|
+
'bg-[var(--color-surface)]',
|
|
209
|
+
'border border-[var(--color-border)]',
|
|
210
|
+
elevationStyles[elevation],
|
|
211
|
+
roundedStyles[rounded],
|
|
212
|
+
paddingStyles[padding],
|
|
213
|
+
className
|
|
214
|
+
)}
|
|
215
|
+
{...props}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Usage:
|
|
221
|
+
<Card padding="large" rounded="medium">
|
|
222
|
+
<ThemedText type="title">Card Title</ThemedText>
|
|
223
|
+
</Card>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Input Component
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// File: src/components/TextInput.tsx
|
|
230
|
+
import { TextInput as RNTextInput, TextInputProps as RNTextInputProps, View } from 'react-native';
|
|
231
|
+
import { ThemedText } from './ThemedText';
|
|
232
|
+
import { cn } from '@/utils/cn';
|
|
233
|
+
|
|
234
|
+
interface TextInputProps extends RNTextInputProps {
|
|
235
|
+
label?: string;
|
|
236
|
+
error?: string;
|
|
237
|
+
helperText?: string;
|
|
238
|
+
leftIcon?: React.ReactNode;
|
|
239
|
+
rightIcon?: React.ReactNode;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function TextInput({
|
|
243
|
+
label,
|
|
244
|
+
error,
|
|
245
|
+
helperText,
|
|
246
|
+
leftIcon,
|
|
247
|
+
rightIcon,
|
|
248
|
+
className,
|
|
249
|
+
...props
|
|
250
|
+
}: TextInputProps) {
|
|
251
|
+
return (
|
|
252
|
+
<View className="w-full">
|
|
253
|
+
{label && (
|
|
254
|
+
<ThemedText type="subtitle" className="mb-2">
|
|
255
|
+
{label}
|
|
256
|
+
</ThemedText>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
<View className="flex-row items-center border border-[var(--color-border)] rounded-lg px-3 bg-[var(--color-surface)]">
|
|
260
|
+
{leftIcon && <View className="mr-2">{leftIcon}</View>}
|
|
261
|
+
|
|
262
|
+
<RNTextInput
|
|
263
|
+
className={cn(
|
|
264
|
+
'flex-1 py-3 text-base text-[var(--color-typography)]',
|
|
265
|
+
'placeholder:text-[var(--color-typography-38)]',
|
|
266
|
+
error && 'border-[var(--color-error)]',
|
|
267
|
+
className
|
|
268
|
+
)}
|
|
269
|
+
placeholderTextColor="var(--color-typography-38)"
|
|
270
|
+
{...props}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
{rightIcon && <View className="ml-2">{rightIcon}</View>}
|
|
274
|
+
</View>
|
|
275
|
+
|
|
276
|
+
{error && (
|
|
277
|
+
<ThemedText type="caption" className="mt-1 text-[var(--color-error)]">
|
|
278
|
+
{error}
|
|
279
|
+
</ThemedText>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
{helperText && !error && (
|
|
283
|
+
<ThemedText type="caption" className="mt-1">
|
|
284
|
+
{helperText}
|
|
285
|
+
</ThemedText>
|
|
286
|
+
)}
|
|
287
|
+
</View>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Usage:
|
|
292
|
+
<TextInput
|
|
293
|
+
label="Email"
|
|
294
|
+
placeholder="user@example.com"
|
|
295
|
+
error={emailError}
|
|
296
|
+
leftIcon={<MailIcon />}
|
|
297
|
+
/>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Badge Component
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// File: src/components/Badge.tsx
|
|
304
|
+
import { View, Text, ViewProps } from 'react-native';
|
|
305
|
+
import { cn } from '@/utils/cn';
|
|
306
|
+
|
|
307
|
+
type BadgeVariant = 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
308
|
+
type BadgeSize = 'small' | 'medium' | 'large';
|
|
309
|
+
|
|
310
|
+
interface BadgeProps extends ViewProps {
|
|
311
|
+
label: string;
|
|
312
|
+
variant?: BadgeVariant;
|
|
313
|
+
size?: BadgeSize;
|
|
314
|
+
dismissible?: boolean;
|
|
315
|
+
onDismiss?: () => void;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function Badge({
|
|
319
|
+
label,
|
|
320
|
+
variant = 'primary',
|
|
321
|
+
size = 'medium',
|
|
322
|
+
dismissible = false,
|
|
323
|
+
onDismiss,
|
|
324
|
+
className,
|
|
325
|
+
...props
|
|
326
|
+
}: BadgeProps) {
|
|
327
|
+
const variantStyles = {
|
|
328
|
+
primary: 'bg-[var(--color-primary)] text-white',
|
|
329
|
+
success: 'bg-[var(--color-success)] text-white',
|
|
330
|
+
warning: 'bg-[var(--color-warning)] text-white',
|
|
331
|
+
error: 'bg-[var(--color-error)] text-white',
|
|
332
|
+
info: 'bg-[var(--color-info)] text-white',
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const sizeStyles = {
|
|
336
|
+
small: 'px-2 py-1 text-xs',
|
|
337
|
+
medium: 'px-3 py-1.5 text-sm',
|
|
338
|
+
large: 'px-4 py-2 text-base',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<View
|
|
343
|
+
className={cn(
|
|
344
|
+
'flex-row items-center rounded-full',
|
|
345
|
+
variantStyles[variant],
|
|
346
|
+
sizeStyles[size],
|
|
347
|
+
className
|
|
348
|
+
)}
|
|
349
|
+
{...props}
|
|
350
|
+
>
|
|
351
|
+
<Text className="font-semibold">{label}</Text>
|
|
352
|
+
{dismissible && (
|
|
353
|
+
<Text
|
|
354
|
+
onPress={onDismiss}
|
|
355
|
+
className="ml-2 font-bold opacity-70"
|
|
356
|
+
>
|
|
357
|
+
×
|
|
358
|
+
</Text>
|
|
359
|
+
)}
|
|
360
|
+
</View>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Usage:
|
|
365
|
+
<Badge label="Active" variant="success" dismissible onDismiss={handleDismiss} />
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Helper Function: `cn()`
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// File: src/utils/cn.ts
|
|
372
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
373
|
+
import { twMerge } from 'tailwind-merge';
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Merge Tailwind classes with proper conflict resolution
|
|
377
|
+
* - Combines multiple class arrays/strings with clsx
|
|
378
|
+
* - Resolves conflicting Tailwind utilities with twMerge
|
|
379
|
+
* - Removes falsy values automatically
|
|
380
|
+
*/
|
|
381
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
382
|
+
return twMerge(clsx(inputs));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Usage:
|
|
386
|
+
<View className={cn('bg-red-500', isActive && 'bg-blue-500')} />
|
|
387
|
+
// Result: "bg-blue-500" (red conflict resolved)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Best Practices
|
|
391
|
+
|
|
392
|
+
### ✅ DO
|
|
393
|
+
|
|
394
|
+
1. **Create primitive components** (Button, Card, Input, Badge)
|
|
395
|
+
```typescript
|
|
396
|
+
export function Button({ title, variant, size, ...props }) {
|
|
397
|
+
// Reusable across entire app
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
2. **Use variant patterns** for styling flexibility
|
|
402
|
+
```typescript
|
|
403
|
+
const variantStyles = {
|
|
404
|
+
primary: 'bg-primary text-white',
|
|
405
|
+
secondary: 'bg-secondary text-white',
|
|
406
|
+
tertiary: 'bg-surface-2 text-typography',
|
|
407
|
+
};
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
3. **Accept className prop** for composition
|
|
411
|
+
```typescript
|
|
412
|
+
export function Button({ className, ...props }) {
|
|
413
|
+
return <Pressable className={cn(baseStyles, className)} {...props} />;
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
4. **Use `cn()` for conditional classes**
|
|
418
|
+
```typescript
|
|
419
|
+
className={cn(
|
|
420
|
+
'base-styles',
|
|
421
|
+
isActive && 'active-styles',
|
|
422
|
+
error && 'error-styles'
|
|
423
|
+
)}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### ❌ DON'T
|
|
427
|
+
|
|
428
|
+
1. **Don't hardcode colors** — use theme variables
|
|
429
|
+
```tsx
|
|
430
|
+
// ❌ BAD
|
|
431
|
+
className="bg-red-500"
|
|
432
|
+
|
|
433
|
+
// ✅ GOOD
|
|
434
|
+
className="bg-[var(--color-primary)]"
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
2. **Don't create one-off styled components**
|
|
438
|
+
```tsx
|
|
439
|
+
// ❌ BAD - not reusable
|
|
440
|
+
function HomeButton({ onPress }) {
|
|
441
|
+
return <Pressable className="bg-blue-500 px-4 py-3 rounded" onPress={onPress} />;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ✅ GOOD - reusable primitive
|
|
445
|
+
function Button({ title, variant, ...props }) {
|
|
446
|
+
// Used everywhere
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
3. **Don't use inline style objects** for Tailwind-able properties
|
|
451
|
+
```tsx
|
|
452
|
+
// ❌ BAD
|
|
453
|
+
style={{ backgroundColor: '#ef5350' }}
|
|
454
|
+
|
|
455
|
+
// ✅ GOOD
|
|
456
|
+
className="bg-[var(--color-primary)]"
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Related Patterns
|
|
460
|
+
|
|
461
|
+
- [Uniwind Setup](./uniwind-setup.md) — Uniwind configuration
|
|
462
|
+
- [Theme Configuration](./theme-configuration.md) — Theme tokens
|
|
463
|
+
- [Responsive Patterns](./responsive-patterns.md) — Responsive design
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
*Pattern extracted from production repositories: PokePages, DJsPortfolio, expo-super-template*
|
|
468
|
+
*Files: src/components/*.tsx from Expo Router projects*
|