internhub-v2-mobile-ui-kit 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,108 @@
1
+ # 🎨 InternHub v2 Mobile UI Kit
2
+
3
+ Bộ thư viện giao diện chuẩn (Design System) dành cho ứng dụng InternHub v2 Mobile.
4
+ Được xây dựng trên React Native, hỗ trợ TypeScript và Dark Mode (future proof).
5
+
6
+ ## 📦 Cài Đặt
7
+
8
+ ```bash
9
+ npm install internhub-v2-mobile-ui-kit
10
+ # Hoặc
11
+ yarn add internhub-v2-mobile-ui-kit
12
+ ```
13
+
14
+ Yêu cầu cài thêm thư viện bổ trợ (Peer Dependencies):
15
+ ```bash
16
+ yarn add react-native-linear-gradient react-native-svg
17
+ ```
18
+
19
+ ## 🚀 Sử Dụng
20
+
21
+ ### 1. Màu Sắc & Tokens
22
+ Dùng `Colors` để đảm bảo đồng bộ giao diện toàn App.
23
+
24
+ ```tsx
25
+ import { Colors, Spacing } from 'internhub-v2-mobile-ui-kit';
26
+
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ backgroundColor: Colors.background, // Màu kem mềm
30
+ padding: Spacing.md,
31
+ },
32
+ title: {
33
+ color: Colors.primary, // Màu cam thương hiệu (#E18308)
34
+ }
35
+ });
36
+ ```
37
+
38
+ ### 2. Components Cơ Bản
39
+
40
+ #### Button
41
+ Button chuẩn có hỗ trợ gradient và các trạng thái loading/disabled.
42
+
43
+ ```tsx
44
+ import { Button } from 'internhub-v2-mobile-ui-kit';
45
+
46
+ <Button
47
+ title="Đăng Nhập"
48
+ onPress={handleLogin}
49
+ variant="primary" // primary | secondary | outline | ghost
50
+ loading={isLoading}
51
+ />
52
+ ```
53
+
54
+ #### Input
55
+ Ô nhập liệu chuẩn form.
56
+
57
+ ```tsx
58
+ import { Input } from 'internhub-v2-mobile-ui-kit';
59
+
60
+ <Input
61
+ label="Họ tên"
62
+ placeholder="Nhập họ tên của bạn"
63
+ error={errors.fullName}
64
+ icon="user"
65
+ />
66
+ ```
67
+
68
+ #### Card
69
+ Thẻ nội dung có đổ bóng và bo góc chuẩn.
70
+
71
+ ```tsx
72
+ import { Card } from 'internhub-v2-mobile-ui-kit';
73
+
74
+ <Card variant="elevated">
75
+ <Text>Nội dung thẻ chấm công...</Text>
76
+ </Card>
77
+ ```
78
+
79
+ #### Badge & Avatar
80
+ Dùng cho status và ảnh đại diện.
81
+
82
+ ```tsx
83
+ import { Badge, Avatar } from 'internhub-v2-mobile-ui-kit';
84
+
85
+ <Avatar name="Nhan Nguyen" size="md" />
86
+ <Badge label="Đang chờ duyệt" type="warning" />
87
+ ```
88
+
89
+ ## 🧩 Danh Sách Components
90
+
91
+ | Component | Trạng Thái | Mô Tả |
92
+ |-----------|------------|-------|
93
+ | `Alert` | ✅ Ready | Hiển thị thông báo warning/info |
94
+ | `Avatar` | ✅ Ready | Ảnh đại diện tròn hoặc chữ cái đầu |
95
+ | `Badge` | ✅ Ready | Nhãn trạng thái (Success, Error...) |
96
+ | `Button` | ✅ Ready | Nút bấm chính/phụ |
97
+ | `Card` | ✅ Ready | Khung chứa nội dung container |
98
+ | `Divider` | ✅ Ready | Đường kẻ phân cách (mới) |
99
+ | `EmptyState`| ✅ Ready | Màn hình hiển thị khi không có dữ liệu (mới)|
100
+ | `Header` | ✅ Ready | Thanh tiêu đề điều hướng (mới) |
101
+ | `Input` | ✅ Ready | Ô nhập liệu văn bản |
102
+ | `Loading` | ✅ Ready | Vòng xoay chờ tải trang (mới) |
103
+ | `Modal` | ✅ Ready | Hộp thoại popup |
104
+ | `Select` | ✅ Ready | Dropdown chọn options |
105
+ | `Toast` | 🚧 W.I.P | Thông báo nổi tự tắt |
106
+
107
+ ## 👨‍💻 Tác Giả
108
+ **Nguyen Thanh Nhan** - InternHub Team
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internhub-v2-mobile-ui-kit",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Premium UI Kit for InternHub Mobile App",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -23,4 +23,4 @@
23
23
  "react-native-linear-gradient": "^2.8.3",
