-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathui-development-guidelines.mdc
More file actions
378 lines (308 loc) · 13.7 KB
/
ui-development-guidelines.mdc
File metadata and controls
378 lines (308 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
---
description: UI Development Guidelines
globs:
- 'app/**/*.{tsx,jsx}'
alwaysApply: true
---
# MetaMask Mobile React Native UI Development Guidelines
## Core Principle
Always prioritize @metamask/design-system-react-native components and Tailwind CSS patterns over custom implementations.
## Component Hierarchy (STRICT ORDER)
### The Rule: Check Design System First
**Before writing any new component or choosing what to use, ask: "Does @metamask/design-system-react-native have this?"**
1. **FIRST**: Use `@metamask/design-system-react-native` components
- **Always use for**: Box (layout), Text, SensitiveText, TextButton (inline links only)
- **Always use for**: Button, ButtonBase, ButtonIcon, ButtonSemantic, Icon, Checkbox, RadioButton
- **Always use for**: Avatar variants (Account, Base, Favicon, Group, Icon, Network, Token)
- **Always use for**: Badge variants (Count, Icon, Network, Status, Wrapper)
- **Always use for**: BottomSheet, BottomSheetFooter, BottomSheetHeader, BottomSheetDialog, BottomSheetOverlay
- **Always use for**: HeaderBase, HeaderRoot, HeaderSearch, HeaderStandard
- **Always use for**: BannerAlert, Card, ListItem, Skeleton, Label, Input, TextField
- **Always use for**: KeyValueRow, KeyValueColumn, ActionListItem, MainActionButton, TabEmptyState
- **Rule**: If it exists in the design system, you MUST use it
2. **SECOND**: Use `app/component-library` ONLY if design system lacks it
- **Use for**: Tabs, Tags, Cells, Modal, Overlay, Toast, Pickers, Select, Sheet/SheetHeader, etc.
- **Rule**: These are MetaMask-specific implementations not (yet) in the design system
- **Important**: component-library components should themselves use design system primitives internally
3. **THIRD**: Feature-specific components
- **Use for**: Complex, domain-specific UI that combines multiple design system/component-library components
- **Examples**: `BridgeInputSelector`, `StakeInputView`, `NFTDetailsModal`
- **Rule**: Must be built using Box, Text, and other design system primitives - NO raw View/Text or StyleSheet
- **Reuse**: Search for existing feature components before building new ones to avoid duplication
4. **LAST RESORT**: Custom components with StyleSheet
- **Only when**: Highly specialized one-off needs with no design system equivalent AND no component-library equivalent
- **Requires**: Strong justification why design system primitives can't be composed
### Decision Tree
```
Need a component?
├─ Is it Box, Text, Button, Icon, Avatar, Badge, Checkbox, RadioButton,
│ BottomSheet, Header, BannerAlert, Card, ListItem, Skeleton, Label,
│ TextField, Input, KeyValueRow, SensitiveText, TextButton, or ActionListItem?
│ └─ YES → Use @metamask/design-system-react-native [STOP]
│
├─ Is it Tabs, Tags, Cells, Modal, Overlay, Toast, Pickers, Select, Sheet?
│ └─ YES → Use app/component-library [STOP]
│
├─ Is it feature-specific UI (e.g., BridgeInputSelector, StakeInputView)?
│ ├─ Does it already exist? (search codebase for similar components)
│ │ ├─ YES → Reuse existing component [STOP]
│ │ └─ NO → Build new component using design system primitives [STOP]
│ └─
│
└─ Can I compose it from Box + Text + other primitives?
├─ YES → Compose from design system [STOP]
└─ NO → Consider if custom implementation is truly necessary
```
### Why This Hierarchy Matters
- **Consistency**: Design system ensures consistent look, feel, and behavior
- **Maintenance**: Centralized updates benefit all consumers
- **Accessibility**: Design system components include a11y best practices
- **Performance**: Optimized implementations tested at scale
- **Type Safety**: Full TypeScript support with JSDoc documentation
## Required Imports for React Native
```tsx
// ALWAYS prefer these imports
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import {
Box,
Text,
Button,
ButtonBase,
Icon,
TextVariant,
BoxFlexDirection,
BoxAlignItems,
BoxJustifyContent,
// ... other design system components
} from '@metamask/design-system-react-native';
```
## Component Documentation Access
### Type Definitions & JSDoc Comments
All @metamask/design-system-react-native components have comprehensive TypeScript definitions with JSDoc comments:
- **Box**: `/node_modules/@metamask/design-system-react-native/dist/components/Box/Box.types.d.cts`
- **Text**: `/node_modules/@metamask/design-system-react-native/dist/components/Text/Text.types.d.cts`
- **Button**: `/node_modules/@metamask/design-system-react-native/dist/components/Button/Button.types.d.cts`
When unsure about component APIs:
1. Read the `.types.d.cts` files for complete prop documentation
2. Reference `app/component-library/components/design-system.stories.tsx` for usage examples
3. Check GitHub source: https://github.com/MetaMask/metamask-design-system/tree/main/packages/design-system-react-native/src/components
### Box Component Quick Reference
- **Spacing**: Use `gap`, `padding*`, `margin*` props with values 0-12 (maps to 0px-48px)
- **Flexbox**: Use `flexDirection`, `alignItems`, `justifyContent` enum props
- **Colors**: Use `backgroundColor` and `borderColor` with semantic tokens
- **Tailwind**: Use `twClassName` for utilities not covered by props
## Styling Rules (ENFORCE STRICTLY)
### ✅ ALWAYS DO:
- Use `const tw = useTailwind();` hook instead of importing twrnc directly
- Use `Box` component instead of `View`
- Use `Text` component with variants instead of raw Text with styles
- Use `twClassName` prop for static styles
- Use `tw.style()` function for interactive/dynamic styles
- Use design system color tokens: `bg-default`, `text-primary`, `border-muted`
- Use component props first: `variant`, `color`, `size`, etc.
### ❌ NEVER SUGGEST:
- `import tw from 'twrnc'` (use useTailwind hook instead)
- `StyleSheet.create()` (use Tailwind classes)
- Raw `View` or `Text` components (use Box/Text from design system)
- Arbitrary color values like `bg-[#3B82F6]` or `text-[#000000]`
- Inline style objects unless for dynamic values
- Mixing multiple styling approaches unnecessarily
## Code Pattern Templates
### Basic Container:
```tsx
const MyComponent = () => {
const tw = useTailwind();
return (
<Box twClassName="w-full bg-default p-4">
<Text variant={TextVariant.HeadingMd}>Title</Text>
</Box>
);
};
```
### Flex Layout:
```tsx
<Box
flexDirection={BoxFlexDirection.Row}
alignItems={BoxAlignItems.Center}
justifyContent={BoxJustifyContent.Between}
twClassName="gap-3"
>
```
### Interactive Element:
```tsx
<ButtonBase
twClassName="h-20 flex-1 rounded-lg bg-muted px-0 py-4"
style={({ pressed }) =>
tw.style(
'w-full flex-row items-center justify-center',
pressed && 'bg-pressed',
)
}
>
<Text fontWeight={FontWeight.Medium}>Button Text</Text>
</ButtonBase>
```
### Pressable with Tailwind:
```tsx
<Pressable
style={({ pressed }) =>
tw.style(
'w-full flex-row items-center justify-between px-4 py-2',
pressed && 'bg-pressed',
)
}
>
```
## Box Component Best Practices
### Prefer Props Over twClassName for Layout
✅ **DO** - Use typed props for type safety and consistency:
```tsx
<Box
flexDirection={BoxFlexDirection.Row}
alignItems={BoxAlignItems.Center}
justifyContent={BoxJustifyContent.Between}
gap={3}
padding={4}
margin={2}
>
```
❌ **DON'T** - Use twClassName for properties that have dedicated props:
```tsx
<Box twClassName="flex-row items-center justify-between gap-3 p-4 m-2">
```
### When to Use twClassName
Use `twClassName` for:
- Width and height: `w-full`, `h-20`, `w-[337px]`
- Complex positioning: `absolute`, `relative`, `top-0`, `left-0`
- Borders (partial): `rounded-lg`, `border-t`
- Shadows and opacity: `shadow-lg`, `opacity-50`
- Utilities not covered by props: `overflow-hidden`, `z-10`
### Spacing System
- Use numeric props (0-12) for spacing: `padding={4}` = 16px
- Each unit = 4px (so 12 = 48px max)
- For custom spacing beyond 48px, use twClassName: `twClassName="p-20"`
### Color Tokens
Always use semantic color tokens:
```tsx
// ✅ Semantic tokens
<Box backgroundColor={BoxBackgroundColor.BackgroundDefault}>
<Box backgroundColor={BoxBackgroundColor.PrimaryDefault}>
<Box backgroundColor={BoxBackgroundColor.ErrorMuted}>
// ❌ Arbitrary colors
<Box twClassName="bg-[#3B82F6]">
<Box style={{ backgroundColor: '#FF0000' }}>
```
## Component Conversion Guide
| DON'T Use | USE Instead |
| ------------------------------------ | -------------------------------------- |
| `<View>` | `<Box>` |
| `<Text style={...}>` | `<Text variant={TextVariant.BodyMd}>` |
| `StyleSheet.create()` | `twClassName="..."` |
| `style={{ backgroundColor: 'red' }}` | `twClassName="bg-error-default"` |
| `flexDirection: 'row'` | `flexDirection={BoxFlexDirection.Row}` |
| Manual padding/margin | `twClassName="p-4 m-2"` |
## Platform-Specific Gotchas
### ScrollView Inside BottomSheet
When using a `ScrollView` inside a `BottomSheet`, you **MUST** import `ScrollView` from `react-native-gesture-handler`, not from `react-native`. The standard React Native `ScrollView` will not scroll on Android within a gesture-handler-managed `BottomSheet`.
```tsx
// ✅ CORRECT - works on both iOS and Android
import { ScrollView } from 'react-native-gesture-handler';
// ❌ WRONG - will not scroll on Android inside BottomSheet
import { ScrollView } from 'react-native';
```
## Legacy Code Migration Guidelines
### Identifying Legacy Patterns
🚫 **Anti-patterns to refactor when encountered:**
- Files using `StyleSheet.create()`
- Separate `.styles.ts` or `.styles.tsx` files
- Raw `View` components instead of `Box`
- Raw `Text` components with custom styles instead of design system `Text` with variants
- Inline style objects for static styles
### Migration Priority
1. **High Priority**: Components being actively modified
2. **Medium Priority**: Frequently used shared components in `app/component-library`
3. **Low Priority**: Stable legacy components with no active development
### Migration Steps
1. Replace `View` → `Box` from design system
2. Replace `Text` → `Text` with appropriate `TextVariant`
3. Convert `StyleSheet.create()` styles → `twClassName` props or `tw.style()`
4. Convert arbitrary colors → design system color tokens
5. Delete `.styles.ts` files after migration
6. Test thoroughly - layout can shift during migration
### Example Migration
**Before:**
```tsx
import { View, Text, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#ffffff',
},
title: {
fontSize: 16,
fontWeight: '500',
color: '#000000',
},
});
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
</View>
```
**After:**
```tsx
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { Box, Text, TextVariant, BoxFlexDirection, BoxAlignItems, FontWeight } from '@metamask/design-system-react-native';
const tw = useTailwind();
<Box
flexDirection={BoxFlexDirection.Row}
alignItems={BoxAlignItems.Center}
twClassName="p-4 bg-default"
>
<Text variant={TextVariant.BodyMd} fontWeight={FontWeight.Medium}>
Title
</Text>
</Box>
```
## Error Prevention & Code Review Checklist
### Before Committing Code, Verify:
- [ ] No `import tw from 'twrnc'` (must use `useTailwind()` hook)
- [ ] No raw `View` components (use `Box`)
- [ ] No raw `Text` without variants (use `Text` with `TextVariant`)
- [ ] No `StyleSheet.create()` (use `twClassName` or `tw.style()`)
- [ ] No arbitrary color values (use design system tokens)
- [ ] No separate `.styles.ts` files for new components
- [ ] Component props used before `twClassName` for layout
- [ ] Interactive styles use `tw.style()` with state functions
- [ ] `ScrollView` inside `BottomSheet` imported from `react-native-gesture-handler` (not `react-native`)
### When You See These Patterns, IMMEDIATELY Suggest Alternatives:
- Any `import tw from 'twrnc'` → `import { useTailwind } from '@metamask/design-system-twrnc-preset'`
- Any `View` component → `Box` from design system
- Any `StyleSheet` usage → Tailwind classes
- Any arbitrary color values → Design system tokens
- Any manual flex properties → Box component props + twClassName
### AI Agent Guidelines
When suggesting code changes:
1. ALWAYS read component type definitions first for accurate API usage
2. ALWAYS check `design-system.stories.tsx` for real-world patterns
3. ALWAYS search for existing feature-specific components before building new ones (use Glob/Grep to find similar components in feature directories)
4. REJECT any suggestions that violate the hierarchy
5. SUGGEST migrations when encountering legacy patterns
6. EXPLAIN why design system approach is preferred
## Design System Priority
Before suggesting any UI solution:
1. Check if `@metamask/design-system-react-native` has the component
2. Use component's built-in props (variant, color, size)
3. Add layout/spacing with `twClassName`
4. Add interactions with `tw.style()`
5. Only suggest component-library or custom components if design system lacks it
## Reference Examples
Always reference the patterns from `app/component-library/components/design-system.stories.tsx` for proper usage examples.
## Enforcement
- REJECT any code suggestions that use StyleSheet.create()
- REJECT raw View/Text usage when Box/Text components exist
- REQUIRE useTailwind hook for all Tailwind usage
- REQUIRE design system components as first choice
- ENFORCE design token usage over arbitrary values
@app/component-library/components/design-system.stories.tsx