@herca/rn-kit 0.1.26 → 0.1.28

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.
Files changed (66) hide show
  1. package/lib/module/BottomSheet/BottomSheet.js +4 -1
  2. package/lib/module/BottomSheet/BottomSheet.js.map +1 -1
  3. package/lib/module/Button/tests/Button.test.js +70 -0
  4. package/lib/module/Button/tests/Button.test.js.map +1 -0
  5. package/lib/module/Button/type.js +1 -1
  6. package/lib/module/DatePicker/TimePicker.js +84 -39
  7. package/lib/module/DatePicker/TimePicker.js.map +1 -1
  8. package/lib/module/Icon/Icon.js +7 -1
  9. package/lib/module/Icon/Icon.js.map +1 -1
  10. package/lib/module/Icon/README.md +6 -0
  11. package/lib/module/Icon/icons/Bold.js +25 -0
  12. package/lib/module/Icon/icons/Bold.js.map +1 -0
  13. package/lib/module/Icon/icons/Italic.js +25 -0
  14. package/lib/module/Icon/icons/Italic.js.map +1 -0
  15. package/lib/module/Icon/icons/UnderLine.js +25 -0
  16. package/lib/module/Icon/icons/UnderLine.js.map +1 -0
  17. package/lib/module/Input/InputOtp.js +7 -5
  18. package/lib/module/Input/InputOtp.js.map +1 -1
  19. package/lib/module/Input/README_INPUT_OTP.md +8 -7
  20. package/lib/module/TextEditor/TextEditor.js +206 -4
  21. package/lib/module/TextEditor/TextEditor.js.map +1 -1
  22. package/lib/module/Toast/README.md +32 -16
  23. package/lib/module/Toast/Toast.js +31 -19
  24. package/lib/module/Toast/Toast.js.map +1 -1
  25. package/lib/module/Toast/ToastContext.js +2 -1
  26. package/lib/module/Toast/ToastContext.js.map +1 -1
  27. package/lib/typescript/src/Button/tests/Button.test.d.ts +2 -0
  28. package/lib/typescript/src/Button/tests/Button.test.d.ts.map +1 -0
  29. package/lib/typescript/src/DatePicker/TimePicker.d.ts.map +1 -1
  30. package/lib/typescript/src/Icon/Icon.d.ts.map +1 -1
  31. package/lib/typescript/src/Icon/icons/Bold.d.ts +9 -0
  32. package/lib/typescript/src/Icon/icons/Bold.d.ts.map +1 -0
  33. package/lib/typescript/src/Icon/icons/Italic.d.ts +9 -0
  34. package/lib/typescript/src/Icon/icons/Italic.d.ts.map +1 -0
  35. package/lib/typescript/src/Icon/icons/UnderLine.d.ts +9 -0
  36. package/lib/typescript/src/Icon/icons/UnderLine.d.ts.map +1 -0
  37. package/lib/typescript/src/Icon/type.d.ts +1 -1
  38. package/lib/typescript/src/Icon/type.d.ts.map +1 -1
  39. package/lib/typescript/src/Input/InputOtp.d.ts +1 -0
  40. package/lib/typescript/src/Input/InputOtp.d.ts.map +1 -1
  41. package/lib/typescript/src/TextEditor/TextEditor.d.ts +2 -1
  42. package/lib/typescript/src/TextEditor/TextEditor.d.ts.map +1 -1
  43. package/lib/typescript/src/TextEditor/types.d.ts +7 -0
  44. package/lib/typescript/src/TextEditor/types.d.ts.map +1 -1
  45. package/lib/typescript/src/Toast/Toast.d.ts +2 -1
  46. package/lib/typescript/src/Toast/Toast.d.ts.map +1 -1
  47. package/lib/typescript/src/Toast/ToastContext.d.ts +1 -0
  48. package/lib/typescript/src/Toast/ToastContext.d.ts.map +1 -1
  49. package/package.json +2 -2
  50. package/src/BottomSheet/BottomSheet.tsx +4 -1
  51. package/src/Button/tests/Button.test.tsx +49 -0
  52. package/src/Button/type.tsx +1 -1
  53. package/src/DatePicker/TimePicker.tsx +116 -52
  54. package/src/Icon/Icon.tsx +6 -0
  55. package/src/Icon/README.md +6 -0
  56. package/src/Icon/icons/Bold.tsx +22 -0
  57. package/src/Icon/icons/Italic.tsx +22 -0
  58. package/src/Icon/icons/UnderLine.tsx +26 -0
  59. package/src/Icon/type.tsx +3 -0
  60. package/src/Input/InputOtp.tsx +7 -3
  61. package/src/Input/README_INPUT_OTP.md +8 -7
  62. package/src/TextEditor/TextEditor.tsx +222 -3
  63. package/src/TextEditor/types.tsx +7 -0
  64. package/src/Toast/README.md +32 -16
  65. package/src/Toast/Toast.tsx +26 -16
  66. package/src/Toast/ToastContext.tsx +2 -0