24
24
  "react-native-svg": "^14.1.0"
25
25
  }
26
- }
26
+ }
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { View, Image, Text, StyleSheet, ImageSourcePropType, ViewStyle } from 'react-native';
3
+ import { Colors } from '../tokens';
4
+
5
+ interface AvatarProps {
6
+ source?: ImageSourcePropType;
7
+ name?: string;
8
+ size?: 'sm' | 'md' | 'lg' | 'xl';
9
+ style?: ViewStyle;
10
+ }
11
+
12
+ const SIZES = {
13
+ sm: 32,
14
+ md: 48,
15
+ lg: 80,
16
+ xl: 120,
17
+ };
18
+
19
+ export const Avatar: React.FC<AvatarProps> = ({ source, name, size = 'md', style }) => {
20
+ const dimension = SIZES[size];
21
+
22
+ return (
23
+ <View style={[
24
+ styles.container,
25
+ { width: dimension, height: dimension, borderRadius: dimension / 2 },
26
+ style
27
+ ]}>
28
+ {source ? (
29
+ <Image source={source} style={styles.image} resizeMode="cover" />
30
+ ) : (
31
+ <Text style={[styles.text, { fontSize: dimension * 0.4 }]}>
32
+ {name ? name.charAt(0).toUpperCase() : '?'}
33
+ </Text>
34
+ )}
35
+ </View>
36
+ );
37
+ };
38
+
39
+ const styles = StyleSheet.create({
40
+ container: {
41
+ backgroundColor: Colors.avatarBg,
42
+ justifyContent: 'center',
43
+ alignItems: 'center',
44
+ overflow: 'hidden',
45
+ borderWidth: 1.5,
46
+ borderColor: Colors.primary,
47
+ },
48
+ image: {
49
+ width: '100%',
50
+ height: '100%',
51
+ },
52
+ text: {
53
+ fontWeight: '800',
54
+ color: Colors.primary,
55
+ }
56
+ });
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native';
3
+ import { Colors } from '../tokens';
4
+
5
+ export type BadgeType = 'success' | 'warning' | 'error' | 'info' | 'default';
6
+
7
+ interface BadgeProps {
8
+ label: string;
9
+ type?: BadgeType;
10
+ size?: 'sm' | 'md';
11
+ style?: ViewStyle;
12
+ }
13
+
14
+ const BADGE_COLORS = {
15
+ success: { bg: Colors.successLight, text: Colors.success },
16
+ warning: { bg: Colors.warningLight, text: Colors.warning },
17
+ error: { bg: Colors.errorLight, text: Colors.error },
18
+ info: { bg: Colors.infoLight, text: Colors.info },
19
+ default: { bg: Colors.surfaceAlt, text: Colors.textSecondary },
20
+ };
21
+
22
+ export const Badge: React.FC<BadgeProps> = ({ label, type = 'default', size = 'sm', style }) => {
23
+ const colors = BADGE_COLORS[type] || BADGE_COLORS.default;
24
+
25
+ return (
26
+ <View style={[
27
+ styles.container,
28
+ { backgroundColor: colors.bg },
29
+ size === 'md' ? styles.md : styles.sm,
30
+ style
31
+ ]}>
32
+ <Text style={[
33
+ styles.text,
34
+ { color: colors.text },
35
+ (size === 'md' ? { fontSize: 12 } : { fontSize: 10 }) as TextStyle
36
+ ]}>{label}</Text>
37
+ </View>
38
+ );
39
+ };
40
+
41
+ const styles = StyleSheet.create({
42
+ container: {
43
+ borderRadius: 99,
44
+ justifyContent: 'center',
45
+ alignItems: 'center',
46
+ alignSelf: 'flex-start',
47
+ },
48
+ sm: {
49
+ paddingHorizontal: 8,
50
+ paddingVertical: 4,
51
+ },
52
+ md: {
53
+ paddingHorizontal: 12,
54
+ paddingVertical: 6,
55
+ },
56
+ text: {
57
+ fontWeight: '700',
58
+ }
59
+ });
Binary file
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, ViewStyle } from 'react-native';
3
+ import { Colors } from '../tokens';
4
+
5
+ interface DividerProps {
6
+ vertical?: boolean;
7
+ color?: string;
8
+ size?: number; // thickness
9
+ spacing?: number; // margin
10
+ style?: ViewStyle;
11
+ }
12
+
13
+ export const Divider: React.FC<DividerProps> = ({
14
+ vertical = false,
15
+ color = Colors.border,
16
+ size = 1,
17
+ spacing = 16,
18
+ style
19
+ }) => {
20
+ return (
21
+ <View
22
+ style={[
23
+ vertical
24
+ ? { height: '100%', width: size, marginHorizontal: spacing }
25
+ : { width: '100%', height: size, marginVertical: spacing },
26
+ { backgroundColor: color },
27
+ style
28
+ ]}
29
+ />
30
+ );
31
+ };
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, Image, ImageSourcePropType, ViewStyle } from 'react-native';
3
+ import { Colors } from '../tokens';
4
+
5
+ interface EmptyStateProps {
6
+ title?: string;
7
+ message: string;
8
+ image?: ImageSourcePropType;
9
+ style?: ViewStyle;
10
+ }
11
+
12
+ export const EmptyState: React.FC<EmptyStateProps> = ({
13
+ title,
14
+ message,
15
+ image,
16
+ style
17
+ }) => {
18
+ return (
19
+ <View style={[styles.container, style]}>
20
+ <View style={styles.circle}>
21
+ {image ? (
22
+ <Image source={image} style={styles.image} resizeMode="contain" />
23
+ ) : (
24
+ <Text style={styles.icon}>📋</Text>
25
+ )}
26
+ </View>
27
+ {title && <Text style={styles.title}>{title}</Text>}
28
+ <Text style={styles.message}>{message}</Text>
29
+ </View>
30
+ );
31
+ };
32
+
33
+ const styles = StyleSheet.create({
34
+ container: {
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ padding: 32,
38
+ flex: 1,
39
+ },
40
+ circle: {
41
+ width: 120,
42
+ height: 120,
43
+ backgroundColor: Colors.surface,
44
+ borderRadius: 60,
45
+ justifyContent: 'center',
46
+ alignItems: 'center',
47
+ marginBottom: 24,
48
+ borderWidth: 2,
49
+ borderColor: Colors.border,
50
+ borderStyle: 'dashed',
51
+ },
52
+ image: {
53
+ width: 80,
54
+ height: 80,
55
+ },
56
+ icon: {
57
+ fontSize: 48,
58
+ },
59
+ title: {
60
+ fontSize: 20,
61
+ fontWeight: '700',
62
+ color: Colors.textPrimary,
63
+ marginBottom: 8,
64
+ textAlign: 'center',
65
+ },
66
+ message: {
67
+ fontSize: 15,
68
+ color: Colors.textSecondary,
69
+ textAlign: 'center',
70
+ lineHeight: 22,
71
+ maxWidth: 280,
72
+ },
73
+ });
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { View, Text, TouchableOpacity, StyleSheet, Modal, FlatList, ViewStyle } from 'react-native';
3
+ import { Colors } from '../tokens';
4
+
5
+ interface Option {
6
+ label: string;
7
+ value: any;
8
+ }
9
+
10
+ interface SelectProps {
11
+ options: Option[];
12
+ value: any;
13
+ onChange: (value: any) => void;
14
+ placeholder?: string;
15
+ label?: string;
16
+ style?: ViewStyle;
17
+ }
18
+
19
+ export const Select: React.FC<SelectProps> = ({ options, value, onChange, placeholder = 'Select...', label, style }) => {
20
+ const [visible, setVisible] = React.useState(false);
21
+ const selected = options.find(o => o.value === value);
22
+
23
+ return (
24
+ <View style={[styles.wrapper, style]}>
25
+ {label && <Text style={styles.label}>{label}</Text>}
26
+ <TouchableOpacity
27
+ style={styles.field}
28
+ onPress={() => setVisible(true)}
29
+ activeOpacity={0.8}
30
+ >
31
+ <Text style={[styles.text, !selected && styles.placeholder]}>
32
+ {selected ? selected.label : placeholder}
33
+ </Text>
34
+ </TouchableOpacity>
35
+
36
+ <Modal visible={visible} transparent animationType="fade">
37
+ <View style={styles.modalOverlay}>
38
+ <TouchableOpacity style={styles.modalBg} onPress={() => setVisible(false)} />
39
+ <View style={styles.modalContent}>
40
+ <Text style={styles.header}>{label || placeholder}</Text>
41
+ <FlatList
42
+ data={options}
43
+ keyExtractor={(item) => String(item.value)}
44
+ renderItem={({ item }) => (
45
+ <TouchableOpacity
46
+ style={[styles.option, item.value === value && styles.selectedOption]}
47
+ onPress={() => {
48
+ onChange(item.value);
49
+ setVisible(false);
50
+ }}
51
+ >
52
+ <Text style={[styles.optionText, item.value === value && styles.selectedOptionText]}>
53
+ {item.label}
54
+ </Text>
55
+ </TouchableOpacity>
56
+ )}
57
+ />
58
+ </View>
59
+ </View>
60
+ </Modal>
61
+ </View>
62
+ );
63
+ };
64
+
65
+ const styles = StyleSheet.create({
66
+ wrapper: { marginBottom: 16 },
67
+ label: { marginBottom: 8, fontSize: 14, fontWeight: '600', color: Colors.textSecondary },
68
+ field: {
69
+ borderWidth: 1,
70
+ borderColor: Colors.border,
71
+ borderRadius: 8,
72
+ padding: 12,
73
+ backgroundColor: Colors.inputBackground
74
+ },
75
+ text: { fontSize: 16, color: Colors.textPrimary },
76
+ placeholder: { color: Colors.textDisabled },
77
+ modalOverlay: { flex: 1, justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0.5)', padding: 20 },
78
+ modalBg: { ...StyleSheet.absoluteFillObject },
79
+ modalContent: { backgroundColor: Colors.surface, borderRadius: 12, maxHeight: 400, padding: 16 },
80
+ header: { fontSize: 18, fontWeight: '700', marginBottom: 12, textAlign: 'center' },
81
+ option: { paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: Colors.borderAlt },
82
+ selectedOption: { backgroundColor: Colors.glassOrange },
83
+ optionText: { fontSize: 16, color: Colors.textPrimary },
84
+ selectedOptionText: { color: Colors.primary, fontWeight: '700' },
85
+ });
Binary file
Binary file
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // Design Tokens
2
2
  export * from './tokens';
3
3
 
4
- // Components
4
+ // Core Components
5
5
  export { Button } from './Button/Button';
6
6
  export { Input } from './Input/Input';
7
7
  export { Card } from './Card/Card';
@@ -10,3 +10,15 @@ export { Alert } from './Alert/Alert';
10
10
  export { GradientBackground } from './GradientBackground/GradientBackground';
11
11
  export { Modal } from './Modal/Modal';
12
12
  export { PasswordChecker } from './PasswordChecker/PasswordChecker';
13
+
14
+ // New Components (v0.0.3)
15
+ export { Avatar } from './Avatar/Avatar';
16
+ export { Badge } from './Badge/Badge';
17
+ export { Select } from './Select/Select';
18
+ export { Divider } from './Divider/Divider';
19
+ export { EmptyState } from './EmptyState/EmptyState';
20
+ export { Header } from './Header/Header';
21
+ export { Loading } from './Loading/Loading';
22
+ export { DatePicker } from './DatePicker/DatePicker';
23
+ export { TimePicker } from './TimePicker/TimePicker';
24
+ export { Toast } from './Toast/Toast';