@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.
- package/lib/module/BottomSheet/BottomSheet.js +4 -1
- package/lib/module/BottomSheet/BottomSheet.js.map +1 -1
- package/lib/module/Button/tests/Button.test.js +70 -0
- package/lib/module/Button/tests/Button.test.js.map +1 -0
- package/lib/module/Button/type.js +1 -1
- package/lib/module/DatePicker/TimePicker.js +84 -39
- package/lib/module/DatePicker/TimePicker.js.map +1 -1
- package/lib/module/Icon/Icon.js +7 -1
- package/lib/module/Icon/Icon.js.map +1 -1
- package/lib/module/Icon/README.md +6 -0
- package/lib/module/Icon/icons/Bold.js +25 -0
- package/lib/module/Icon/icons/Bold.js.map +1 -0
- package/lib/module/Icon/icons/Italic.js +25 -0
- package/lib/module/Icon/icons/Italic.js.map +1 -0
- package/lib/module/Icon/icons/UnderLine.js +25 -0
- package/lib/module/Icon/icons/UnderLine.js.map +1 -0
- package/lib/module/Input/InputOtp.js +7 -5
- package/lib/module/Input/InputOtp.js.map +1 -1
- package/lib/module/Input/README_INPUT_OTP.md +8 -7
- package/lib/module/TextEditor/TextEditor.js +206 -4
- package/lib/module/TextEditor/TextEditor.js.map +1 -1
- package/lib/module/Toast/README.md +32 -16
- package/lib/module/Toast/Toast.js +31 -19
- package/lib/module/Toast/Toast.js.map +1 -1
- package/lib/module/Toast/ToastContext.js +2 -1
- package/lib/module/Toast/ToastContext.js.map +1 -1
- package/lib/typescript/src/Button/tests/Button.test.d.ts +2 -0
- package/lib/typescript/src/Button/tests/Button.test.d.ts.map +1 -0
- package/lib/typescript/src/DatePicker/TimePicker.d.ts.map +1 -1
- package/lib/typescript/src/Icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/Icon/icons/Bold.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/Bold.d.ts.map +1 -0
- package/lib/typescript/src/Icon/icons/Italic.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/Italic.d.ts.map +1 -0
- package/lib/typescript/src/Icon/icons/UnderLine.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/UnderLine.d.ts.map +1 -0
- package/lib/typescript/src/Icon/type.d.ts +1 -1
- package/lib/typescript/src/Icon/type.d.ts.map +1 -1
- package/lib/typescript/src/Input/InputOtp.d.ts +1 -0
- package/lib/typescript/src/Input/InputOtp.d.ts.map +1 -1
- package/lib/typescript/src/TextEditor/TextEditor.d.ts +2 -1
- package/lib/typescript/src/TextEditor/TextEditor.d.ts.map +1 -1
- package/lib/typescript/src/TextEditor/types.d.ts +7 -0
- package/lib/typescript/src/TextEditor/types.d.ts.map +1 -1
- package/lib/typescript/src/Toast/Toast.d.ts +2 -1
- package/lib/typescript/src/Toast/Toast.d.ts.map +1 -1
- package/lib/typescript/src/Toast/ToastContext.d.ts +1 -0
- package/lib/typescript/src/Toast/ToastContext.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.tsx +4 -1
- package/src/Button/tests/Button.test.tsx +49 -0
- package/src/Button/type.tsx +1 -1
- package/src/DatePicker/TimePicker.tsx +116 -52
- package/src/Icon/Icon.tsx +6 -0
- package/src/Icon/README.md +6 -0
- package/src/Icon/icons/Bold.tsx +22 -0
- package/src/Icon/icons/Italic.tsx +22 -0
- package/src/Icon/icons/UnderLine.tsx +26 -0
- package/src/Icon/type.tsx +3 -0
- package/src/Input/InputOtp.tsx +7 -3
- package/src/Input/README_INPUT_OTP.md +8 -7
- package/src/TextEditor/TextEditor.tsx +222 -3
- package/src/TextEditor/types.tsx +7 -0
- package/src/Toast/README.md +32 -16
- package/src/Toast/Toast.tsx +26 -16
- package/src/Toast/ToastContext.tsx +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextEditor.d.ts","sourceRoot":"","sources":["../../../../src/TextEditor/TextEditor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TextEditor.d.ts","sourceRoot":"","sources":["../../../../src/TextEditor/TextEditor.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAK9C,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,MAAY,GACb,EAAE,cAAc,2CAyKhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/TextEditor/types.tsx"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/TextEditor/types.tsx"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -8,7 +8,8 @@ interface ToastProps {
|
|
|
8
8
|
icon?: IconNameProps;
|
|
9
9
|
onClear?: (val: boolean) => void;
|
|
10
10
|
onHide?: (val: boolean) => void;
|
|
11
|
+
children?: React.ReactNode;
|
|
11
12
|
}
|
|
12
|
-
declare const Toast: ({ visible, message, duration, onHide, color, icon, onClear, }: ToastProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
declare const Toast: ({ visible, message, duration, onHide, color, icon, onClear, children, }: ToastProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
13
14
|
export default Toast;
|
|
14
15
|
//# sourceMappingURL=Toast.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../../src/Toast/Toast.tsx"],"names":[],"mappings":"AAIA,OAAa,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../../src/Toast/Toast.tsx"],"names":[],"mappings":"AAIA,OAAa,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnD,KAAK,UAAU,GACX,SAAS,GACT,SAAS,GACT,QAAQ,GACR,SAAS,GACT,SAAS,GACT,MAAM,GACN,QAAQ,GACR,QAAQ,CAAC;AAoDb,UAAU,UAAU;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AACD,QAAA,MAAM,KAAK,GAAI,yEASZ,UAAU,mDAmEZ,CAAC;AAoCF,eAAe,KAAK,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastContext.d.ts","sourceRoot":"","sources":["../../../../src/Toast/ToastContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,UAAU,YAAY;IACpB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ToastContext.d.ts","sourceRoot":"","sources":["../../../../src/Toast/ToastContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,UAAU,YAAY;IACpB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;CACzD;AAID,eAAO,MAAM,aAAa,GAAI,cAAc;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,4CA4BlE,CAAC;AAEF,eAAO,MAAM,QAAQ,QAAO,gBAI3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herca/rn-kit",
|
|
3
|
-
"
|
|
4
|
-
"version": "0.1.26",
|
|
3
|
+
"version": "0.1.28",
|
|
5
4
|
"description": "React Native UI Kit",
|
|
6
5
|
"main": "./lib/module/index.js",
|
|
7
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -94,6 +93,7 @@
|
|
|
94
93
|
"@react-native-documents/picker": "^10.1.3",
|
|
95
94
|
"@react-native-documents/viewer": "^1.0.1",
|
|
96
95
|
"@react-navigation/native": "^7.1.14",
|
|
96
|
+
"@testing-library/react-native": "^13.2.0",
|
|
97
97
|
"react-native-blob-util": "^0.22.2",
|
|
98
98
|
"react-native-image-picker": "^8.2.1",
|
|
99
99
|
"react-native-pager-view": "^6.8.1",
|
|
@@ -249,9 +249,12 @@ const styles = StyleSheet.create({
|
|
|
249
249
|
},
|
|
250
250
|
contentWithFooter: {},
|
|
251
251
|
footer: {
|
|
252
|
+
borderTopWidth: 1,
|
|
253
|
+
borderTopColor: Color.gray[100],
|
|
252
254
|
backgroundColor: Color.base.white100,
|
|
253
255
|
position: 'absolute',
|
|
254
|
-
|
|
256
|
+
paddingTop: 10,
|
|
257
|
+
marginBottom: 32,
|
|
255
258
|
bottom: 0,
|
|
256
259
|
left: 0,
|
|
257
260
|
right: 0,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import Button from '../Button';
|
|
5
|
+
|
|
6
|
+
describe('Button Component', () => {
|
|
7
|
+
it('renders with title', () => {
|
|
8
|
+
const { getByText } = render(<Button title="Submit" onPress={() => {}} />);
|
|
9
|
+
expect(getByText('Submit')).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('calls onPress when pressed', () => {
|
|
13
|
+
const mockFn = jest.fn();
|
|
14
|
+
const { getByText } = render(<Button title="Click me" onPress={mockFn} />);
|
|
15
|
+
fireEvent.press(getByText('Click me'));
|
|
16
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not call onPress when disabled', () => {
|
|
20
|
+
const mockFn = jest.fn();
|
|
21
|
+
const { getByText } = render(
|
|
22
|
+
<Button title="Disabled" onPress={mockFn} disabled />
|
|
23
|
+
);
|
|
24
|
+
fireEvent.press(getByText('Disabled'));
|
|
25
|
+
expect(mockFn).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders loading indicator when loading', () => {
|
|
29
|
+
const { getByTestId } = render(
|
|
30
|
+
<Button title="Load" onPress={() => {}} loading />
|
|
31
|
+
);
|
|
32
|
+
// You might want to set `testID="loading-indicator"` in <ActivityIndicator /> in the component
|
|
33
|
+
// For now, we check for presence of ActivityIndicator via accessibilityRole
|
|
34
|
+
const indicator = getByTestId('ActivityIndicator');
|
|
35
|
+
expect(indicator).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders custom child instead of title', () => {
|
|
39
|
+
const { queryByText, getByText } = render(
|
|
40
|
+
<Button onPress={() => {}}>
|
|
41
|
+
<React.Fragment>
|
|
42
|
+
<Text>Custom Child</Text>
|
|
43
|
+
</React.Fragment>
|
|
44
|
+
</Button>
|
|
45
|
+
);
|
|
46
|
+
expect(getByText('Custom Child')).toBeTruthy();
|
|
47
|
+
expect(queryByText('title')).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/Button/type.tsx
CHANGED
|
@@ -255,7 +255,7 @@ export const COLOR_MAP: Record<
|
|
|
255
255
|
tertiaryFocusBorderColor: Color.info[200],
|
|
256
256
|
tertiaryPressTextColor: Color.info[700],
|
|
257
257
|
tertiaryPressBorderColor: 'transparent',
|
|
258
|
-
tertiaryDisabledTextColor: Color.info[
|
|
258
|
+
tertiaryDisabledTextColor: Color.info[200],
|
|
259
259
|
tertiaryDisabledBorderColor: Color.base.white100,
|
|
260
260
|
},
|
|
261
261
|
orange: {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import React, { useRef, useState, useEffect } from 'react';
|
|
1
|
+
import React, { useRef, useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
|
-
ScrollView,
|
|
5
4
|
StyleSheet,
|
|
6
5
|
Text,
|
|
7
|
-
type NativeScrollEvent,
|
|
8
|
-
type NativeSyntheticEvent,
|
|
9
6
|
Dimensions,
|
|
7
|
+
Animated,
|
|
8
|
+
type NativeSyntheticEvent,
|
|
9
|
+
type NativeScrollEvent,
|
|
10
|
+
ScrollView,
|
|
10
11
|
} from 'react-native';
|
|
11
12
|
import BottomSheet from '../BottomSheet/BottomSheet';
|
|
12
13
|
import Button from '../Button/Button';
|
|
@@ -17,6 +18,7 @@ const ITEM_HEIGHT = 40;
|
|
|
17
18
|
const SCREEN_WIDTH = Dimensions.get('window').width;
|
|
18
19
|
const VISIBLE_ITEMS = 5;
|
|
19
20
|
const MIDDLE_INDEX = Math.floor(VISIBLE_ITEMS / 2);
|
|
21
|
+
const REPEAT_COUNT = 5;
|
|
20
22
|
|
|
21
23
|
interface TimePickerProps {
|
|
22
24
|
isOpen: boolean;
|
|
@@ -26,42 +28,61 @@ interface TimePickerProps {
|
|
|
26
28
|
|
|
27
29
|
const generateItems = (max: number) => Array.from({ length: max }, (_, i) => i);
|
|
28
30
|
|
|
31
|
+
const repeatItems = (items: number[], repeatCount: number) =>
|
|
32
|
+
Array.from({ length: repeatCount }).flatMap(() => items);
|
|
33
|
+
|
|
29
34
|
export default function TimePickerWheel({
|
|
30
35
|
isOpen,
|
|
31
36
|
onClose,
|
|
32
37
|
onChange,
|
|
33
38
|
}: TimePickerProps) {
|
|
34
|
-
const [selectedHour, setSelectedHour] = useState(0);
|
|
35
|
-
const [selectedMinute, setSelectedMinute] = useState(0);
|
|
39
|
+
const [selectedHour, setSelectedHour] = useState<number>(0);
|
|
40
|
+
const [selectedMinute, setSelectedMinute] = useState<number>(0);
|
|
36
41
|
|
|
37
42
|
const hourRef = useRef<ScrollView>(null);
|
|
38
43
|
const minuteRef = useRef<ScrollView>(null);
|
|
39
44
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
];
|
|
46
|
-
};
|
|
45
|
+
const hourScrollY = useRef(new Animated.Value(0)).current;
|
|
46
|
+
const minuteScrollY = useRef(new Animated.Value(0)).current;
|
|
47
|
+
|
|
48
|
+
const hoursBase = generateItems(24);
|
|
49
|
+
const minutesBase = generateItems(60);
|
|
47
50
|
|
|
48
|
-
const hours =
|
|
49
|
-
|
|
51
|
+
const hours = useMemo(
|
|
52
|
+
() => repeatItems(hoursBase, REPEAT_COUNT),
|
|
53
|
+
[hoursBase]
|
|
54
|
+
);
|
|
55
|
+
const minutes = useMemo(
|
|
56
|
+
() => repeatItems(minutesBase, REPEAT_COUNT),
|
|
57
|
+
[minutesBase]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const CENTER_INDEX_HOUR =
|
|
61
|
+
Math.floor(hours.length / 2 / 24) * 24 + selectedHour;
|
|
62
|
+
const CENTER_INDEX_MINUTE =
|
|
63
|
+
Math.floor(minutes.length / 2 / 60) * 60 + selectedMinute;
|
|
50
64
|
|
|
51
65
|
useEffect(() => {
|
|
52
66
|
if (isOpen) {
|
|
53
67
|
setTimeout(() => {
|
|
54
|
-
scrollToIndex(hourRef,
|
|
55
|
-
scrollToIndex(minuteRef,
|
|
68
|
+
scrollToIndex(hourRef, CENTER_INDEX_HOUR);
|
|
69
|
+
scrollToIndex(minuteRef, CENTER_INDEX_MINUTE);
|
|
56
70
|
}, 100);
|
|
57
71
|
}
|
|
58
|
-
}, [
|
|
72
|
+
}, [
|
|
73
|
+
isOpen,
|
|
74
|
+
selectedMinute,
|
|
75
|
+
selectedHour,
|
|
76
|
+
CENTER_INDEX_HOUR,
|
|
77
|
+
CENTER_INDEX_MINUTE,
|
|
78
|
+
]);
|
|
59
79
|
|
|
60
80
|
const scrollToIndex = (
|
|
61
81
|
ref: React.RefObject<ScrollView | null>,
|
|
62
|
-
index: number
|
|
82
|
+
index: number,
|
|
83
|
+
animated: boolean = true
|
|
63
84
|
) => {
|
|
64
|
-
ref.current?.scrollTo({ y: index * ITEM_HEIGHT, animated
|
|
85
|
+
ref.current?.scrollTo({ y: index * ITEM_HEIGHT, animated });
|
|
65
86
|
};
|
|
66
87
|
|
|
67
88
|
const handleScrollEnd = (
|
|
@@ -70,14 +91,23 @@ export default function TimePickerWheel({
|
|
|
70
91
|
) => {
|
|
71
92
|
const offsetY = event.nativeEvent.contentOffset.y;
|
|
72
93
|
const index = Math.round(offsetY / ITEM_HEIGHT);
|
|
94
|
+
|
|
73
95
|
if (type === 'hour') {
|
|
74
|
-
const
|
|
75
|
-
setSelectedHour(
|
|
76
|
-
|
|
96
|
+
const realValue = hours[index % 24] as number;
|
|
97
|
+
setSelectedHour(realValue);
|
|
98
|
+
|
|
99
|
+
const newCenter = Math.floor(hours.length / 2 / 24) * 24 + realValue;
|
|
100
|
+
if (Math.abs(index - newCenter) > 12) {
|
|
101
|
+
scrollToIndex(hourRef, newCenter, false);
|
|
102
|
+
}
|
|
77
103
|
} else {
|
|
78
|
-
const
|
|
79
|
-
setSelectedMinute(
|
|
80
|
-
|
|
104
|
+
const realValue = minutes[index % 60] as number;
|
|
105
|
+
setSelectedMinute(realValue);
|
|
106
|
+
|
|
107
|
+
const newCenter = Math.floor(minutes.length / 2 / 60) * 60 + realValue;
|
|
108
|
+
if (Math.abs(index - newCenter) > 12) {
|
|
109
|
+
scrollToIndex(minuteRef, newCenter, false);
|
|
110
|
+
}
|
|
81
111
|
}
|
|
82
112
|
};
|
|
83
113
|
|
|
@@ -86,22 +116,44 @@ export default function TimePickerWheel({
|
|
|
86
116
|
onClose?.();
|
|
87
117
|
};
|
|
88
118
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
const renderAnimatedItem = (
|
|
120
|
+
value: number,
|
|
121
|
+
type: 'hour' | 'minute',
|
|
122
|
+
index: number,
|
|
123
|
+
scrollY: Animated.Value
|
|
124
|
+
) => {
|
|
125
|
+
const normalizedValue =
|
|
126
|
+
type === 'hour' ? ((value % 24) + 24) % 24 : ((value % 60) + 60) % 60;
|
|
127
|
+
|
|
128
|
+
const inputRange = [
|
|
129
|
+
(index - 2) * ITEM_HEIGHT,
|
|
130
|
+
(index - 1) * ITEM_HEIGHT,
|
|
131
|
+
index * ITEM_HEIGHT,
|
|
132
|
+
(index + 1) * ITEM_HEIGHT,
|
|
133
|
+
(index + 2) * ITEM_HEIGHT,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const scale = scrollY.interpolate({
|
|
137
|
+
inputRange,
|
|
138
|
+
outputRange: [0.7, 0.85, 1, 0.85, 0.7],
|
|
139
|
+
extrapolate: 'clamp',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const opacity = scrollY.interpolate({
|
|
143
|
+
inputRange,
|
|
144
|
+
outputRange: [0.3, 0.6, 1, 0.6, 0.3],
|
|
145
|
+
extrapolate: 'clamp',
|
|
146
|
+
});
|
|
98
147
|
|
|
99
148
|
return (
|
|
100
|
-
<View
|
|
101
|
-
|
|
102
|
-
|
|
149
|
+
<Animated.View
|
|
150
|
+
key={`${type}-${index}`}
|
|
151
|
+
style={[styles.item, { transform: [{ scale }], opacity }]}
|
|
152
|
+
>
|
|
153
|
+
<Text style={styles.itemText}>
|
|
154
|
+
{normalizedValue.toString().padStart(2, '0')}
|
|
103
155
|
</Text>
|
|
104
|
-
</View>
|
|
156
|
+
</Animated.View>
|
|
105
157
|
);
|
|
106
158
|
};
|
|
107
159
|
|
|
@@ -136,7 +188,7 @@ export default function TimePickerWheel({
|
|
|
136
188
|
weight="semibold"
|
|
137
189
|
color={Color.gray[900]}
|
|
138
190
|
center
|
|
139
|
-
style={
|
|
191
|
+
style={styles.hintTime}
|
|
140
192
|
>
|
|
141
193
|
Pilih Waktu
|
|
142
194
|
</Typography>
|
|
@@ -146,31 +198,46 @@ export default function TimePickerWheel({
|
|
|
146
198
|
<View style={styles.highlightOverlay} pointerEvents="none">
|
|
147
199
|
<View style={styles.highlight} />
|
|
148
200
|
</View>
|
|
201
|
+
|
|
149
202
|
<View style={styles.scrollContainer}>
|
|
150
203
|
<View style={styles.pickerColumn}>
|
|
151
|
-
<ScrollView
|
|
204
|
+
<Animated.ScrollView
|
|
152
205
|
ref={hourRef}
|
|
153
206
|
showsVerticalScrollIndicator={false}
|
|
154
207
|
snapToInterval={ITEM_HEIGHT}
|
|
155
208
|
decelerationRate="fast"
|
|
156
209
|
onMomentumScrollEnd={(e) => handleScrollEnd(e, 'hour')}
|
|
210
|
+
onScroll={Animated.event(
|
|
211
|
+
[{ nativeEvent: { contentOffset: { y: hourScrollY } } }],
|
|
212
|
+
{ useNativeDriver: true }
|
|
213
|
+
)}
|
|
214
|
+
scrollEventThrottle={16}
|
|
157
215
|
contentContainerStyle={styles.scrollContent}
|
|
158
216
|
>
|
|
159
|
-
{hours.map((h) =>
|
|
160
|
-
|
|
217
|
+
{hours.map((h, i) =>
|
|
218
|
+
renderAnimatedItem(h, 'hour', i, hourScrollY)
|
|
219
|
+
)}
|
|
220
|
+
</Animated.ScrollView>
|
|
161
221
|
</View>
|
|
162
222
|
|
|
163
223
|
<View style={styles.pickerColumn}>
|
|
164
|
-
<ScrollView
|
|
224
|
+
<Animated.ScrollView
|
|
165
225
|
ref={minuteRef}
|
|
166
226
|
showsVerticalScrollIndicator={false}
|
|
167
227
|
snapToInterval={ITEM_HEIGHT}
|
|
168
228
|
decelerationRate="fast"
|
|
169
229
|
onMomentumScrollEnd={(e) => handleScrollEnd(e, 'minute')}
|
|
230
|
+
onScroll={Animated.event(
|
|
231
|
+
[{ nativeEvent: { contentOffset: { y: minuteScrollY } } }],
|
|
232
|
+
{ useNativeDriver: true }
|
|
233
|
+
)}
|
|
234
|
+
scrollEventThrottle={16}
|
|
170
235
|
contentContainerStyle={styles.scrollContent}
|
|
171
236
|
>
|
|
172
|
-
{minutes.map((m) =>
|
|
173
|
-
|
|
237
|
+
{minutes.map((m, i) =>
|
|
238
|
+
renderAnimatedItem(m, 'minute', i, minuteScrollY)
|
|
239
|
+
)}
|
|
240
|
+
</Animated.ScrollView>
|
|
174
241
|
</View>
|
|
175
242
|
</View>
|
|
176
243
|
</View>
|
|
@@ -209,13 +276,9 @@ const styles = StyleSheet.create({
|
|
|
209
276
|
alignItems: 'center',
|
|
210
277
|
},
|
|
211
278
|
itemText: {
|
|
212
|
-
fontSize: 18,
|
|
213
|
-
color: Color.gray[400],
|
|
214
|
-
},
|
|
215
|
-
selectedItemText: {
|
|
216
|
-
color: Color.gray[900],
|
|
217
|
-
fontWeight: 'bold',
|
|
218
279
|
fontSize: 22,
|
|
280
|
+
color: Color.gray[900],
|
|
281
|
+
fontWeight: '600',
|
|
219
282
|
},
|
|
220
283
|
highlightOverlay: {
|
|
221
284
|
position: 'absolute',
|
|
@@ -231,4 +294,5 @@ const styles = StyleSheet.create({
|
|
|
231
294
|
zIndex: -1,
|
|
232
295
|
backgroundColor: Color.primary[50],
|
|
233
296
|
},
|
|
297
|
+
hintTime: { marginBottom: 16 },
|
|
234
298
|
});
|
package/src/Icon/Icon.tsx
CHANGED
|
@@ -56,6 +56,9 @@ import QuestionCircleOutline from './icons/QuestionCircleOutline';
|
|
|
56
56
|
import ShieldFill from './icons/ShieldFill';
|
|
57
57
|
import Users from './icons/Users';
|
|
58
58
|
import WhatsAppFill from './icons/WhatsappFill';
|
|
59
|
+
import Bold from './icons/Bold';
|
|
60
|
+
import Italic from './icons/Italic';
|
|
61
|
+
import UnderLine from './icons/UnderLine';
|
|
59
62
|
|
|
60
63
|
const icons: Record<
|
|
61
64
|
IconNameProps,
|
|
@@ -116,6 +119,9 @@ const icons: Record<
|
|
|
116
119
|
'shield-fill': ShieldFill,
|
|
117
120
|
'Users': Users,
|
|
118
121
|
'whatsapp-fill': WhatsAppFill,
|
|
122
|
+
'Bold': Bold,
|
|
123
|
+
'Italic': Italic,
|
|
124
|
+
'UnderLine': UnderLine,
|
|
119
125
|
};
|
|
120
126
|
|
|
121
127
|
const Icon: React.FC<IconProps> = ({
|
package/src/Icon/README.md
CHANGED
|
@@ -53,6 +53,7 @@ export default function App() {
|
|
|
53
53
|
- ArrowLeft
|
|
54
54
|
- ArrowRight
|
|
55
55
|
- ArrowUp
|
|
56
|
+
- Bold
|
|
56
57
|
- Camera
|
|
57
58
|
- Calendar
|
|
58
59
|
- Check
|
|
@@ -64,12 +65,14 @@ export default function App() {
|
|
|
64
65
|
- Filter
|
|
65
66
|
- Home
|
|
66
67
|
- Image
|
|
68
|
+
- Italic
|
|
67
69
|
- Pdf
|
|
68
70
|
- RadioButton
|
|
69
71
|
- Search
|
|
70
72
|
- StickyNote
|
|
71
73
|
- Times
|
|
72
74
|
- User
|
|
75
|
+
- UnderLine
|
|
73
76
|
- x-circle
|
|
74
77
|
- Plus
|
|
75
78
|
- Bell
|
|
@@ -100,3 +103,6 @@ export default function App() {
|
|
|
100
103
|
- shield-fill
|
|
101
104
|
- Users
|
|
102
105
|
- whatsapp-fill
|
|
106
|
+
- Bold
|
|
107
|
+
- UnderLine
|
|
108
|
+
- Italic
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Svg, { Path } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
style?: object;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Bold: React.FC<Props> = ({ size = 24, color = '#000', style }) => (
|
|
11
|
+
<Svg width={size} height={size} viewBox="0 0 13 16" fill="none" style={style}>
|
|
12
|
+
<Path
|
|
13
|
+
d="M1 8.07682H7.66667C9.50762 8.07682 11 6.58444 11 4.74349C11 2.90254 9.50762 1.41016 7.66667 1.41016H1V8.07682ZM1 8.07682H8.5C10.3409 8.07682 11.8333 9.56921 11.8333 11.4102C11.8333 13.2511 10.3409 14.7435 8.5 14.7435H1V8.07682Z"
|
|
14
|
+
stroke={color}
|
|
15
|
+
stroke-width="2"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
</Svg>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default Bold;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Svg, { Path } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
style?: object;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Italic: React.FC<Props> = ({ size = 24, color = '#909090', style }) => (
|
|
11
|
+
<Svg width={size} height={size} viewBox="0 0 14 16" fill="none" style={style}>
|
|
12
|
+
<Path
|
|
13
|
+
d="M12.8334 1.41016H5.33341M8.66675 14.7435H1.16675M9.50008 1.41016L4.50008 14.7435"
|
|
14
|
+
stroke={color}
|
|
15
|
+
stroke-width="2"
|
|
16
|
+
stroke-linecap="round"
|
|
17
|
+
stroke-linejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
</Svg>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default Italic;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Svg, { Path } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
size?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
style?: object;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const UnderLine: React.FC<Props> = ({
|
|
11
|
+
size = 24,
|
|
12
|
+
color = '#909090',
|
|
13
|
+
style,
|
|
14
|
+
}) => (
|
|
15
|
+
<Svg width={size} height={size} viewBox="0 0 16 17" fill="none" style={style}>
|
|
16
|
+
<Path
|
|
17
|
+
d="M12.9999 1.41016V7.24349C12.9999 10.0049 10.7613 12.2435 7.99992 12.2435C5.23849 12.2435 2.99992 10.0049 2.99992 7.24349V1.41016M1.33325 15.5768H14.6666"
|
|
18
|
+
stroke={color}
|
|
19
|
+
stroke-width="2"
|
|
20
|
+
stroke-linecap="round"
|
|
21
|
+
stroke-linejoin="round"
|
|
22
|
+
/>
|
|
23
|
+
</Svg>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export default UnderLine;
|
package/src/Icon/type.tsx
CHANGED
package/src/Input/InputOtp.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { StyleSheet, TextInput, View } from 'react-native';
|
|
3
3
|
import Color from '../Color/Color';
|
|
4
4
|
import LabelForm from '../LabelForm/LabelForm';
|
|
5
5
|
import Typography from '../Typography/Typography';
|
|
@@ -10,6 +10,7 @@ type InputOtpProps = {
|
|
|
10
10
|
label?: string;
|
|
11
11
|
hint?: string;
|
|
12
12
|
hasError?: boolean;
|
|
13
|
+
inputCenter?: boolean;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
const InputOtp: React.FC<InputOtpProps> = ({
|
|
@@ -18,6 +19,7 @@ const InputOtp: React.FC<InputOtpProps> = ({
|
|
|
18
19
|
label,
|
|
19
20
|
hint,
|
|
20
21
|
hasError,
|
|
22
|
+
inputCenter = false,
|
|
21
23
|
}) => {
|
|
22
24
|
const inputs = useRef<Array<TextInput | null>>([]);
|
|
23
25
|
const values = useRef<string[]>(Array(length).fill(''));
|
|
@@ -50,7 +52,10 @@ const InputOtp: React.FC<InputOtpProps> = ({
|
|
|
50
52
|
<View style={styles.gap4}>
|
|
51
53
|
{label ? <LabelForm title={label} /> : null}
|
|
52
54
|
<View
|
|
53
|
-
style={
|
|
55
|
+
style={[
|
|
56
|
+
styles.container,
|
|
57
|
+
{ justifyContent: inputCenter ? 'center' : 'flex-start' },
|
|
58
|
+
]}
|
|
54
59
|
onLayout={(e) => {
|
|
55
60
|
// cek apakah lebar container itu cukup untuk menampilkan semua input
|
|
56
61
|
containerWidth.current = e.nativeEvent.layout.width;
|
|
@@ -101,7 +106,6 @@ const styles = StyleSheet.create({
|
|
|
101
106
|
container: {
|
|
102
107
|
flexDirection: 'row',
|
|
103
108
|
gap: 10,
|
|
104
|
-
justifyContent: 'flex-start',
|
|
105
109
|
},
|
|
106
110
|
input: {
|
|
107
111
|
flexGrow: 1,
|
|
@@ -24,13 +24,14 @@ Salin file `InputOtp.tsx` ke proyek React Native Anda dan import sesuai kebutuha
|
|
|
24
24
|
|
|
25
25
|
## Props
|
|
26
26
|
|
|
27
|
-
| Nama
|
|
28
|
-
|
|
29
|
-
| `length`
|
|
30
|
-
| `onChange`
|
|
31
|
-
| `label`
|
|
32
|
-
| `hint`
|
|
33
|
-
| `hasError`
|
|
27
|
+
| Nama | Tipe | Default | Deskripsi |
|
|
28
|
+
| ------------- | ----------------------- | ------- | ---------------------------------------------------------------------------------------- |
|
|
29
|
+
| `length` | `number` | 6 | Jumlah kotak input OTP yang ingin ditampilkan |
|
|
30
|
+
| `onChange` | `(otp: string) => void` | - | Callback saat isi OTP berubah, menerima OTP lengkap sebagai string |
|
|
31
|
+
| `label` | `string` | - | Label yang tampil di atas kotak input |
|
|
32
|
+
| `hint` | `string` | - | Pesan kecil di bawah input (misal instruksi atau error) |
|
|
33
|
+
| `hasError` | `boolean` | false | Jika `true`, border input berubah warna merah (indikasi error) |
|
|
34
|
+
| `inputCenter` | `boolean` | false | Jika `true`, maka seluruh input field akan memposisikan diri ke tengah secara horizontal |
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|