@vetc-miniapp/ui-react 0.0.23 → 0.0.24
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/package.json +17 -5
- package/src/dist/ui-react/index.js +1 -1
- package/src/ui-react/components/avatar/Avatar.tsx +88 -0
- package/src/ui-react/components/avatar/index.ts +2 -0
- package/src/ui-react/components/bottom-sheet/BottomSheet.tsx +149 -0
- package/src/ui-react/components/bottom-sheet/index.ts +2 -0
- package/src/ui-react/components/button/Button.tsx +246 -0
- package/src/ui-react/components/button/index.ts +2 -0
- package/src/ui-react/components/button-group/ButtonGroup.tsx +108 -0
- package/src/ui-react/components/button-group/index.ts +2 -0
- package/src/ui-react/components/card/Card.tsx +77 -0
- package/src/ui-react/components/card/index.ts +2 -0
- package/src/ui-react/components/checkbox/Checkbox.tsx +232 -0
- package/src/ui-react/components/checkbox/index.ts +2 -0
- package/src/ui-react/components/chip/Chip.tsx +137 -0
- package/src/ui-react/components/chip/index.ts +2 -0
- package/src/ui-react/components/dialog/Dialog.tsx +135 -0
- package/src/ui-react/components/dialog/index.ts +2 -0
- package/src/ui-react/components/divider/Divider.tsx +54 -0
- package/src/ui-react/components/divider/index.ts +2 -0
- package/src/ui-react/components/input/Input.tsx +195 -0
- package/src/ui-react/components/input/index.ts +2 -0
- package/src/ui-react/components/list/List.tsx +180 -0
- package/src/ui-react/components/list/index.ts +2 -0
- package/src/ui-react/components/loading/Loading.tsx +121 -0
- package/src/ui-react/components/loading/index.ts +2 -0
- package/src/ui-react/components/modal/Modal.tsx +116 -0
- package/src/ui-react/components/modal/index.ts +2 -0
- package/src/ui-react/components/navigation-bar/NavigationBar.tsx +188 -0
- package/src/ui-react/components/navigation-bar/index.ts +2 -0
- package/src/ui-react/components/radio/Radio.tsx +216 -0
- package/src/ui-react/components/radio/index.ts +2 -0
- package/src/ui-react/components/select/Select.tsx +109 -0
- package/src/ui-react/components/select/index.ts +2 -0
- package/src/ui-react/components/switch/Switch.tsx +164 -0
- package/src/ui-react/components/switch/index.ts +2 -0
- package/src/ui-react/components/tab-bar/TabBar.tsx +137 -0
- package/src/ui-react/components/tab-bar/index.ts +2 -0
- package/src/ui-react/components/textarea/Textarea.tsx +109 -0
- package/src/ui-react/components/textarea/index.ts +2 -0
- package/src/ui-react/components/toast/Toast.ts +98 -0
- package/src/ui-react/components/toast/index.ts +2 -0
- package/src/ui-react/components/typography/Typography.tsx +201 -0
- package/src/ui-react/components/typography/index.ts +2 -0
- package/src/ui-react/hooks/use-tap-app-bar.js +26 -0
- package/src/ui-react/hooks/use-tap-app-bar.ts +34 -0
- package/src/ui-react/index.js +1 -0
- package/src/ui-react/index.ts +79 -3
- package/src/ui-react/styles/VETCProvider.tsx +152 -0
- package/src/ui-react/styles/tokens.css +427 -0
- package/src/ui-react/tokens/colors.ts +91 -0
- package/src/ui-react/tokens/index.ts +3 -0
- package/src/ui-react/tokens/spacing.ts +59 -0
- package/src/ui-react/tokens/typography.ts +63 -0
- package/src/ui-react/tokens_vetc.json +1517 -0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vetc-miniapp/ui-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "MiniApp Platform UI React",
|
|
5
5
|
"main": "src/ui-react/index.js",
|
|
6
6
|
"types": "src/ui-react/index.ts",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"src"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "webpack"
|
|
12
|
+
"build": "webpack",
|
|
13
|
+
"demo": "webpack serve --config webpack.demo.js",
|
|
14
|
+
"build:demo": "webpack --config webpack.demo.js --mode production"
|
|
13
15
|
},
|
|
14
16
|
"keywords": [
|
|
15
17
|
"miniapp",
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
"author": "Hieuth052@gmail.com",
|
|
21
23
|
"license": "MIT",
|
|
22
24
|
"devDependencies": {
|
|
25
|
+
"@ant-design/icons": "^6.2.2",
|
|
23
26
|
"@babel/cli": "^7.28.6",
|
|
24
27
|
"@babel/core": "^7.29.0",
|
|
25
28
|
"@babel/preset-env": "^7.29.0",
|
|
@@ -28,14 +31,21 @@
|
|
|
28
31
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
29
32
|
"@rollup/plugin-terser": "^0.4.4",
|
|
30
33
|
"@types/node": "^25.2.0",
|
|
34
|
+
"@types/react": "^19.2.14",
|
|
35
|
+
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"antd": "^6.3.7",
|
|
31
37
|
"babel-loader": "^10.0.0",
|
|
38
|
+
"css-loader": "^7.1.4",
|
|
39
|
+
"html-webpack-plugin": "^5.6.7",
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0",
|
|
32
42
|
"rollup": "^4.57.1",
|
|
43
|
+
"style-loader": "^4.0.0",
|
|
33
44
|
"ts-loader": "^9.5.4",
|
|
34
45
|
"typescript": "^5.9.3",
|
|
35
46
|
"webpack": "^5.104.1",
|
|
36
47
|
"webpack-cli": "^6.0.1",
|
|
37
|
-
"
|
|
38
|
-
"react-dom": "^19.0.0"
|
|
48
|
+
"webpack-dev-server": "^5.2.3"
|
|
39
49
|
},
|
|
40
50
|
"dependencies": {
|
|
41
51
|
"acorn": "^8.15.0",
|
|
@@ -49,7 +59,9 @@
|
|
|
49
59
|
"webpack-sources": "^3.3.3"
|
|
50
60
|
},
|
|
51
61
|
"peerDependencies": {
|
|
62
|
+
"@ant-design/icons": ">=5.0.0",
|
|
63
|
+
"antd": ">=5.0.0",
|
|
52
64
|
"react": ">=19.0.0",
|
|
53
65
|
"react-dom": ">=19.0.0"
|
|
54
66
|
}
|
|
55
|
-
}
|
|
67
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see index.js.LICENSE.txt */
|
|
2
|
-
!function(n,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):"object"==typeof exports?exports["vetc-miniapp/ui-react"]=e(require("react")):n["vetc-miniapp/ui-react"]=e(n.react)}(this,n=>(()=>{"use strict";var e={155(e){e.exports=n},698(n,e){var r=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function o(n,e,t){var o=null;if(void 0!==t&&(o=""+t),void 0!==e.key&&(o=""+e.key),"key"in e)for(var i in t={},e)"key"!==i&&(t[i]=e[i]);else t=e;return e=t.ref,{$$typeof:r,type:n,key:o,ref:void 0!==e?e:null,props:t}}e.Fragment=t,e.jsx=o,e.jsxs=o},848(n,e,r){n.exports=r(698)}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n](i,i.exports,t),i.exports}t.d=(n,e)=>{for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.o=(n,e)=>Object.prototype.hasOwnProperty.call(n,e),t.r=n=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})};var o={};t.r(o),t.d(o,{App:()=>x,appConfig:()=>
|
|
2
|
+
!function(n,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):"object"==typeof exports?exports["vetc-miniapp/ui-react"]=e(require("react")):n["vetc-miniapp/ui-react"]=e(n.react)}(this,n=>(()=>{"use strict";var e={155(e){e.exports=n},698(n,e){var r=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function o(n,e,t){var o=null;if(void 0!==t&&(o=""+t),void 0!==e.key&&(o=""+e.key),"key"in e)for(var i in t={},e)"key"!==i&&(t[i]=e[i]);else t=e;return e=t.ref,{$$typeof:r,type:n,key:o,ref:void 0!==e?e:null,props:t}}e.Fragment=t,e.jsx=o,e.jsxs=o},848(n,e,r){n.exports=r(698)}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n](i,i.exports,t),i.exports}t.d=(n,e)=>{for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.o=(n,e)=>Object.prototype.hasOwnProperty.call(n,e),t.r=n=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})};var o={};t.r(o),t.d(o,{App:()=>x,appConfig:()=>m,useAppPause:()=>u,useAppResume:()=>a,useDidHide:()=>f,useDidShow:()=>c,useListenerScanQr:()=>b,useNavigate:()=>h,useTapAppBar:()=>p});var i=t(155);function u(n){var e=(0,i.useRef)(n);(0,i.useEffect)(function(){e.current=n},[n]),(0,i.useEffect)(function(){if("undefined"!=typeof window&&window.MiniApp){var n=function(){try{e.current&&e.current()}catch(n){console.error("[useAppPause error]",n)}};return window.MiniApp.on("appPause",n),function(){window.MiniApp.off("appPause",n)}}},[])}function a(n){var e=(0,i.useRef)(n);(0,i.useEffect)(function(){e.current=n},[n]),(0,i.useEffect)(function(){if(console.warn("MiniApp: useAppResume"),"undefined"!=typeof window&&window.MiniApp){var n=function(){try{e.current&&e.current()}catch(n){console.error("[useAppResume error]",n)}};return window.MiniApp.on("appResume",n),function(){window.MiniApp.off("appResume",n)}}},[])}function c(n,e){var r=(0,i.useRef)(e);(0,i.useEffect)(function(){r.current=e},[e]),(0,i.useEffect)(function(){if(window.MiniApp){var e=function(e){var t;(null==e?void 0:e.route)===n&&(console.log("didShow",e),null===(t=r.current)||void 0===t||t.call(r,e))};return window.MiniApp.on("didShow",e),function(){window.MiniApp.off("didShow",e)}}},[n])}function f(n,e){var r=(0,i.useRef)(e);(0,i.useEffect)(function(){r.current=e},[e]),(0,i.useEffect)(function(){if(window.MiniApp){var e=function(e){var t;(null==e?void 0:e.route)===n&&(null===(t=r.current)||void 0===t||t.call(r,e))};return window.MiniApp.on("didHide",e),function(){window.MiniApp.off("didHide",e)}}},[n])}function p(n,e){var r=(0,i.useRef)(e);(0,i.useEffect)(function(){r.current=e},[e]),(0,i.useEffect)(function(){if(window.MiniApp){var e=function(e){var t;(null==e?void 0:e.route)===n&&(console.log("onTapAppBar",e),null===(t=r.current)||void 0===t||t.call(r,e))};return window.MiniApp.on("onTapAppBar",e),function(){window.MiniApp.off("onTapAppBar",e)}}},[n])}function l(){var n,e,r="function"==typeof Symbol?Symbol:{},t=r.iterator||"@@iterator",o=r.toStringTag||"@@toStringTag";function i(r,t,o,i){var c=t&&t.prototype instanceof a?t:a,f=Object.create(c.prototype);return s(f,"_invoke",function(r,t,o){var i,a,c,f=0,p=o||[],l=!1,s={p:0,n:0,v:n,a:d,f:d.bind(n,4),d:function(e,r){return i=e,a=0,c=n,s.n=r,u}};function d(r,t){for(a=r,c=t,e=0;!l&&f&&!o&&e<p.length;e++){var o,i=p[e],d=s.p,v=i[2];r>3?(o=v===t)&&(c=i[(a=i[4])?5:(a=3,3)],i[4]=i[5]=n):i[0]<=d&&((o=r<2&&d<i[1])?(a=0,s.v=t,s.n=i[1]):d<v&&(o=r<3||i[0]>t||t>v)&&(i[4]=r,i[5]=t,s.n=v,a=0))}if(o||r>1)return u;throw l=!0,t}return function(o,p,v){if(f>1)throw TypeError("Generator is already running");for(l&&1===p&&d(p,v),a=p,c=v;(e=a<2?n:c)||!l;){i||(a?a<3?(a>1&&(s.n=-1),d(a,c)):s.n=c:s.v=c);try{if(f=2,i){if(a||(o="next"),e=i[o]){if(!(e=e.call(i,c)))throw TypeError("iterator result is not an object");if(!e.done)return e;c=e.value,a<2&&(a=0)}else 1===a&&(e=i.return)&&e.call(i),a<2&&(c=TypeError("The iterator does not provide a '"+o+"' method"),a=1);i=n}else if((e=(l=s.n<0)?c:r.call(t,s))!==u)break}catch(e){i=n,a=1,c=e}finally{f=1}}return{value:e,done:l}}}(r,o,i),!0),f}var u={};function a(){}function c(){}function f(){}e=Object.getPrototypeOf;var p=[][t]?e(e([][t]())):(s(e={},t,function(){return this}),e),d=f.prototype=a.prototype=Object.create(p);function v(n){return Object.setPrototypeOf?Object.setPrototypeOf(n,f):(n.__proto__=f,s(n,o,"GeneratorFunction")),n.prototype=Object.create(d),n}return c.prototype=f,s(d,"constructor",f),s(f,"constructor",c),c.displayName="GeneratorFunction",s(f,o,"GeneratorFunction"),s(d),s(d,o,"Generator"),s(d,t,function(){return this}),s(d,"toString",function(){return"[object Generator]"}),(l=function(){return{w:i,m:v}})()}function s(n,e,r,t){var o=Object.defineProperty;try{o({},"",{})}catch(n){o=0}s=function(n,e,r,t){function i(e,r){s(n,e,function(n){return this._invoke(e,r,n)})}e?o?o(n,e,{value:r,enumerable:!t,configurable:!t,writable:!t}):n[e]=r:(i("next",0),i("throw",1),i("return",2))},s(n,e,r,t)}function d(n,e,r,t,o,i,u){try{var a=n[i](u),c=a.value}catch(n){return void r(n)}a.done?e(c):Promise.resolve(c).then(t,o)}var v="undefined"!=typeof window,w=function(){return v&&window.flutter_inappwebview&&window.flutter_inappwebview.callHandler&&"function"==typeof window.flutter_inappwebview.callHandler?window.flutter_inappwebview:null},y=function(){var n,e=(n=l().m(function n(e){var r,t,o,i,u=arguments;return l().w(function(n){for(;;)switch(n.p=n.n){case 0:if(r=u.length>1&&void 0!==u[1]?u[1]:{},t=w()){n.n=1;break}return n.a(2,{ok:!1,error:"BRIDGE_CALL_FAILED"});case 1:return n.p=1,n.n=2,t.callHandler("MiniAppBridge",{action:e,payload:r});case 2:return o=n.v,n.a(2,o);case 3:return n.p=3,i=n.v,console.error("Bridge error:",i),n.a(2,{ok:!1,error:"BRIDGE_CALL_FAILED"})}},n,null,[[1,3]])}),function(){var e=this,r=arguments;return new Promise(function(t,o){var i=n.apply(e,r);function u(n){d(i,t,o,u,a,"next",n)}function a(n){d(i,t,o,u,a,"throw",n)}u(void 0)})});return function(n){return e.apply(this,arguments)}}();function h(){return function(n){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("number"==typeof n)return y("navigate",{type:"native",action:"pop",delta:Math.abs(n)});y("navigate",{type:"native",action:"push",route:n,params:e,options:r})}}var m={locale:"vi",theme:"light",pages:[]};function b(n){var e=(0,i.useRef)(n);(0,i.useEffect)(function(){e.current=n},[n]),(0,i.useEffect)(function(){if("undefined"!=typeof window&&window.MiniApp){var n=function(n){try{var r;null===(r=e.current)||void 0===r||r.call(e,n)}catch(n){console.error("[useListenerScanQr error]",n)}};return window.MiniApp.on("onScanQRResult",n),function(){window.MiniApp.off("onScanQRResult",n)}}},[])}var g=t(848);function A(n,e){(null==e||e>n.length)&&(e=n.length);for(var r=0,t=Array(e);r<e;r++)t[r]=n[r];return t}var M=new Map,S="/";function j(){return M.get(S)}function E(n){S=n}function x(n){var e,r,t=n.config,o=n.children,u=(e=(0,i.useState)(null),r=2,function(n){if(Array.isArray(n))return n}(e)||function(n,e){var r=null==n?null:"undefined"!=typeof Symbol&&n[Symbol.iterator]||n["@@iterator"];if(null!=r){var t,o,i,u,a=[],c=!0,f=!1;try{if(i=(r=r.call(n)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(t=i.call(r)).done)&&(a.push(t.value),a.length!==e);c=!0);}catch(n){f=!0,o=n}finally{try{if(!c&&null!=r.return&&(u=r.return(),Object(u)!==u))return}finally{if(f)throw o}}return a}}(e,r)||function(n,e){if(n){if("string"==typeof n)return A(n,e);var r={}.toString.call(n).slice(8,-1);return"Object"===r&&n.constructor&&(r=n.constructor.name),"Map"===r||"Set"===r?Array.from(n):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?A(n,e):void 0}}(e,r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),a=u[0],c=u[1],f=(0,i.useRef)(!1);(0,i.useEffect)(function(){var n;if(null!=t&&null!==(n=t.pages)&&void 0!==n&&n.length&&!f.current){f.current=!0,function(n){M=new Map(n.pages.map(function(n){return[n.path,n]}))}(t),E((window.location.pathname||"/").replace("/miniapp","")||"/");var e=j();e||(E("/"),e=j()),c(e||null),y("registerAppConfig",{config:t})}},[t]);var p=(0,i.useMemo)(function(){return a&&a.Component?a.Component:null},[a]);return p?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(p,{}),o]}):(console.warn("MiniApp: Page not found"),(0,g.jsx)("div",{}))}return o})());
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VETC Avatar — tokenized
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Avatar as AntAvatar } from 'antd';
|
|
6
|
+
|
|
7
|
+
export type AvatarSize = 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
|
8
|
+
export type AvatarShape = 'circle' | 'square';
|
|
9
|
+
|
|
10
|
+
// Map size token name to CSS var
|
|
11
|
+
const sizeVarMap: Record<AvatarSize, string> = {
|
|
12
|
+
xl: 'var(--vetc-avatar-size-xl)',
|
|
13
|
+
lg: 'var(--vetc-avatar-size-lg)',
|
|
14
|
+
md: 'var(--vetc-avatar-size-md)',
|
|
15
|
+
sm: 'var(--vetc-avatar-size-sm)',
|
|
16
|
+
xs: 'var(--vetc-avatar-size-xs)',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Pixel values needed for antd size prop (numeric)
|
|
20
|
+
const sizePxMap: Record<AvatarSize, number> = { xl: 56, lg: 48, md: 40, sm: 32, xs: 24 };
|
|
21
|
+
|
|
22
|
+
// Font size scales with avatar size (from Figma)
|
|
23
|
+
const fontSizeMap: Record<AvatarSize, string> = {
|
|
24
|
+
xl: 'var(--vetc-font-size-2xl)',
|
|
25
|
+
lg: 'var(--vetc-font-size-xl)',
|
|
26
|
+
md: 'var(--vetc-font-size-base)',
|
|
27
|
+
sm: 'var(--vetc-font-size-sm)',
|
|
28
|
+
xs: 'var(--vetc-font-size-2xs)',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export interface AvatarProps {
|
|
32
|
+
src?: string;
|
|
33
|
+
alt?: string;
|
|
34
|
+
initials?: string;
|
|
35
|
+
icon?: React.ReactNode;
|
|
36
|
+
size?: AvatarSize;
|
|
37
|
+
shape?: AvatarShape;
|
|
38
|
+
color?: string;
|
|
39
|
+
className?: string;
|
|
40
|
+
style?: React.CSSProperties;
|
|
41
|
+
onClick?: () => void;
|
|
42
|
+
id?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function Avatar({
|
|
46
|
+
src,
|
|
47
|
+
alt,
|
|
48
|
+
initials,
|
|
49
|
+
icon,
|
|
50
|
+
size = 'md',
|
|
51
|
+
shape = 'circle',
|
|
52
|
+
color,
|
|
53
|
+
className = '',
|
|
54
|
+
style,
|
|
55
|
+
onClick,
|
|
56
|
+
id,
|
|
57
|
+
}: AvatarProps) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
id={id}
|
|
61
|
+
style={{ display: 'inline-flex', flexShrink: 0, width: sizeVarMap[size], height: sizeVarMap[size] }}
|
|
62
|
+
>
|
|
63
|
+
<AntAvatar
|
|
64
|
+
src={src}
|
|
65
|
+
alt={alt}
|
|
66
|
+
icon={!src && !initials ? icon : undefined}
|
|
67
|
+
size={sizePxMap[size]}
|
|
68
|
+
shape={shape}
|
|
69
|
+
onClick={onClick}
|
|
70
|
+
className={`vetc-avatar vetc-avatar--${size} ${className}`}
|
|
71
|
+
style={{
|
|
72
|
+
backgroundColor: !src ? (color ?? 'var(--vetc-avatar-bg-default)') : undefined,
|
|
73
|
+
fontSize: fontSizeMap[size],
|
|
74
|
+
fontWeight: 'var(--vetc-font-weight-bold)' as any,
|
|
75
|
+
fontFamily: 'var(--vetc-font-family)',
|
|
76
|
+
color: 'var(--vetc-avatar-text-color)',
|
|
77
|
+
cursor: onClick ? 'pointer' : 'default',
|
|
78
|
+
flexShrink: 0,
|
|
79
|
+
...style,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{!src && initials ? initials.slice(0, 2).toUpperCase() : undefined}
|
|
83
|
+
</AntAvatar>
|
|
84
|
+
</span>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default Avatar;
|