@@ -1,5 +1,224 @@
1
- import { View } from 'react-native';
1
+ import { useRef, useState, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Platform,
6
+ KeyboardAvoidingView,
7
+ TouchableOpacity,
8
+ Keyboard,
9
+ type KeyboardEvent,
10
+ Dimensions,
11
+ SafeAreaView,
12
+ } from 'react-native';
13
+ import { WebView } from 'react-native-webview';
14
+ import Color from '../Color/Color';
15
+ import type { TextEditorType } from './types';
16
+ import LabelForm from '../LabelForm/LabelForm';
17
+ import Typography from '../Typography/Typography';
18
+ import Icon from '../Icon';
2
19
 
3
- export default function TextEditor() {
4
- return <View></View>;
20
+ export default function TextEditor({
21
+ label,
22
+ hasError,
23
+ hint,
24
+ onChange,
25
+ height = 300,
26
+ }: TextEditorType) {
27
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
28
+ const [showToolbar, setShowToolbar] = useState<boolean>(false);
29
+ const webviewRef = useRef<WebView>(null);
30
+ const handleOnChange = (data: string) => {
31
+ onChange?.(data);
32
+ };
33
+
34
+ const htmlEditor = `
35
+ <html>
36
+ <head>
37
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
38
+ <style>
39
+ *,
40
+ *::before,
41
+ *::after {
42
+ box-sizing: border-box;
43
+ }
44
+ html, body {
45
+ margin: 0;
46
+ padding: 0;
47
+ width: 99.9%;
48
+ height: 100%;
49
+ overflow-x: hidden;
50
+ }
51
+ #editor {
52
+ min-height: ${height};
53
+ width: 100%;
54
+ max-width: 100%;
55
+ padding: 12px;
56
+ font-size: 12px;
57
+ border: 1px solid ${Color.gray[200]};
58
+ border-radius: 6px;
59
+ outline: none;
60
+ transition: border 0.2s;
61
+ overflow-wrap: break-word;
62
+ font-weight: 500;
63
+ word-wrap: break-word;
64
+ word-break: break-word;
65
+ }
66
+ #editor.focused {
67
+ border-color: ${Color.gray[200]}
68
+ }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <div id="editor" contenteditable="true">Tulis sesuatu di sini...</div>
73
+ <script>
74
+ const editor = document.getElementById("editor");
75
+
76
+ editor.addEventListener("focus", () => {
77
+ editor.classList.add("focused");
78
+ });
79
+
80
+ editor.addEventListener("blur", () => {
81
+ editor.classList.remove("focused");
82
+ });
83
+ const handleMessage = (command) => {
84
+ if (command === 'getContent') {
85
+ window.ReactNativeWebView.postMessage(editor.innerHTML);
86
+ } else {
87
+ editor.focus();
88
+ document.execCommand(command, false, '');
89
+ }
90
+ };
91
+
92
+ // ini untuk Android
93
+ document.addEventListener("message", (event) => {
94
+ handleMessage(event.data);
95
+ });
96
+
97
+ // ini untuk iOS
98
+ window.addEventListener("message", (event) => {
99
+ handleMessage(event.data);
100
+ });
101
+ </script>
102
+ </body>
103
+ </html>
104
+ `;
105
+
106
+ const formatText = (command: string) => {
107
+ if (webviewRef.current) {
108
+ if (Platform.OS === 'ios') {
109
+ webviewRef.current.injectJavaScript(`
110
+ document.execCommand('${command}', false, '');
111
+ true; //di ios harus biar berfunsi boldnya
112
+ `);
113
+ } else {
114
+ webviewRef.current.postMessage(command);
115
+ }
116
+ }
117
+ };
118
+
119
+ useEffect(() => {
120
+ const showSub = Keyboard.addListener(
121
+ 'keyboardDidShow',
122
+ (e: KeyboardEvent) => {
123
+ setKeyboardHeight(e.endCoordinates.height - 35);
124
+ console.log(e.endCoordinates.height)
125
+ setShowToolbar(true);
126
+ }
127
+ );
128
+ const hideSub = Keyboard.addListener('keyboardDidHide', () => {
129
+ setKeyboardHeight(0);
130
+ setShowToolbar(false);
131
+ });
132
+
133
+ return () => {
134
+ showSub.remove();
135
+ hideSub.remove();
136
+ };
137
+ }, []);
138
+
139
+ return (
140
+ <View style={styles.container}>
141
+ {label ? <LabelForm title={label} /> : null}
142
+ <KeyboardAvoidingView
143
+ style={(styles.editorWrapper, [{ minHeight: height }])}
144
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
145
+ >
146
+ <WebView
147
+ ref={webviewRef}
148
+ originWhitelist={['*']}
149
+ source={{ html: htmlEditor }}
150
+ onMessage={(event) => {
151
+ handleOnChange(event.nativeEvent.data);
152
+ }}
153
+ style={styles.webview}
154
+ javaScriptEnabled={true}
155
+ domStorageEnabled={true}
156
+ mixedContentMode="always"
157
+ allowUniversalAccessFromFileURLs={true}
158
+ allowFileAccess={true}
159
+ />
160
+ </KeyboardAvoidingView>
161
+
162
+ {hint ? (
163
+ <Typography
164
+ color={hasError ? Color.danger[500] : Color.gray[700]}
165
+ variant="t3"
166
+ weight="medium"
167
+ >
168
+ {hint}
169
+ </Typography>
170
+ ) : null}
171
+ {showToolbar ? (
172
+ <SafeAreaView style={[styles.toolbar, { top: Dimensions.get("screen").height - keyboardHeight - 200}]}>
173
+ <TouchableOpacity
174
+ onPress={() => formatText('bold')}
175
+ style={styles.toolButton}
176
+ >
177
+ <Icon name="Bold" />
178
+ </TouchableOpacity>
179
+ <TouchableOpacity
180
+ onPress={() => formatText('italic')}
181
+ style={styles.toolButton}
182
+ >
183
+ <Icon name="Italic" />
184
+ </TouchableOpacity>
185
+ <TouchableOpacity
186
+ onPress={() => formatText('underline')}
187
+ style={styles.toolButton}
188
+ >
189
+ <Icon name="UnderLine" />
190
+ </TouchableOpacity>
191
+ </SafeAreaView>
192
+ ) : null}
193
+ </View>
194
+ );
5
195
  }
196
+
197
+ const styles = StyleSheet.create({
198
+ container: {
199
+ backgroundColor: '#fff',
200
+ gap: 4,
201
+ position: 'relative',
202
+ },
203
+ editorWrapper: { flex: 1 },
204
+ toolbar: {
205
+ flexDirection: 'row',
206
+ borderTopWidth: 1,
207
+ borderColor: Color.gray[200],
208
+ paddingVertical: 8,
209
+ paddingHorizontal: 10,
210
+ position: 'absolute',
211
+ left: 0,
212
+ width: '100%',
213
+ alignItems: 'center',
214
+ },
215
+ toolButton: {
216
+ padding: 8,
217
+ marginHorizontal: 4,
218
+ borderRadius: 6,
219
+ },
220
+ toolText: {
221
+ fontWeight: 'bold',
222
+ },
223
+ webview: { flex: 1 },
224
+ });
@@ -0,0 +1,7 @@
1
+ export interface TextEditorType {
2
+ label?: string;
3
+ hint?: string;
4
+ hasError?: boolean;
5
+ onChange?: (data: string) => void;
6
+ height?: number;
7
+ }
@@ -1,7 +1,9 @@
1
1
  # 🌐 ToastProvider & useToast()
2
+
2
3
  ToastProvider adalah context global untuk menampilkan notifikasi Toast dari bagian manapun dalam aplikasi Anda. Digunakan bersama dengan hook useToast() untuk memanggil toast secara imperatif.
3
4
 
4
5
  ## ⚙️ Setup
6
+
5
7
  1. Bungkus aplikasi Anda dengan ToastProvider
6
8
 
7
9
  ```tsx
@@ -9,18 +11,12 @@ import React from 'react';
9
11
  import { ToastProvider } from '@herca/rn-kit';
10
12
 
11
13
  export default function App() {
12
- return (
13
- <ToastProvider>
14
- {/* Seluruh aplikasi Anda */}
15
- </ToastProvider>
16
- );
14
+ return <ToastProvider>{/* Seluruh aplikasi Anda */}</ToastProvider>;
17
15
  }
18
-
19
16
  ```
20
17
 
21
18
  2. Gunakan useToast() di dalam komponen
22
19
 
23
-
24
20
  ```tsx
25
21
  import React from 'react';
26
22
  import { Button } from '@herca/rn-kit';
@@ -39,33 +35,53 @@ export default function Dashboard() {
39
35
 
40
36
  return <Button title="Simpan" onPress={handleSuccess} />;
41
37
  }
42
-
43
38
  ```
44
39
 
45
40
  ## 🧩 API Reference
41
+
46
42
  ### ✅ ToastProvider
43
+
47
44
  Menyediakan context global toast. Harus membungkus komponen root Anda.
48
45
 
49
46
  | Prop | Type | Required | Description |
50
47
  | ---------- | ----------- | -------- | --------------------------------- |
51
- | `children` | `ReactNode` | ✅ | Semua komponen di dalam aplikasi. |
52
-
48
+ | `children` | `ReactNode` | ✅ | Semua komponen di dalam aplikasi. |
53
49
 
54
50
  ### useToast() Hook
51
+
55
52
  | Function | Type |
56
53
  | ------------------------- | --------------------------------------------------- |
57
54
  | `show(message, options?)` | `(message: string, options?: ToastOptions) => void` |
58
55
 
59
56
  ### ToastOptions
60
- | Option | Type | Default | Description |
61
- | ---------- | ------------------ | ------------------ | ---------------------------------- |
62
- | `color` | `ColorVariantType` | `'default'` | Warna latar belakang dan teks. |
63
- | `icon` | `IconNameProps` | `'ExclamationMark'` | Ikon di samping pesan. |
64
- | `duration` | `number` (ms) | `3000` | Waktu tampil Toast sebelum hilang. |
57
+
58
+ | Option | Type | Default | Description |
59
+ | ---------- | ------------------ | ------------------- | ----------------------------------------------------------------------- |
60
+ | `color` | `ColorVariantType` | `'default'` | Warna latar belakang dan teks. |
61
+ | `icon` | `IconNameProps` | `'ExclamationMark'` | Ikon di samping pesan. |
62
+ | `duration` | `number` (ms) | `3000` | Waktu tampil Toast sebelum hilang. |
63
+ | `children` | `React.ReactNode` | `-` | Konten opsional yang bisa diisi dengan Semua komponen di dalam aplikasi |
64
+
65
+ ### 🎨 ToastColor (Available Values)
66
+
67
+ | Value | Keterangan |
68
+ | --------- | ----------------- |
69
+ | `default` | Abu gelap |
70
+ | `primary` | Biru utama |
71
+ | `success` | Hijau sukses |
72
+ | `danger` | Merah galat |
73
+ | `warning` | Kuning peringatan |
74
+ | `info` | Biru informasi |
75
+ | `orange` | Oranye |
76
+ | `purple` | Ungu |
77
+
78
+ ---
65
79
 
66
80
  ## ❗ Error Handling
81
+
67
82
  Error: useToast must be used within a ToastProvider
83
+
68
84
  ```tsx
69
85
  Error: useToast must be used within a ToastProvider
70
86
 
71
- ```
87
+ ```
@@ -1,10 +1,8 @@
1
- import { useRef, useEffect } from 'react';
2
- import { Animated, StyleSheet, View } from 'react-native';
1
+ import { useEffect, useRef } from 'react';
2
+ import { Animated, Pressable, StyleSheet, View } from 'react-native';
3
3
  import Color from '../Color/Color';
4
4
  import Typography from '../Typography/Typography';
5
5
  import Icon, { type IconNameProps } from '../Icon';
6
- import Button from '../Button/Button';
7
- import Container from '../Ui/Container';
8
6
 
9
7
  type ToastColor =
10
8
  | 'default'
@@ -25,9 +23,9 @@ const COLOR_MAP: Record<
25
23
  }
26
24
  > = {
27
25
  primary: {
28
- bg: Color.base.white100,
26
+ bg: Color.primary[1000],
29
27
  border: Color.gray[50],
30
- text: Color.primary[1000],
28
+ text: Color.base.white100,
31
29
  },
32
30
  default: {
33
31
  bg: Color.gray[700],
@@ -74,6 +72,7 @@ interface ToastProps {
74
72
  icon?: IconNameProps;
75
73
  onClear?: (val: boolean) => void;
76
74
  onHide?: (val: boolean) => void;
75
+ children?: React.ReactNode;
77
76
  }
78
77
  const Toast = ({
79
78
  visible,
@@ -83,6 +82,7 @@ const Toast = ({
83
82
  color = 'default',
84
83
  icon = 'ExclamationMark',
85
84
  onClear,
85
+ children,
86
86
  }: ToastProps) => {
87
87
  const fadeAnim = useRef(new Animated.Value(0)).current;
88
88
  const validColors: ToastColor[] = [
@@ -132,7 +132,7 @@ const Toast = ({
132
132
  <Animated.View
133
133
  style={[styles.toast, baseContainerStyle, { opacity: fadeAnim }]}
134
134
  >
135
- <Container>
135
+ <View style={styles.wrapper}>
136
136
  <View style={styles.container}>
137
137
  <View style={styles.content}>
138
138
  <Icon name={icon} size={20} color={COLOR_MAP[safeColor].text} />
@@ -140,16 +140,14 @@ const Toast = ({
140
140
  {message}
141
141
  </Typography>
142
142
  </View>
143
- <Button
144
- variant="tertiary"
145
- size="small"
146
- title="x"
147
- onPress={handlePressClearButton}
148
- >
149
- <Icon name="Times" size={10} color={COLOR_MAP[safeColor].text} />
150
- </Button>
143
+ <View style={styles.actionWrapper}>
144
+ {children}
145
+ <Pressable onPress={handlePressClearButton}>
146
+ <Icon name="Times" size={10} color={COLOR_MAP[safeColor].text} />
147
+ </Pressable>
148
+ </View>
151
149
  </View>
152
- </Container>
150
+ </View>
153
151
  </Animated.View>
154
152
  );
155
153
  };
@@ -168,12 +166,24 @@ const styles = StyleSheet.create({
168
166
  container: {
169
167
  flexDirection: 'row',
170
168
  justifyContent: 'space-between',
169
+ alignItems: 'center',
171
170
  },
172
171
  content: {
173
172
  alignItems: 'center',
174
173
  flexDirection: 'row',
175
174
  gap: 8,
176
175
  },
176
+ wrapper: {
177
+ paddingHorizontal: 12,
178
+ paddingVertical: 8,
179
+ minHeight: 48,
180
+ justifyContent: 'center',
181
+ },
182
+ actionWrapper: {
183
+ flexDirection: 'row',
184
+ alignItems: 'center',
185
+ gap: 10,
186
+ },
177
187
  });
178
188
 
179
189
  export default Toast;
@@ -7,6 +7,7 @@ interface ToastOptions {
7
7
  color?: ColorVariantType;
8
8
  icon?: IconNameProps;
9
9
  duration?: number;
10
+ children?: React.ReactNode;
10
11
  }
11
12
 
12
13
  interface ToastContextType {
@@ -39,6 +40,7 @@ export const ToastProvider = ({ children }: { children: ReactNode }) => {
39
40
  color={options.color}
40
41
  icon={options.icon}
41
42
  duration={options.duration}
43
+ children={options.children ? options.children : undefined}
42
44
  />
43
45
  </ToastContext.Provider>
44
46
  );