@vetc-miniapp/ui-react 0.0.23 → 0.0.25
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/README.md +375 -56
- package/dist/bridge.d.ts +11 -0
- package/dist/bridge.js +20 -0
- package/dist/components/app.d.ts +6 -0
- package/dist/components/app.js +34 -0
- package/dist/components/avatar/Avatar.d.ts +21 -0
- package/dist/components/avatar/Avatar.js +33 -0
- package/dist/components/avatar/index.d.ts +2 -0
- package/dist/components/avatar/index.js +1 -0
- package/dist/components/bottom-sheet/BottomSheet.d.ts +19 -0
- package/dist/components/bottom-sheet/BottomSheet.js +70 -0
- package/dist/components/bottom-sheet/index.d.ts +2 -0
- package/dist/components/bottom-sheet/index.js +1 -0
- package/dist/components/button/Button.d.ts +32 -0
- package/dist/components/button/Button.js +165 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/button/index.js +1 -0
- package/dist/components/button-group/ButtonGroup.d.ts +28 -0
- package/dist/components/button-group/ButtonGroup.js +21 -0
- package/dist/components/button-group/index.d.ts +2 -0
- package/dist/components/button-group/index.js +1 -0
- package/dist/components/card/Card.d.ts +18 -0
- package/dist/components/card/Card.js +35 -0
- package/dist/components/card/index.d.ts +2 -0
- package/dist/components/card/index.js +1 -0
- package/dist/components/checkbox/Checkbox.d.ts +41 -0
- package/dist/components/checkbox/Checkbox.js +94 -0
- package/dist/components/checkbox/index.d.ts +2 -0
- package/dist/components/checkbox/index.js +1 -0
- package/dist/components/chip/Chip.d.ts +24 -0
- package/dist/components/chip/Chip.js +83 -0
- package/dist/components/chip/index.d.ts +2 -0
- package/dist/components/chip/index.js +1 -0
- package/dist/components/dialog/Dialog.d.ts +19 -0
- package/dist/components/dialog/Dialog.js +51 -0
- package/dist/components/dialog/index.d.ts +2 -0
- package/dist/components/dialog/index.js +1 -0
- package/dist/components/divider/Divider.d.ts +16 -0
- package/dist/components/divider/Divider.js +18 -0
- package/dist/components/divider/index.d.ts +2 -0
- package/dist/components/divider/index.js +1 -0
- package/dist/components/input/Input.d.ts +40 -0
- package/dist/components/input/Input.js +51 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/index.js +1 -0
- package/dist/components/list/List.d.ts +31 -0
- package/dist/components/list/List.js +72 -0
- package/dist/components/list/index.d.ts +2 -0
- package/dist/components/list/index.js +1 -0
- package/dist/components/loading/Loading.d.ts +28 -0
- package/dist/components/loading/Loading.js +33 -0
- package/dist/components/loading/index.d.ts +2 -0
- package/dist/components/loading/index.js +1 -0
- package/dist/components/modal/Modal.d.ts +38 -0
- package/dist/components/modal/Modal.js +50 -0
- package/dist/components/modal/index.d.ts +2 -0
- package/dist/components/modal/index.js +1 -0
- package/dist/components/navigation-bar/NavigationBar.d.ts +44 -0
- package/dist/components/navigation-bar/NavigationBar.js +70 -0
- package/dist/components/navigation-bar/index.d.ts +2 -0
- package/dist/components/navigation-bar/index.js +1 -0
- package/dist/components/radio/Radio.d.ts +40 -0
- package/dist/components/radio/Radio.js +88 -0
- package/dist/components/radio/index.d.ts +2 -0
- package/dist/components/radio/index.js +1 -0
- package/dist/components/select/Select.d.ts +29 -0
- package/dist/components/select/Select.js +30 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +1 -0
- package/dist/components/switch/Switch.d.ts +23 -0
- package/dist/components/switch/Switch.js +81 -0
- package/dist/components/switch/index.d.ts +2 -0
- package/dist/components/switch/index.js +1 -0
- package/dist/components/tab-bar/TabBar.d.ts +28 -0
- package/dist/components/tab-bar/TabBar.js +60 -0
- package/dist/components/tab-bar/index.d.ts +2 -0
- package/dist/components/tab-bar/index.js +1 -0
- package/dist/components/textarea/Textarea.d.ts +31 -0
- package/dist/components/textarea/Textarea.js +33 -0
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +1 -0
- package/dist/components/toast/Toast.d.ts +41 -0
- package/dist/components/toast/Toast.js +61 -0
- package/dist/components/toast/index.d.ts +2 -0
- package/dist/components/toast/index.js +1 -0
- package/dist/components/typography/Typography.d.ts +45 -0
- package/dist/components/typography/Typography.js +143 -0
- package/dist/components/typography/index.d.ts +2 -0
- package/dist/components/typography/index.js +1 -0
- package/dist/hooks/use-app-pause.d.ts +6 -0
- package/dist/hooks/use-app-pause.js +29 -0
- package/dist/hooks/use-app-resume.d.ts +6 -0
- package/dist/hooks/use-app-resume.js +28 -0
- package/{src/ui-react/hooks/use-app-state.ts → dist/hooks/use-app-state.d.ts} +0 -1
- package/dist/hooks/use-app-state.js +1 -0
- package/dist/hooks/use-did-hide.d.ts +6 -0
- package/dist/hooks/use-did-hide.js +21 -0
- package/dist/hooks/use-did-show.d.ts +6 -0
- package/dist/hooks/use-did-show.js +21 -0
- package/dist/hooks/use-listener-scan-qr.d.ts +21 -0
- package/dist/hooks/use-listener-scan-qr.js +29 -0
- package/dist/hooks/use-navigate.d.ts +8 -0
- package/dist/hooks/use-navigate.js +23 -0
- package/dist/hooks/use-tap-app-bar.d.ts +6 -0
- package/dist/hooks/use-tap-app-bar.js +21 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +41 -0
- package/dist/styles/VETCProvider.d.ts +114 -0
- package/dist/styles/VETCProvider.js +124 -0
- package/dist/styles/tokens.css +448 -0
- package/dist/tokens/colors.d.ts +127 -0
- package/dist/tokens/colors.js +75 -0
- package/dist/tokens/index.d.ts +3 -0
- package/dist/tokens/index.js +3 -0
- package/dist/tokens/spacing.d.ts +56 -0
- package/dist/tokens/spacing.js +56 -0
- package/dist/tokens/typography.d.ts +121 -0
- package/dist/tokens/typography.js +57 -0
- package/dist/types/app.d.ts +21 -0
- package/dist/types/app.js +1 -0
- package/package.json +26 -8
- package/src/dist/ui-react/index.js +0 -2
- package/src/dist/ui-react/index.js.LICENSE.txt +0 -11
- package/src/ui-react/bridge.js +0 -36
- package/src/ui-react/bridge.ts +0 -48
- package/src/ui-react/components/app.d.ts +0 -7
- package/src/ui-react/components/app.jsx +0 -80
- package/src/ui-react/components/app.tsx +0 -42
- package/src/ui-react/components/app1.js +0 -101
- package/src/ui-react/hooks/use-app-pause.js +0 -35
- package/src/ui-react/hooks/use-app-pause.ts +0 -33
- package/src/ui-react/hooks/use-app-resume.js +0 -37
- package/src/ui-react/hooks/use-app-resume.ts +0 -32
- package/src/ui-react/hooks/use-app-state.js +0 -35
- package/src/ui-react/hooks/use-did-hide.js +0 -25
- package/src/ui-react/hooks/use-did-hide.ts +0 -34
- package/src/ui-react/hooks/use-did-show.js +0 -26
- package/src/ui-react/hooks/use-did-show.ts +0 -34
- package/src/ui-react/hooks/use-listener-scan-qr.js +0 -33
- package/src/ui-react/hooks/use-listener-scan-qr.ts +0 -52
- package/src/ui-react/hooks/use-navigate.js +0 -15
- package/src/ui-react/hooks/use-navigate.ts +0 -41
- package/src/ui-react/index.js +0 -8
- package/src/ui-react/index.ts +0 -9
- package/src/ui-react/types/app.js +0 -30
- package/src/ui-react/types/app.ts +0 -32
package/README.md
CHANGED
|
@@ -1,102 +1,421 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vetc-miniapp/ui-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Bộ UI component React cho VETC MiniApp Platform, được xây dựng theo design system **Tasco DLS Mini App** từ Figma, sử dụng Ant Design v6 làm base và override hoàn toàn theo VETC design token.
|
|
4
|
+
|
|
5
|
+
---
|
|
4
6
|
|
|
5
7
|
## Cài đặt
|
|
6
8
|
|
|
7
9
|
```bash
|
|
10
|
+
npm install antd @ant-design/icons
|
|
8
11
|
npm install @vetc-miniapp/ui-react
|
|
9
12
|
```
|
|
10
|
-
## Sử dụng
|
|
11
|
-
# useNavigate – Điều hướng Mini App qua Native (Flutter)
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
## Sử dụng cơ bản
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
```tsx
|
|
17
|
+
// 1. Import CSS tokens ở app root (1 lần duy nhất)
|
|
18
|
+
import '@vetc-miniapp/ui-react/src/ui-react/styles/tokens.css';
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// 2. Wrap app với VETCProvider
|
|
21
|
+
import { VETCProvider } from '@vetc-miniapp/ui-react';
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<VETCProvider>
|
|
26
|
+
<YourApp />
|
|
27
|
+
</VETCProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
20
31
|
|
|
21
32
|
---
|
|
22
33
|
|
|
23
|
-
##
|
|
34
|
+
## Components
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
import { useNavigate } from '@vetc-miniapp/ui-react';
|
|
36
|
+
### Button
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
```tsx
|
|
39
|
+
import { Button } from '@vetc-miniapp/ui-react';
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
title: string,
|
|
33
|
-
params?: Record<string, any>,
|
|
34
|
-
options?: {
|
|
35
|
-
replace?: boolean;
|
|
36
|
-
popTo?: string;
|
|
37
|
-
}
|
|
38
|
-
): void
|
|
41
|
+
// Primary (default) — brand green
|
|
42
|
+
<Button variant="primary" size="lg">Xác nhận</Button>
|
|
39
43
|
|
|
44
|
+
// Secondary — outlined
|
|
45
|
+
<Button variant="secondary" size="lg">Hủy</Button>
|
|
46
|
+
|
|
47
|
+
// Tertiary — ghost/text
|
|
48
|
+
<Button variant="tertiary">Xem thêm</Button>
|
|
49
|
+
|
|
50
|
+
// Danger
|
|
51
|
+
<Button variant="danger">Xóa</Button>
|
|
52
|
+
|
|
53
|
+
// Pill shape
|
|
54
|
+
<Button variant="primary" shape="pill">Tìm kiếm</Button>
|
|
55
|
+
|
|
56
|
+
// Small size (32px)
|
|
57
|
+
<Button variant="primary" size="sm">OK</Button>
|
|
58
|
+
|
|
59
|
+
// Loading & disabled
|
|
60
|
+
<Button loading>Đang xử lý...</Button>
|
|
61
|
+
<Button disabled>Không khả dụng</Button>
|
|
40
62
|
```
|
|
41
63
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
**Props:** `variant` (primary|secondary|tertiary|danger), `size` (lg|sm), `shape` (rounded|pill), `block`, `disabled`, `loading`, `icon`, `iconRight`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Typography
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Display, Headline, Title, Label, Body, Typography } from '@vetc-miniapp/ui-react';
|
|
72
|
+
|
|
73
|
+
<Display level="2xl">Tiêu đề lớn</Display>
|
|
74
|
+
<Headline level="xl">Headline</Headline>
|
|
75
|
+
<Title size="base">Tiêu đề phần</Title>
|
|
76
|
+
<Label size="sm" color="secondary">Nhãn</Label>
|
|
77
|
+
<Body size="base" color="primary">Nội dung văn bản</Body>
|
|
78
|
+
|
|
79
|
+
// Màu sắc: primary | secondary | disabled | brand | error | warning | success | inherit
|
|
80
|
+
<Body color="error">Lỗi rồi!</Body>
|
|
81
|
+
|
|
82
|
+
// Clamp lines
|
|
83
|
+
<Body lines={2}>Nội dung dài sẽ bị cắt sau 2 dòng...</Body>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### Input / TextField
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { Input, PasswordInput } from '@vetc-miniapp/ui-react';
|
|
92
|
+
|
|
93
|
+
// Default
|
|
94
|
+
<Input label="Số điện thoại" placeholder="0901 234 567" />
|
|
95
|
+
|
|
96
|
+
// Required + error
|
|
97
|
+
<Input
|
|
98
|
+
label="Email"
|
|
99
|
+
required
|
|
100
|
+
error="Email không hợp lệ"
|
|
101
|
+
value={email}
|
|
102
|
+
onChange={setEmail}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
// With icons
|
|
106
|
+
<Input prefix={<PhoneIcon />} suffix={<ClearIcon />} allowClear />
|
|
107
|
+
|
|
108
|
+
// Password
|
|
109
|
+
<PasswordInput label="Mật khẩu" placeholder="Nhập mật khẩu" />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Textarea
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { Textarea } from '@vetc-miniapp/ui-react';
|
|
118
|
+
|
|
119
|
+
<Textarea
|
|
120
|
+
label="Ghi chú"
|
|
121
|
+
placeholder="Nhập nội dung..."
|
|
122
|
+
rows={4}
|
|
123
|
+
maxLength={500}
|
|
124
|
+
showCount
|
|
125
|
+
/>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### Select
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { Select } from '@vetc-miniapp/ui-react';
|
|
134
|
+
|
|
135
|
+
<Select
|
|
136
|
+
label="Tỉnh/Thành phố"
|
|
137
|
+
placeholder="Chọn tỉnh thành"
|
|
138
|
+
options={[
|
|
139
|
+
{ label: 'Hà Nội', value: 'HN' },
|
|
140
|
+
{ label: 'TP.HCM', value: 'HCM' },
|
|
141
|
+
]}
|
|
142
|
+
onChange={(val) => console.log(val)}
|
|
143
|
+
/>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Checkbox / Radio / Switch
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Checkbox, CheckboxGroup, Radio, RadioGroup, Switch } from '@vetc-miniapp/ui-react';
|
|
152
|
+
|
|
153
|
+
// Checkbox
|
|
154
|
+
<Checkbox label="Đồng ý điều khoản" checked={agreed} onChange={setAgreed} />
|
|
155
|
+
|
|
156
|
+
// Checkbox Group
|
|
157
|
+
<CheckboxGroup
|
|
158
|
+
options={[
|
|
159
|
+
{ label: 'Tùy chọn A', value: 'a' },
|
|
160
|
+
{ label: 'Tùy chọn B', value: 'b' },
|
|
161
|
+
]}
|
|
162
|
+
value={selected}
|
|
163
|
+
onChange={setSelected}
|
|
164
|
+
direction="vertical"
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
// Radio Group
|
|
168
|
+
<RadioGroup
|
|
169
|
+
options={[{ label: 'Nam', value: 'male' }, { label: 'Nữ', value: 'female' }]}
|
|
170
|
+
value={gender}
|
|
171
|
+
onChange={setGender}
|
|
172
|
+
/>
|
|
173
|
+
|
|
174
|
+
// Switch
|
|
175
|
+
<Switch label="Bật thông báo" checked={notif} onChange={setNotif} />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### Chip / Tag
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { Chip } from '@vetc-miniapp/ui-react';
|
|
184
|
+
|
|
185
|
+
// Types: default | brand | positive | negative | warning | info
|
|
186
|
+
// Variants: tinted | outlined | filled
|
|
187
|
+
<Chip type="positive" variant="tinted">Hoàn thành</Chip>
|
|
188
|
+
<Chip type="negative" variant="filled">Lỗi</Chip>
|
|
189
|
+
<Chip type="warning" variant="outlined" closable onClose={...}>Cảnh báo</Chip>
|
|
190
|
+
|
|
191
|
+
// Small
|
|
192
|
+
<Chip size="sm" type="info">Mới</Chip>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### Avatar
|
|
46
198
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</button>
|
|
199
|
+
```tsx
|
|
200
|
+
import { Avatar } from '@vetc-miniapp/ui-react';
|
|
50
201
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
202
|
+
// Image
|
|
203
|
+
<Avatar src="https://..." alt="User" size="md" />
|
|
204
|
+
|
|
205
|
+
// Initials
|
|
206
|
+
<Avatar initials="Nguyễn Văn A" size="lg" color="#25a45e" />
|
|
207
|
+
|
|
208
|
+
// Sizes: xl(56) | lg(48) | md(40) | sm(32) | xs(24)
|
|
59
209
|
```
|
|
60
210
|
|
|
61
|
-
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### Card
|
|
62
214
|
|
|
63
|
-
|
|
215
|
+
```tsx
|
|
216
|
+
import { Card } from '@vetc-miniapp/ui-react';
|
|
64
217
|
|
|
65
|
-
|
|
218
|
+
// Elevations: outlined | raised | flat
|
|
219
|
+
<Card elevation="outlined" title="Thông tin xe" extra={<Button size="sm">Sửa</Button>}>
|
|
220
|
+
Nội dung card...
|
|
221
|
+
</Card>
|
|
66
222
|
```
|
|
67
|
-
navigate('/login', { mode: 'replace' });
|
|
68
223
|
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
### List / ListItem
|
|
69
227
|
|
|
70
|
-
|
|
228
|
+
```tsx
|
|
229
|
+
import { List, ListItem } from '@vetc-miniapp/ui-react';
|
|
230
|
+
|
|
231
|
+
<List
|
|
232
|
+
bordered
|
|
233
|
+
items={vehicles}
|
|
234
|
+
renderItem={(v) => (
|
|
235
|
+
<ListItem
|
|
236
|
+
leading={<CarIcon />}
|
|
237
|
+
title={v.plate}
|
|
238
|
+
description={v.type}
|
|
239
|
+
arrow
|
|
240
|
+
onClick={() => navigate(v.id)}
|
|
241
|
+
/>
|
|
242
|
+
)}
|
|
243
|
+
/>
|
|
71
244
|
```
|
|
72
|
-
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Loading
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
import { Spinner, SkeletonLoader } from '@vetc-miniapp/ui-react';
|
|
252
|
+
|
|
253
|
+
// Spinner inline
|
|
254
|
+
<Spinner size="md" />
|
|
255
|
+
|
|
256
|
+
// Full-screen overlay
|
|
257
|
+
<Spinner fullscreen spinning={isLoading} />
|
|
258
|
+
|
|
259
|
+
// Spinner wrapping content
|
|
260
|
+
<Spinner spinning={loading}>
|
|
261
|
+
<Content />
|
|
262
|
+
</Spinner>
|
|
263
|
+
|
|
264
|
+
// Skeleton
|
|
265
|
+
<SkeletonLoader loading={loading} rows={3} avatar>
|
|
266
|
+
<ActualContent />
|
|
267
|
+
</SkeletonLoader>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Toast
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import { toast, useToast } from '@vetc-miniapp/ui-react';
|
|
276
|
+
|
|
277
|
+
// Static (simple)
|
|
278
|
+
toast.success('Đăng ký thành công!');
|
|
279
|
+
toast.error('Có lỗi xảy ra', 'Vui lòng thử lại');
|
|
280
|
+
toast.warning('Cảnh báo');
|
|
281
|
+
toast.info('Thông tin');
|
|
282
|
+
|
|
283
|
+
// Hook-based (recommended)
|
|
284
|
+
function MyComponent() {
|
|
285
|
+
const { toast, contextHolder } = useToast();
|
|
286
|
+
return (
|
|
287
|
+
<>
|
|
288
|
+
{contextHolder}
|
|
289
|
+
<Button onClick={() => toast.success('OK!')}>Thông báo</Button>
|
|
290
|
+
</>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
73
293
|
```
|
|
74
294
|
|
|
75
|
-
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### Modal / Dialog
|
|
76
298
|
|
|
77
|
-
|
|
78
|
-
|
|
299
|
+
```tsx
|
|
300
|
+
import { Modal, useConfirm } from '@vetc-miniapp/ui-react';
|
|
79
301
|
|
|
302
|
+
// Controlled modal
|
|
303
|
+
<Modal
|
|
304
|
+
open={isOpen}
|
|
305
|
+
title="Xác nhận thanh toán"
|
|
306
|
+
okText="Thanh toán"
|
|
307
|
+
cancelText="Hủy"
|
|
308
|
+
onOk={handlePay}
|
|
309
|
+
onCancel={() => setOpen(false)}
|
|
310
|
+
>
|
|
311
|
+
Bạn có chắc muốn thanh toán 50,000đ?
|
|
312
|
+
</Modal>
|
|
313
|
+
|
|
314
|
+
// Confirm dialog
|
|
315
|
+
function MyComp() {
|
|
316
|
+
const { confirm } = useConfirm();
|
|
80
317
|
return (
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
318
|
+
<Button onClick={() => confirm({
|
|
319
|
+
title: 'Xóa phương tiện?',
|
|
320
|
+
content: 'Hành động này không thể hoàn tác.',
|
|
321
|
+
okDanger: true,
|
|
322
|
+
onOk: handleDelete,
|
|
323
|
+
})}>
|
|
324
|
+
Xóa
|
|
325
|
+
</Button>
|
|
84
326
|
);
|
|
85
327
|
}
|
|
86
328
|
```
|
|
87
329
|
|
|
88
|
-
|
|
89
|
-
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
### Bottom Sheet
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
import { BottomSheet } from '@vetc-miniapp/ui-react';
|
|
336
|
+
|
|
337
|
+
<BottomSheet
|
|
338
|
+
open={isOpen}
|
|
339
|
+
title="Chọn phương thức"
|
|
340
|
+
onClose={() => setOpen(false)}
|
|
341
|
+
>
|
|
342
|
+
<ListItem title="Thẻ ngân hàng" arrow onClick={...} />
|
|
343
|
+
<ListItem title="Ví điện tử" arrow onClick={...} />
|
|
344
|
+
</BottomSheet>
|
|
90
345
|
```
|
|
91
346
|
|
|
92
|
-
|
|
93
|
-
import { useNavigate } from 'react-router-dom';
|
|
347
|
+
---
|
|
94
348
|
|
|
349
|
+
### NavigationBar (Top Nav)
|
|
95
350
|
|
|
96
|
-
|
|
351
|
+
```tsx
|
|
352
|
+
import { NavigationBar } from '@vetc-miniapp/ui-react';
|
|
353
|
+
|
|
354
|
+
<NavigationBar
|
|
355
|
+
title="Chi tiết giao dịch"
|
|
356
|
+
showBack
|
|
357
|
+
onBack={() => history.back()}
|
|
358
|
+
actions={[
|
|
359
|
+
{ icon: <ShareIcon />, label: 'Chia sẻ', onClick: handleShare },
|
|
360
|
+
{ icon: <MoreIcon />, label: 'Thêm', onClick: handleMore },
|
|
361
|
+
]}
|
|
362
|
+
/>
|
|
97
363
|
```
|
|
98
364
|
|
|
99
|
-
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### TabBar (Bottom Nav)
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { TabBar } from '@vetc-miniapp/ui-react';
|
|
371
|
+
|
|
372
|
+
<TabBar
|
|
373
|
+
activeKey={activeTab}
|
|
374
|
+
onChange={setActiveTab}
|
|
375
|
+
items={[
|
|
376
|
+
{ key: 'home', label: 'Trang chủ', icon: <HomeIcon /> },
|
|
377
|
+
{ key: 'history', label: 'Lịch sử', icon: <HistoryIcon />, badge: 3 },
|
|
378
|
+
{ key: 'profile', label: 'Tài khoản', icon: <UserIcon /> },
|
|
379
|
+
]}
|
|
380
|
+
/>
|
|
100
381
|
```
|
|
101
|
-
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Design Tokens
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { colorAlias, colorGlobal, fontSize, spacing, borderRadius } from '@vetc-miniapp/ui-react';
|
|
389
|
+
|
|
390
|
+
// Colors
|
|
391
|
+
colorAlias.brand // #25a45e
|
|
392
|
+
colorAlias.negative // #da2c39
|
|
393
|
+
colorAlias.warning // #ff8c2f
|
|
394
|
+
|
|
395
|
+
// Typography
|
|
396
|
+
fontSize.base // '16px'
|
|
397
|
+
fontSize.sm // '13px'
|
|
398
|
+
|
|
399
|
+
// Spacing
|
|
400
|
+
spacing[16] // '16px'
|
|
401
|
+
|
|
402
|
+
// Border radius
|
|
403
|
+
borderRadius.md // '8px'
|
|
404
|
+
borderRadius.xl // '16px'
|
|
405
|
+
borderRadius.pill // '1000px'
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Theming
|
|
411
|
+
|
|
412
|
+
`VETCProvider` tự động inject Ant Design ConfigProvider với toàn bộ VETC token. Bạn có thể extend theme:
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
import { VETCProvider, vetcAntdTheme } from '@vetc-miniapp/ui-react';
|
|
416
|
+
import { ConfigProvider } from 'antd';
|
|
417
|
+
|
|
418
|
+
<ConfigProvider theme={{ ...vetcAntdTheme, token: { ...vetcAntdTheme.token, colorPrimary: '#custom' } }}>
|
|
419
|
+
<App />
|
|
420
|
+
</ConfigProvider>
|
|
102
421
|
```
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FlutterInAppWebViewBridge {
|
|
2
|
+
callHandler(handlerName: string, args?: unknown): Promise<unknown>;
|
|
3
|
+
}
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
flutter_inappwebview?: FlutterInAppWebViewBridge;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare const isBrowser: boolean;
|
|
10
|
+
export declare const getBridge: () => FlutterInAppWebViewBridge | null;
|
|
11
|
+
export declare const callHost: <T = unknown>(action: string, payload?: Record<string, unknown>) => Promise<T>;
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* ================= Types ================= */
|
|
2
|
+
/* ================= Runtime helpers ================= */
|
|
3
|
+
export const isBrowser = typeof window !== "undefined";
|
|
4
|
+
export const getBridge = () => {
|
|
5
|
+
if (!isBrowser || !window.flutter_inappwebview || !window.flutter_inappwebview.callHandler || typeof window.flutter_inappwebview.callHandler != "function") {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return window.flutter_inappwebview;
|
|
9
|
+
};
|
|
10
|
+
/* ================= Core bridge call ================= */
|
|
11
|
+
export const callHost = (action, payload = {}) => {
|
|
12
|
+
const bridge = getBridge();
|
|
13
|
+
if (!bridge) {
|
|
14
|
+
return Promise.resolve({ ok: false, error: "BRIDGE_CALL_FAILED" });
|
|
15
|
+
}
|
|
16
|
+
return bridge.callHandler("MiniAppBridge", {
|
|
17
|
+
action,
|
|
18
|
+
payload,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { callHost } from "../bridge";
|
|
4
|
+
let routeMap = new Map();
|
|
5
|
+
let currentPath = "/";
|
|
6
|
+
export function initRouter(config) {
|
|
7
|
+
routeMap = new Map(config.pages.map(p => [p.pathname, p]));
|
|
8
|
+
}
|
|
9
|
+
export function getCurrentPage() {
|
|
10
|
+
return routeMap.get(currentPath);
|
|
11
|
+
}
|
|
12
|
+
export function setCurrentPath(path) {
|
|
13
|
+
currentPath = path;
|
|
14
|
+
}
|
|
15
|
+
export function App({ config }) {
|
|
16
|
+
const [page, setPage] = useState(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
initRouter(config);
|
|
19
|
+
const rawPath = window.location.pathname || "/";
|
|
20
|
+
const path = rawPath.replace("/miniapp", "") || "/";
|
|
21
|
+
setCurrentPath(path);
|
|
22
|
+
let current = getCurrentPage();
|
|
23
|
+
if (!current) {
|
|
24
|
+
setCurrentPath("/");
|
|
25
|
+
current = getCurrentPage();
|
|
26
|
+
}
|
|
27
|
+
setPage(current);
|
|
28
|
+
callHost("registerAppConfig", { config });
|
|
29
|
+
}, [config]);
|
|
30
|
+
if (!page)
|
|
31
|
+
return _jsx("div", {});
|
|
32
|
+
const Component = page.Component;
|
|
33
|
+
return _jsx(Component, {});
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VETC Avatar — tokenized
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export type AvatarSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
|
6
|
+
export type AvatarShape = 'circle' | 'square';
|
|
7
|
+
export interface AvatarProps {
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
initials?: string;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
size?: AvatarSize;
|
|
13
|
+
shape?: AvatarShape;
|
|
14
|
+
color?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
id?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function Avatar({ src, alt, initials, icon, size, shape, color, className, style, onClick, id, }: AvatarProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export default Avatar;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar as AntAvatar } from 'antd';
|
|
3
|
+
// Map size token name to CSS var
|
|
4
|
+
const sizeVarMap = {
|
|
5
|
+
xl: 'var(--vetc-avatar-size-xl)',
|
|
6
|
+
lg: 'var(--vetc-avatar-size-lg)',
|
|
7
|
+
md: 'var(--vetc-avatar-size-md)',
|
|
8
|
+
sm: 'var(--vetc-avatar-size-sm)',
|
|
9
|
+
xs: 'var(--vetc-avatar-size-xs)',
|
|
10
|
+
};
|
|
11
|
+
// Pixel values needed for antd size prop (numeric)
|
|
12
|
+
const sizePxMap = { xl: 56, lg: 48, md: 40, sm: 32, xs: 24 };
|
|
13
|
+
// Font size scales with avatar size (from Figma)
|
|
14
|
+
const fontSizeMap = {
|
|
15
|
+
xl: 'var(--vetc-font-size-2xl)',
|
|
16
|
+
lg: 'var(--vetc-font-size-xl)',
|
|
17
|
+
md: 'var(--vetc-font-size-base)',
|
|
18
|
+
sm: 'var(--vetc-font-size-sm)',
|
|
19
|
+
xs: 'var(--vetc-font-size-2xs)',
|
|
20
|
+
};
|
|
21
|
+
export function Avatar({ src, alt, initials, icon, size = 'md', shape = 'circle', color, className = '', style, onClick, id, }) {
|
|
22
|
+
return (_jsx("span", { id: id, style: { display: 'inline-flex', flexShrink: 0, width: sizeVarMap[size], height: sizeVarMap[size] }, children: _jsx(AntAvatar, { src: src, alt: alt, icon: !src && !initials ? icon : undefined, size: sizePxMap[size], shape: shape, onClick: onClick, className: `vetc-avatar vetc-avatar--${size} ${className}`, style: {
|
|
23
|
+
backgroundColor: !src ? (color ?? 'var(--vetc-avatar-bg-default)') : undefined,
|
|
24
|
+
fontSize: fontSizeMap[size],
|
|
25
|
+
fontWeight: 'var(--vetc-font-weight-bold)',
|
|
26
|
+
fontFamily: 'var(--vetc-font-family)',
|
|
27
|
+
color: 'var(--vetc-avatar-text-color)',
|
|
28
|
+
cursor: onClick ? 'pointer' : 'default',
|
|
29
|
+
flexShrink: 0,
|
|
30
|
+
...style,
|
|
31
|
+
}, children: !src && initials ? initials.slice(0, 2).toUpperCase() : undefined }) }));
|
|
32
|
+
}
|
|
33
|
+
export default Avatar;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Avatar } from './Avatar';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VETC BottomSheet — tokenized
|
|
3
|
+
* Figma: Sheet page — 16px top radius, handle bar, slide-up animation
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
export interface BottomSheetProps {
|
|
7
|
+
open: boolean;
|
|
8
|
+
title?: React.ReactNode;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
maskClosable?: boolean;
|
|
11
|
+
showHandle?: boolean;
|
|
12
|
+
maxHeight?: string;
|
|
13
|
+
onClose?: () => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
style?: React.CSSProperties;
|
|
16
|
+
id?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function BottomSheet({ open, title, children, maskClosable, showHandle, maxHeight, onClose, className, style, id, }: BottomSheetProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export default BottomSheet;
|