@momo-kits/native-kits 0.157.5-debug → 0.158.1-beta.1-debug
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/.claude/settings.local.json +11 -0
- package/.claude/skills/design-system/SKILL.md +88 -0
- package/.claude/skills/design-system/references/components/avatar.md +134 -0
- package/.claude/skills/design-system/references/components/badge.md +127 -0
- package/.claude/skills/design-system/references/components/bottom-tab.md +177 -0
- package/.claude/skills/design-system/references/components/bottomsheet.md +170 -0
- package/.claude/skills/design-system/references/components/button.md +206 -0
- package/.claude/skills/design-system/references/components/carousel.md +117 -0
- package/.claude/skills/design-system/references/components/checkbox.md +98 -0
- package/.claude/skills/design-system/references/components/chip.md +146 -0
- package/.claude/skills/design-system/references/components/collapse.md +120 -0
- package/.claude/skills/design-system/references/components/date-picker.md +119 -0
- package/.claude/skills/design-system/references/components/divider.md +84 -0
- package/.claude/skills/design-system/references/components/icon.md +130 -0
- package/.claude/skills/design-system/references/components/image.md +81 -0
- package/.claude/skills/design-system/references/components/information.md +107 -0
- package/.claude/skills/design-system/references/components/input-dropdown.md +138 -0
- package/.claude/skills/design-system/references/components/input-money.md +157 -0
- package/.claude/skills/design-system/references/components/input-otp.md +132 -0
- package/.claude/skills/design-system/references/components/input-phone-number.md +140 -0
- package/.claude/skills/design-system/references/components/input-search.md +124 -0
- package/.claude/skills/design-system/references/components/input-text-area.md +133 -0
- package/.claude/skills/design-system/references/components/input.md +152 -0
- package/.claude/skills/design-system/references/components/loader.md +87 -0
- package/.claude/skills/design-system/references/components/pagination.md +105 -0
- package/.claude/skills/design-system/references/components/popup-notify.md +128 -0
- package/.claude/skills/design-system/references/components/progress-info.md +114 -0
- package/.claude/skills/design-system/references/components/radio.md +86 -0
- package/.claude/skills/design-system/references/components/rating.md +126 -0
- package/.claude/skills/design-system/references/components/skeleton.md +120 -0
- package/.claude/skills/design-system/references/components/slider.md +141 -0
- package/.claude/skills/design-system/references/components/snackbar.md +97 -0
- package/.claude/skills/design-system/references/components/stepper.md +100 -0
- package/.claude/skills/design-system/references/components/steps.md +91 -0
- package/.claude/skills/design-system/references/components/suggest-action.md +95 -0
- package/.claude/skills/design-system/references/components/swipe.md +121 -0
- package/.claude/skills/design-system/references/components/switch.md +98 -0
- package/.claude/skills/design-system/references/components/tab-view.md +120 -0
- package/.claude/skills/design-system/references/components/tag.md +118 -0
- package/.claude/skills/design-system/references/components/text.md +151 -0
- package/.claude/skills/design-system/references/components/toast.md +99 -0
- package/.claude/skills/design-system/references/components/tooltip.md +138 -0
- package/.claude/skills/design-system/references/components/top-nav-miniapp.md +94 -0
- package/.claude/skills/design-system/references/components/top-nav.md +226 -0
- package/.claude/skills/design-system/references/components/uploader.md +115 -0
- package/.claude/skills/design-system/references/navigation/bottom-tab.md +131 -0
- package/.claude/skills/design-system/references/navigation/bottomsheet.md +161 -0
- package/.claude/skills/design-system/references/navigation/modal.md +133 -0
- package/.claude/skills/design-system/references/navigation/navigation-options.md +225 -0
- package/.claude/skills/design-system/references/navigation/navigator.md +111 -0
- package/.claude/skills/design-system/references/navigation/setup.md +134 -0
- package/.claude/skills/design-system/references/navigation/stack.md +128 -0
- package/.claude/skills/design-system/references/spec-convention.md +80 -0
- package/.claude/skills/design-system/references/tokens/colors.md +131 -0
- package/.claude/skills/design-system/references/tokens/spacing-radius.md +144 -0
- package/.claude/skills/design-system/references/tokens/theme.md +125 -0
- package/.claude/skills/design-system/references/tokens/typography.md +135 -0
- package/.claude/skills/design-system-kits/SKILL.md +102 -0
- package/.claude/skills/design-system-kits/references/code-convention.md +118 -0
- package/.claude/skills/design-system-kits/references/components/avatar.md +45 -0
- package/.claude/skills/design-system-kits/references/components/badge.md +27 -0
- package/.claude/skills/design-system-kits/references/components/button.md +65 -0
- package/.claude/skills/design-system-kits/references/components/carousel.md +51 -0
- package/.claude/skills/design-system-kits/references/components/checkbox.md +39 -0
- package/.claude/skills/design-system-kits/references/components/chip.md +54 -0
- package/.claude/skills/design-system-kits/references/components/collapse.md +63 -0
- package/.claude/skills/design-system-kits/references/components/date-picker.md +36 -0
- package/.claude/skills/design-system-kits/references/components/divider.md +21 -0
- package/.claude/skills/design-system-kits/references/components/icon.md +382 -0
- package/.claude/skills/design-system-kits/references/components/image.md +62 -0
- package/.claude/skills/design-system-kits/references/components/information.md +61 -0
- package/.claude/skills/design-system-kits/references/components/input-dropdown.md +92 -0
- package/.claude/skills/design-system-kits/references/components/input-money.md +128 -0
- package/.claude/skills/design-system-kits/references/components/input-otp.md +85 -0
- package/.claude/skills/design-system-kits/references/components/input-phone-number.md +96 -0
- package/.claude/skills/design-system-kits/references/components/input-search.md +127 -0
- package/.claude/skills/design-system-kits/references/components/input-text-area.md +100 -0
- package/.claude/skills/design-system-kits/references/components/input.md +126 -0
- package/.claude/skills/design-system-kits/references/components/loader.md +41 -0
- package/.claude/skills/design-system-kits/references/components/pagination.md +47 -0
- package/.claude/skills/design-system-kits/references/components/popup-notify.md +69 -0
- package/.claude/skills/design-system-kits/references/components/popup-promotion.md +35 -0
- package/.claude/skills/design-system-kits/references/components/progress-info.md +55 -0
- package/.claude/skills/design-system-kits/references/components/radio.md +42 -0
- package/.claude/skills/design-system-kits/references/components/rating.md +36 -0
- package/.claude/skills/design-system-kits/references/components/skeleton.md +25 -0
- package/.claude/skills/design-system-kits/references/components/slider.md +53 -0
- package/.claude/skills/design-system-kits/references/components/snackbar.md +52 -0
- package/.claude/skills/design-system-kits/references/components/stepper.md +46 -0
- package/.claude/skills/design-system-kits/references/components/steps.md +57 -0
- package/.claude/skills/design-system-kits/references/components/suggest-action.md +44 -0
- package/.claude/skills/design-system-kits/references/components/swipe.md +44 -0
- package/.claude/skills/design-system-kits/references/components/switch.md +43 -0
- package/.claude/skills/design-system-kits/references/components/tab-view.md +56 -0
- package/.claude/skills/design-system-kits/references/components/tag.md +50 -0
- package/.claude/skills/design-system-kits/references/components/text.md +56 -0
- package/.claude/skills/design-system-kits/references/components/toast.md +51 -0
- package/.claude/skills/design-system-kits/references/components/tooltip.md +95 -0
- package/.claude/skills/design-system-kits/references/components/uploader.md +48 -0
- package/.claude/skills/design-system-kits/references/design-spec-structure.md +356 -0
- package/.claude/skills/design-system-kits/references/design-spec-to-code.md +596 -0
- package/.claude/skills/design-system-kits/references/navigation/bottom-tab.md +155 -0
- package/.claude/skills/design-system-kits/references/navigation/bottomsheet.md +94 -0
- package/.claude/skills/design-system-kits/references/navigation/modal.md +125 -0
- package/.claude/skills/design-system-kits/references/navigation/navigation-options.md +430 -0
- package/.claude/skills/design-system-kits/references/navigation/navigator.md +177 -0
- package/.claude/skills/design-system-kits/references/navigation/setup.md +94 -0
- package/.claude/skills/design-system-kits/references/navigation/stack.md +152 -0
- package/.claude/skills/design-system-kits/references/screen-layout-rule.md +125 -0
- package/.claude/skills/design-system-kits/references/tokens/colors.md +183 -0
- package/.claude/skills/design-system-kits/references/tokens/spacing-radius.md +45 -0
- package/.claude/skills/design-system-kits/references/tokens/theme.md +97 -0
- package/.claude/skills/design-system-kits/references/tokens/typography.md +105 -0
- package/.claude/skills/vibe-design/SKILL.md +306 -0
- package/compose/build.gradle.kts +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +7 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +2 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +5 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +12 -0
- package/gradle.properties +1 -1
- package/ios/Popup/PopupPromotion.swift +2 -2
- package/local.properties +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
# Design Spec to Code Mapping
|
|
2
|
+
|
|
3
|
+
How to translate a design spec JSON into platform code. Read `design-spec-structure.md` first to understand the JSON schema.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## React Native
|
|
8
|
+
|
|
9
|
+
### File & Imports
|
|
10
|
+
|
|
11
|
+
- `meta.screenName` → file name and default export
|
|
12
|
+
- Import design system components from `@momo-kits/foundation`
|
|
13
|
+
- Import `Chip` from `@momo-kits/chip` (separate package)
|
|
14
|
+
- Import `View`, `Pressable`, `StyleSheet` from `react-native`
|
|
15
|
+
- Import tokens: `Colors`, `Spacing`, `Radius` from `@momo-kits/foundation`
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Colors, Icon, Screen, ScreenRef, Spacing, Text } from '@momo-kits/foundation';
|
|
19
|
+
import { Chip } from '@momo-kits/chip';
|
|
20
|
+
import { Pressable, StyleSheet, View } from 'react-native';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### `meta` → Component Declaration
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// meta.screenName → component name
|
|
27
|
+
const MoneyTransferScreen: React.FC<NavigationScreenProps> = ({ navigation }) => {
|
|
28
|
+
const screenRef = useRef<ScreenRef>(null);
|
|
29
|
+
// ...
|
|
30
|
+
};
|
|
31
|
+
export default MoneyTransferScreen;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `components` → Sub-Components
|
|
35
|
+
|
|
36
|
+
Each blueprint becomes a typed React functional component.
|
|
37
|
+
|
|
38
|
+
| Spec field | Code target |
|
|
39
|
+
|------------|-------------|
|
|
40
|
+
| `type` | Component name: `const SecurityBanner: React.FC<Props>` |
|
|
41
|
+
| `props` | TypeScript interface |
|
|
42
|
+
| `styles` | `StyleSheet.create` entry |
|
|
43
|
+
| `children` | JSX children |
|
|
44
|
+
|
|
45
|
+
**Prop type mapping:**
|
|
46
|
+
|
|
47
|
+
| Spec type | TypeScript type |
|
|
48
|
+
|-----------|----------------|
|
|
49
|
+
| `"string"` | `string` |
|
|
50
|
+
| `"number"` | `number` |
|
|
51
|
+
| `"boolean"` | `boolean` |
|
|
52
|
+
| `"function"` | `() => void` |
|
|
53
|
+
| `"array"` | `string[]` or typed array |
|
|
54
|
+
|
|
55
|
+
**Child element mapping:**
|
|
56
|
+
|
|
57
|
+
| Spec `type` | JSX |
|
|
58
|
+
|-------------|-----|
|
|
59
|
+
| `"Icon"` | `<Icon source="..." size={24} />` |
|
|
60
|
+
| `"Text"` | `<Text typography="...">{children}</Text>` |
|
|
61
|
+
| `"View"` | `<View style={...}>{children}</View>` |
|
|
62
|
+
| `"Pressable"` | `<Pressable onPress={...}>{children}</Pressable>` |
|
|
63
|
+
| Any design system component | `<Component {...props} />` |
|
|
64
|
+
|
|
65
|
+
**Example:**
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
interface SecurityBannerProps {
|
|
69
|
+
description: string;
|
|
70
|
+
aiLabel: string;
|
|
71
|
+
onPressMore: () => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const SecurityBanner: React.FC<SecurityBannerProps> = ({
|
|
75
|
+
description, aiLabel, onPressMore,
|
|
76
|
+
}) => (
|
|
77
|
+
<View style={styles.securityBanner}>
|
|
78
|
+
<Icon source="24_check_circle" size={24} color={Colors.green_03} />
|
|
79
|
+
<Text typography="body_default_regular" color={Colors.black_17} style={styles.flex1}>
|
|
80
|
+
{description}
|
|
81
|
+
</Text>
|
|
82
|
+
<Tag label={aiLabel} icon="16_sparkle" customColor={Colors.pink_03} size="medium" />
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `header` → Two Code Targets
|
|
88
|
+
|
|
89
|
+
Header props split across **two targets**. Putting a prop in the wrong target produces broken headers.
|
|
90
|
+
|
|
91
|
+
**Target 1: `<Screen>` JSX props** — visual appearance
|
|
92
|
+
|
|
93
|
+
| Spec `header.props` | Screen prop | Example |
|
|
94
|
+
|----------------------|------------|---------|
|
|
95
|
+
| `headerType` | `headerType` | `headerType="extended"` |
|
|
96
|
+
| `headerBackground` | `headerBackground` | `headerBackground="https://bg.png"` |
|
|
97
|
+
| `useShadowHeader` | `useShadowHeader` | `useShadowHeader={true}` |
|
|
98
|
+
|
|
99
|
+
**Target 2: `screenRef.current?.setOptions()` in `useEffect`** — content/behavior
|
|
100
|
+
|
|
101
|
+
| Spec `header.props` | setOptions prop | Transform |
|
|
102
|
+
|----------------------|----------------|-----------|
|
|
103
|
+
| `useBack` | `hiddenBack` | **Invert**: `useBack: true` → `hiddenBack: false` |
|
|
104
|
+
| `headerTitle` | `headerTitle` | Pass object as-is: `{ type, data }` |
|
|
105
|
+
| `headerRight` | `headerRight` | Pass object as-is |
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
const screenRef = useRef<ScreenRef>(null);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
screenRef.current?.setOptions({
|
|
112
|
+
hiddenBack: false,
|
|
113
|
+
headerTitle: {
|
|
114
|
+
type: 'user',
|
|
115
|
+
data: { title: name, subTitle: phone, image: avatar },
|
|
116
|
+
},
|
|
117
|
+
headerRight: {
|
|
118
|
+
useShortcut: false,
|
|
119
|
+
tools: [{ title: { vi: 'Tiện ích', en: 'Tools' }, items: [...] }],
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}, []);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `body` → Screen Component
|
|
126
|
+
|
|
127
|
+
| Spec field | Screen prop |
|
|
128
|
+
|------------|-------------|
|
|
129
|
+
| `scrollable: true` | `scrollable` |
|
|
130
|
+
| `style.backgroundColor` | `backgroundColor={Colors.xxx}` |
|
|
131
|
+
| `children` | JSX children inside `<Screen>` |
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<Screen
|
|
135
|
+
ref={screenRef}
|
|
136
|
+
navigation={navigation}
|
|
137
|
+
headerType="extended"
|
|
138
|
+
scrollable
|
|
139
|
+
backgroundColor={Colors.black_02}
|
|
140
|
+
footerComponent={renderFooter}
|
|
141
|
+
>
|
|
142
|
+
<SecurityBanner description={text} />
|
|
143
|
+
<TransferCard amount={amount} />
|
|
144
|
+
</Screen>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `footer` → `footerComponent` Prop
|
|
148
|
+
|
|
149
|
+
`Screen` footer already provides built-in spacing and shadow. **Skip `footer.style` properties like `padding`, `margin`, and `backgroundColor`** — they are redundant and will cause double-spacing.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<Screen
|
|
153
|
+
footerComponent={
|
|
154
|
+
<Button title={buttonTitle} type="disabled" size="large" full onPress={handleTransfer} />
|
|
155
|
+
}
|
|
156
|
+
>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- `footer.style` → **Skip** padding/margin/backgroundColor (Screen footer handles these)
|
|
160
|
+
- `footer.children` → render design system or custom components directly as `footerComponent` value
|
|
161
|
+
- If footer has a single child → pass it directly (no wrapping `View`)
|
|
162
|
+
- If footer has multiple children → wrap in a `View` but **without** padding/margin styles
|
|
163
|
+
|
|
164
|
+
### `tabs` → `{ScreenName}Tab` Wrapper File
|
|
165
|
+
|
|
166
|
+
When `tabs` is an object (not `null`), generate a separate `{ScreenName}Tab.tsx` file that wraps the screen in a `BottomTab`.
|
|
167
|
+
|
|
168
|
+
- `tabs.indexActive` determines which item in `tabs.items` renders the screen from this spec
|
|
169
|
+
- Other items reference external screen components by `screen` name
|
|
170
|
+
- `initialRouteName` defaults to `items[indexActive].name`
|
|
171
|
+
- When `tabs.floatingButton` is not empty (`{}`), map it to the `floatingButton` prop
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import {
|
|
175
|
+
BottomTab, BottomTabItemProps, FloatingButtonProps, NavigationScreenProps,
|
|
176
|
+
} from '@momo-kits/foundation';
|
|
177
|
+
import MoneyTransferScreen from './MoneyTransferScreen';
|
|
178
|
+
import ScanScreen from './ScanScreen';
|
|
179
|
+
import ProfileScreen from './ProfileScreen';
|
|
180
|
+
|
|
181
|
+
const tabs: BottomTabItemProps[] = [
|
|
182
|
+
{
|
|
183
|
+
name: 'MoneyTransfer',
|
|
184
|
+
label: 'Chuyển tiền', // from items[].label
|
|
185
|
+
icon: 'ic_transfer',
|
|
186
|
+
screen: MoneyTransferScreen, // indexActive: 0 → this spec's screen
|
|
187
|
+
options: { title: 'Chuyển tiền' },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'Profile',
|
|
191
|
+
label: 'Tôi',
|
|
192
|
+
icon: 'ic_profile',
|
|
193
|
+
screen: ProfileScreen, // non-active → external component
|
|
194
|
+
options: { title: 'Tôi' },
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
// tabs.floatingButton → floatingButton prop
|
|
199
|
+
const floatingButton: FloatingButtonProps = {
|
|
200
|
+
icon: 'ic_scan', // from tabs.floatingButton.icon
|
|
201
|
+
label: 'Quét mọi QR', // from tabs.floatingButton.label
|
|
202
|
+
onPress: () => {}, // handler to navigate to ScanScreen
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const MoneyTransferTab: React.FC<NavigationScreenProps> = ({ navigation }) => (
|
|
206
|
+
<BottomTab
|
|
207
|
+
tabs={tabs}
|
|
208
|
+
navigation={navigation}
|
|
209
|
+
initialRouteName="MoneyTransfer"
|
|
210
|
+
nested={false}
|
|
211
|
+
floatingButton={floatingButton}
|
|
212
|
+
/>
|
|
213
|
+
);
|
|
214
|
+
export default MoneyTransferTab;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
If a referenced `screen` component does not exist, generate a placeholder screen file for it.
|
|
218
|
+
|
|
219
|
+
### `actions` → `useCallback` Handlers
|
|
220
|
+
|
|
221
|
+
Each action key becomes a `useCallback` function. Use `code` as the function body.
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
const handleChangeAmount = useCallback((value: string) => {
|
|
225
|
+
setTransferAmount(value);
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
const handlePressGreetingCard = useCallback(() => {
|
|
229
|
+
navigation.navigate('GreetingCardPicker');
|
|
230
|
+
}, [navigation]);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `localization` → Default State Values
|
|
234
|
+
|
|
235
|
+
Use `vi` locale values as initial state and display strings.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
const [transferAmount, setTransferAmount] = useState('0');
|
|
239
|
+
const recipientName = 'Anh Dũng Hồng';
|
|
240
|
+
const quickMessageList = ['Mình chuyển tiền nhé 🤑', 'Em cảm ơn ạ! 🤟'];
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### `tracking` → Comments
|
|
244
|
+
|
|
245
|
+
Generate as inline comments near the relevant handler.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
const handleTransfer = useCallback(() => {
|
|
249
|
+
// tracking: TransferButtonPressed { amount, message, recipientPhone }
|
|
250
|
+
initiateTransfer({ amount, message, recipient });
|
|
251
|
+
}, []);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Token Resolution
|
|
255
|
+
|
|
256
|
+
| Spec token | Code |
|
|
257
|
+
|------------|------|
|
|
258
|
+
| `"Colors.pink_03"` | `Colors.pink_03` (in JSX: `{Colors.pink_03}`) |
|
|
259
|
+
| `"Spacing.L"` | `Spacing.L` (in style: `paddingHorizontal: Spacing.L`) |
|
|
260
|
+
| `"Radius.M"` | `Radius.M` (in style: `borderRadius: Radius.M`) |
|
|
261
|
+
|
|
262
|
+
All tokens import from `@momo-kits/foundation`.
|
|
263
|
+
|
|
264
|
+
### Variable Resolution
|
|
265
|
+
|
|
266
|
+
| Spec value | Code |
|
|
267
|
+
|------------|------|
|
|
268
|
+
| `"{amount}"` | `{amount}` (prop or state in JSX) |
|
|
269
|
+
| `"{onPress}"` | `{onPress}` (callback prop) |
|
|
270
|
+
| `"{items[0]}"` | `{items[0]}` (array access) |
|
|
271
|
+
|
|
272
|
+
### Styles
|
|
273
|
+
|
|
274
|
+
Use `StyleSheet.create` at the bottom of the file. Never inline style objects in JSX — extract to stylesheet.
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
const styles = StyleSheet.create({
|
|
278
|
+
banner: {
|
|
279
|
+
flexDirection: 'row',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
backgroundColor: Colors.green_08,
|
|
282
|
+
paddingHorizontal: Spacing.L,
|
|
283
|
+
gap: Spacing.S,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Compose Multiplatform
|
|
291
|
+
|
|
292
|
+
### File & Imports
|
|
293
|
+
|
|
294
|
+
- `meta.screenName` → file name and `@Composable` function name
|
|
295
|
+
- Import components from `vn.momo.kits.components`
|
|
296
|
+
- Import tokens from `vn.momo.kits.const` (Colors, Spacing, Radius, Typography)
|
|
297
|
+
- Import navigation from `vn.momo.kits.navigation`
|
|
298
|
+
|
|
299
|
+
```kotlin
|
|
300
|
+
import vn.momo.kits.components.*
|
|
301
|
+
import vn.momo.kits.const.Colors
|
|
302
|
+
import vn.momo.kits.const.Spacing
|
|
303
|
+
import vn.momo.kits.const.Radius
|
|
304
|
+
import vn.momo.kits.const.Typography
|
|
305
|
+
import vn.momo.kits.navigation.LocalNavigation
|
|
306
|
+
import vn.momo.kits.navigation.LocalNavigator
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### `meta` → Composable Function
|
|
310
|
+
|
|
311
|
+
```kotlin
|
|
312
|
+
@Composable
|
|
313
|
+
fun MoneyTransferScreen() {
|
|
314
|
+
val navigation = LocalNavigation.current
|
|
315
|
+
val navigator = LocalNavigator.current
|
|
316
|
+
// ...
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### `components` → Composable Sub-Functions
|
|
321
|
+
|
|
322
|
+
Each blueprint becomes a `@Composable` function with a data class for props (if more than 2 params).
|
|
323
|
+
|
|
324
|
+
| Spec field | Code target |
|
|
325
|
+
|------------|-------------|
|
|
326
|
+
| `type` | Function name: `@Composable fun SecurityBanner(...)` |
|
|
327
|
+
| `props` | Function parameters or data class |
|
|
328
|
+
| `styles` | `Modifier` chain |
|
|
329
|
+
| `children` | Composable children in `Column`/`Row`/`Box` |
|
|
330
|
+
|
|
331
|
+
**Prop type mapping:**
|
|
332
|
+
|
|
333
|
+
| Spec type | Kotlin type |
|
|
334
|
+
|-----------|-------------|
|
|
335
|
+
| `"string"` | `String` |
|
|
336
|
+
| `"number"` | `Int` or `Float` |
|
|
337
|
+
| `"boolean"` | `Boolean` |
|
|
338
|
+
| `"function"` | `() -> Unit` |
|
|
339
|
+
| `"array"` | `List<String>` or typed list |
|
|
340
|
+
|
|
341
|
+
**Style mapping:**
|
|
342
|
+
|
|
343
|
+
| Spec style | Compose Modifier/Layout |
|
|
344
|
+
|------------|------------------------|
|
|
345
|
+
| `flexDirection: "row"` | `Row { }` |
|
|
346
|
+
| `flexDirection: "column"` (default) | `Column { }` |
|
|
347
|
+
| `flex: 1` | `Modifier.weight(1f)` |
|
|
348
|
+
| `padding: "Spacing.L"` | `Modifier.padding(Spacing.L)` |
|
|
349
|
+
| `paddingHorizontal: "Spacing.L"` | `Modifier.padding(horizontal = Spacing.L)` |
|
|
350
|
+
| `paddingVertical: "Spacing.S"` | `Modifier.padding(vertical = Spacing.S)` |
|
|
351
|
+
| `gap: "Spacing.S"` | `Arrangement.spacedBy(Spacing.S)` |
|
|
352
|
+
| `backgroundColor: "Colors.green_08"` | `Modifier.background(Colors.green_08)` |
|
|
353
|
+
| `borderRadius: "Radius.M"` | `Modifier.clip(RoundedCornerShape(Radius.M))` |
|
|
354
|
+
| `alignItems: "center"` | `verticalAlignment = Alignment.CenterVertically` (Row) |
|
|
355
|
+
| `justifyContent: "center"` | `horizontalArrangement = Arrangement.Center` (Row) |
|
|
356
|
+
| `alignSelf: "center"` | `Modifier.align(Alignment.CenterHorizontally)` |
|
|
357
|
+
|
|
358
|
+
**Child element mapping:**
|
|
359
|
+
|
|
360
|
+
| Spec `type` | Compose |
|
|
361
|
+
|-------------|---------|
|
|
362
|
+
| `"Icon"` | `Icon(source = "...", size = 24.dp)` |
|
|
363
|
+
| `"Text"` | `Text(text = "...", style = Typography.bodyDefaultRegular)` |
|
|
364
|
+
| `"View"` | `Column { }` / `Row { }` / `Box { }` (based on flexDirection in styles) |
|
|
365
|
+
| `"Pressable"` | `Modifier.activeOpacityClickable { onClick() }` on a Box/Row |
|
|
366
|
+
|
|
367
|
+
**Example:**
|
|
368
|
+
|
|
369
|
+
```kotlin
|
|
370
|
+
@Composable
|
|
371
|
+
fun SecurityBanner(
|
|
372
|
+
description: String,
|
|
373
|
+
aiLabel: String,
|
|
374
|
+
onPressMore: () -> Unit,
|
|
375
|
+
) {
|
|
376
|
+
Row(
|
|
377
|
+
modifier = Modifier
|
|
378
|
+
.fillMaxWidth()
|
|
379
|
+
.background(Colors.green_08)
|
|
380
|
+
.padding(horizontal = Spacing.L, vertical = Spacing.S),
|
|
381
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
382
|
+
horizontalArrangement = Arrangement.spacedBy(Spacing.S),
|
|
383
|
+
) {
|
|
384
|
+
Icon(source = "24_check_circle", size = 24.dp, color = Colors.green_03)
|
|
385
|
+
Text(
|
|
386
|
+
text = description,
|
|
387
|
+
style = Typography.bodyDefaultRegular,
|
|
388
|
+
color = Colors.black_17,
|
|
389
|
+
modifier = Modifier.weight(1f),
|
|
390
|
+
)
|
|
391
|
+
Tag(label = aiLabel, customColor = Colors.pink_03, size = TagSize.Medium)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### `header` → `LaunchedEffect` + `navigation.setOptions`
|
|
397
|
+
|
|
398
|
+
In Compose, header configuration goes into `navigation.setOptions()` inside `LaunchedEffect(Unit)`.
|
|
399
|
+
|
|
400
|
+
| Spec `header.props` | setOptions param | Transform |
|
|
401
|
+
|----------------------|-----------------|-----------|
|
|
402
|
+
| `useBack` | `hiddenBack` | **Invert**: `useBack: true` → `hiddenBack = false` |
|
|
403
|
+
| `headerType` | `headerType` | `HeaderType.Default()` / `HeaderType.Extended()` / `HeaderType.None` |
|
|
404
|
+
| `headerTitle` (string) | `headerTitle` | `HeaderTitle.Default("Title")` |
|
|
405
|
+
| `headerTitle` (user) | `headerTitle` | Custom composable in header |
|
|
406
|
+
| `headerRight` | `headerRight` | `HeaderRight.Toolkit(...)` |
|
|
407
|
+
|
|
408
|
+
```kotlin
|
|
409
|
+
val navigation = LocalNavigation.current
|
|
410
|
+
|
|
411
|
+
LaunchedEffect(Unit) {
|
|
412
|
+
navigation.setOptions(
|
|
413
|
+
headerTitle = HeaderTitle.Default("Transfer"),
|
|
414
|
+
headerType = HeaderType.Extended(),
|
|
415
|
+
hiddenBack = false,
|
|
416
|
+
headerRight = HeaderRight.Toolkit(),
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### `body` → Screen Content
|
|
422
|
+
|
|
423
|
+
| Spec field | Compose |
|
|
424
|
+
|------------|---------|
|
|
425
|
+
| `scrollable: true` | `ScrollData(scrollable = true)` in `setOptions` |
|
|
426
|
+
| `style.backgroundColor` | `backgroundColor` in `setOptions` or `Modifier.background()` |
|
|
427
|
+
| `children` | Composable calls in `Column`/`LazyColumn` |
|
|
428
|
+
|
|
429
|
+
```kotlin
|
|
430
|
+
Column(
|
|
431
|
+
modifier = Modifier
|
|
432
|
+
.fillMaxSize()
|
|
433
|
+
.background(Colors.black_02),
|
|
434
|
+
) {
|
|
435
|
+
SecurityBanner(description = securityText, ...)
|
|
436
|
+
TransferCard(amount = transferAmount, ...)
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
For scrollable content with header collapse:
|
|
441
|
+
|
|
442
|
+
```kotlin
|
|
443
|
+
val listState = rememberLazyListState()
|
|
444
|
+
|
|
445
|
+
LaunchedEffect(Unit) {
|
|
446
|
+
navigation.setOptions(
|
|
447
|
+
scrollData = ScrollData(scrollState = listState),
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
LazyColumn(state = listState) {
|
|
452
|
+
item { SecurityBanner(...) }
|
|
453
|
+
item { TransferCard(...) }
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### `footer` → `footerComponent` in `setOptions`
|
|
458
|
+
|
|
459
|
+
Navigation footer already provides built-in spacing and shadow. **Skip `footer.style` properties like `padding`, `margin`, and `backgroundColor`** — they are redundant.
|
|
460
|
+
|
|
461
|
+
```kotlin
|
|
462
|
+
LaunchedEffect(Unit) {
|
|
463
|
+
navigation.setOptions(
|
|
464
|
+
footerComponent = {
|
|
465
|
+
Button(
|
|
466
|
+
title = buttonTitle,
|
|
467
|
+
type = ButtonType.DISABLED,
|
|
468
|
+
size = Size.LARGE,
|
|
469
|
+
isFull = true,
|
|
470
|
+
onClick = { handleTransfer() },
|
|
471
|
+
)
|
|
472
|
+
},
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
- `footer.style` → **Skip** padding/margin/backgroundColor (footer handles these)
|
|
478
|
+
- Single child → pass directly; multiple children → wrap in `Column`/`Row` without spacing styles
|
|
479
|
+
|
|
480
|
+
### `tabs` → `{ScreenName}Tab` Composable
|
|
481
|
+
|
|
482
|
+
When `tabs` is an object (not `null`), generate a separate `{ScreenName}Tab.kt` file with a `BottomTab` composable.
|
|
483
|
+
|
|
484
|
+
- `tabs.indexActive` determines which item renders the screen from this spec
|
|
485
|
+
- When `tabs.floatingButton` is not empty (`{}`), map it to the `floatingButton` parameter
|
|
486
|
+
|
|
487
|
+
```kotlin
|
|
488
|
+
import vn.momo.kits.navigation.bottomtab.BottomTab
|
|
489
|
+
import vn.momo.kits.navigation.bottomtab.BottomTabItem
|
|
490
|
+
import vn.momo.kits.navigation.bottomtab.BottomTabFloatingButton
|
|
491
|
+
|
|
492
|
+
@Composable
|
|
493
|
+
fun MoneyTransferTab() {
|
|
494
|
+
BottomTab(
|
|
495
|
+
items = listOf(
|
|
496
|
+
BottomTabItem(
|
|
497
|
+
name = "MoneyTransfer",
|
|
498
|
+
label = "Chuyển tiền",
|
|
499
|
+
icon = "ic_transfer",
|
|
500
|
+
screen = { MoneyTransferScreen() },
|
|
501
|
+
),
|
|
502
|
+
BottomTabItem(
|
|
503
|
+
name = "Profile",
|
|
504
|
+
label = "Tôi",
|
|
505
|
+
icon = "ic_profile",
|
|
506
|
+
screen = { ProfileScreen() },
|
|
507
|
+
),
|
|
508
|
+
),
|
|
509
|
+
// tabs.floatingButton → floatingButton prop
|
|
510
|
+
floatingButton = BottomTabFloatingButton(
|
|
511
|
+
icon = "ic_scan",
|
|
512
|
+
label = "Quét mọi QR",
|
|
513
|
+
onPress = { /* navigate to ScanScreen */ },
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### `actions` → Lambda / Remember Handlers
|
|
520
|
+
|
|
521
|
+
```kotlin
|
|
522
|
+
val handleChangeAmount: (String) -> Unit = { value ->
|
|
523
|
+
transferAmount = value
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
val handlePressGreetingCard = {
|
|
527
|
+
navigator.push { GreetingCardPickerScreen() }
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### `localization` → Default State
|
|
532
|
+
|
|
533
|
+
Use `vi` locale values as initial state with `remember` + `mutableStateOf`.
|
|
534
|
+
|
|
535
|
+
```kotlin
|
|
536
|
+
var transferAmount by remember { mutableStateOf("0") }
|
|
537
|
+
val recipientName = "Anh Dũng Hồng"
|
|
538
|
+
val quickMessageList = listOf("Mình chuyển tiền nhé 🤑", "Em cảm ơn ạ! 🤟")
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### `tracking` → Comments
|
|
542
|
+
|
|
543
|
+
```kotlin
|
|
544
|
+
val handleTransfer = {
|
|
545
|
+
// tracking: TransferButtonPressed { amount, message, recipientPhone }
|
|
546
|
+
initiateTransfer(amount, message, recipient)
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Token Resolution
|
|
551
|
+
|
|
552
|
+
| Spec token | Compose code |
|
|
553
|
+
|------------|-------------|
|
|
554
|
+
| `"Colors.pink_03"` | `Colors.pink_03` |
|
|
555
|
+
| `"Spacing.L"` | `Spacing.L` (already Dp — do NOT call `.dp`) |
|
|
556
|
+
| `"Radius.M"` | `Radius.M` |
|
|
557
|
+
|
|
558
|
+
Import from `vn.momo.kits.const`.
|
|
559
|
+
|
|
560
|
+
### Variable Resolution
|
|
561
|
+
|
|
562
|
+
| Spec value | Compose code |
|
|
563
|
+
|------------|-------------|
|
|
564
|
+
| `"{amount}"` | `amount` (parameter or state) |
|
|
565
|
+
| `"{onPress}"` | `onPress()` (lambda invocation) |
|
|
566
|
+
| `"{items[0]}"` | `items[0]` (list access) |
|
|
567
|
+
|
|
568
|
+
### Typography Mapping
|
|
569
|
+
|
|
570
|
+
Spec typography tokens map to Compose `Typography` object properties using camelCase:
|
|
571
|
+
|
|
572
|
+
| Spec token | Compose |
|
|
573
|
+
|------------|---------|
|
|
574
|
+
| `"body_default_regular"` | `Typography.bodyDefaultRegular` |
|
|
575
|
+
| `"header_s_semibold"` | `Typography.headerSSemibold` |
|
|
576
|
+
| `"description_default_regular"` | `Typography.descriptionDefaultRegular` |
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Quick Reference: RN vs Compose Side-by-Side
|
|
581
|
+
|
|
582
|
+
| Concept | React Native | Compose |
|
|
583
|
+
|---------|-------------|---------|
|
|
584
|
+
| Screen wrapper | `<Screen ref={screenRef}>` | `Column` / `LazyColumn` + `setOptions` |
|
|
585
|
+
| Header config | `screenRef.current?.setOptions()` in `useEffect` | `navigation.setOptions()` in `LaunchedEffect` |
|
|
586
|
+
| Footer | `<Screen footerComponent={fn}>` | `footerComponent` in `setOptions` |
|
|
587
|
+
| Styles | `StyleSheet.create({})` | `Modifier` chain |
|
|
588
|
+
| State | `useState` | `mutableStateOf` + `remember` |
|
|
589
|
+
| Handlers | `useCallback` | Lambda or local function |
|
|
590
|
+
| Row layout | `<View style={{ flexDirection: 'row' }}>` | `Row { }` |
|
|
591
|
+
| Flex grow | `style={{ flex: 1 }}` | `Modifier.weight(1f)` |
|
|
592
|
+
| Gap | `style={{ gap: Spacing.S }}` | `Arrangement.spacedBy(Spacing.S)` |
|
|
593
|
+
| Token import | `@momo-kits/foundation` | `vn.momo.kits.const` |
|
|
594
|
+
| Component import | `@momo-kits/foundation` or `@momo-kits/<lib>` | `vn.momo.kits.components` |
|
|
595
|
+
| Navigation | `navigation` prop | `LocalNavigation.current` |
|
|
596
|
+
| Navigator | `ApplicationContext.navigator` | `LocalNavigator.current` |
|