ripal-ui 1.0.1 → 1.0.2

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.
@@ -0,0 +1,88 @@
1
+ import React, { useState } from "react";
2
+ import { ScrollView, StyleSheet, View } from "react-native";
3
+ import config from "../config";
4
+ import Text from "./Text";
5
+ import Inline from "./Inline";
6
+ import Ionicons from "@expo/vector-icons/Ionicons";
7
+
8
+ type OptionType<T> =
9
+ | T[] // Array of objects
10
+ | string[]; // Array of strings
11
+
12
+ interface DropdownProps<T> {
13
+ options: OptionType<T>;
14
+ value: T | null;
15
+ setValue: (value: T) => void;
16
+ onChange?: (value: T) => void;
17
+ color?: string;
18
+ label?: string | null;
19
+ placeholder?: string;
20
+ objectKey?: keyof T | null; // Key for accessing properties if options are objects
21
+ withSearch?: boolean;
22
+ }
23
+
24
+ const Dropdown = <T,>({
25
+ options,
26
+ value,
27
+ setValue,
28
+ onChange,
29
+ color = config.colors.primary,
30
+ label = null,
31
+ placeholder = 'Choose',
32
+ objectKey = null,
33
+ withSearch = false,
34
+ }: DropdownProps<T>) => {
35
+ const [isExpanded, setExpanded] = useState(false);
36
+ const isObject = Array.isArray(options) && typeof options[0] === 'object';
37
+
38
+ return (
39
+ <View style={{ gap: 5 }}>
40
+ {label !== null && <Text size={12} weight="400Regular">{label}</Text>}
41
+ <View style={styles.area}>
42
+ <Inline onPress={() => setExpanded(!isExpanded)}>
43
+ <Text style={{ flexGrow: 1 }}>{value || placeholder}</Text>
44
+ <Ionicons name={isExpanded ? 'chevron-up-outline' : 'chevron-down-outline'} />
45
+ </Inline>
46
+
47
+ {isExpanded && (
48
+ <ScrollView style={{ maxHeight: 200 }}>
49
+ {(options as OptionType<T>).map((opt, o) => {
50
+ const isActive = isObject ? opt[objectKey as keyof T] === value : opt === value;
51
+ const displayValue = isObject ? opt[objectKey as keyof T] : opt;
52
+
53
+ return (
54
+ <Inline key={o} style={styles.item} onPress={() => {
55
+ if (onChange) {
56
+ onChange(opt);
57
+ } else {
58
+ setValue(isObject ? opt[objectKey as keyof T] : opt);
59
+ }
60
+ setExpanded(false);
61
+ }}>
62
+ <Text weight={isActive ? '600SemiBold' : '400Regular'} color={isActive ? color : config.colors.slate[500]}>
63
+ {displayValue}
64
+ </Text>
65
+ </Inline>
66
+ );
67
+ })}
68
+ </ScrollView>
69
+ )}
70
+ </View>
71
+ </View>
72
+ );
73
+ };
74
+
75
+ const styles = StyleSheet.create({
76
+ area: {
77
+ padding: 15,
78
+ borderWidth: 1,
79
+ borderColor: config.colors.slate[200],
80
+ borderRadius: 12,
81
+ gap: 15,
82
+ },
83
+ item: {
84
+ height: 40,
85
+ },
86
+ });
87
+
88
+ export default Dropdown;
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { Pressable, PressableProps, StyleSheet, ViewStyle } from "react-native";
3
+
4
+ interface InlineProps extends PressableProps {
5
+ children: React.ReactNode;
6
+ alignItems?: "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
7
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
8
+ gap?: number;
9
+ style?: ViewStyle;
10
+ }
11
+
12
+ const Inline: React.FC<InlineProps> = ({
13
+ children,
14
+ alignItems = "center",
15
+ justifyContent = "flex-start",
16
+ gap = 20,
17
+ style,
18
+ onPress,
19
+ onLayout,
20
+ }) => {
21
+ return (
22
+ <Pressable
23
+ style={{
24
+ flexDirection: 'row',
25
+ alignItems,
26
+ justifyContent,
27
+ gap,
28
+ ...style,
29
+ }}
30
+ onPress={onPress}
31
+ onLayout={onLayout}
32
+ >
33
+ {children}
34
+ </Pressable>
35
+ );
36
+ };
37
+
38
+ export default Inline;
@@ -0,0 +1,75 @@
1
+ import React, { useState } from "react";
2
+ import { View, TextInput, StyleSheet, TextInputProps, ViewStyle } from "react-native";
3
+ import Text from "./Text";
4
+ import Inline from "./Inline";
5
+ import config from "../config";
6
+
7
+ interface InputProps {
8
+ value?: string;
9
+ label?: string | null;
10
+ left?: React.ReactNode | null; // Allows for any valid React node
11
+ right?: React.ReactNode | null; // Allows for any valid React node
12
+ height?: number;
13
+ placeholder?: string | null;
14
+ mode?: TextInputProps["keyboardType"]; // Use keyboardType from TextInputProps
15
+ secureTextEntry?: boolean;
16
+ onChangeText: (text: string) => void; // Function to handle text changes
17
+ }
18
+
19
+ const Input: React.FC<InputProps> = ({
20
+ value="",
21
+ label = null,
22
+ left = null,
23
+ right = null,
24
+ height = 50,
25
+ placeholder = null,
26
+ mode = "default",
27
+ secureTextEntry = false,
28
+ onChangeText,
29
+ }) => {
30
+ const [isFocused, setFocused] = useState(false);
31
+
32
+ return (
33
+ <View style={{ gap: 5 }}>
34
+ {label !== null && (
35
+ <Text size={12} weight="400Regular" color={isFocused ? config.colors.primary : config.colors.slate[500]}>
36
+ {label}
37
+ </Text>
38
+ )}
39
+ <Inline
40
+ style={{
41
+ ...styles.input,
42
+ borderColor: isFocused ? config.colors.primary : config.colors.slate[200],
43
+ }}
44
+ >
45
+ {left !== null && left}
46
+ <TextInput
47
+ value={value}
48
+ placeholder={placeholder}
49
+ style={{
50
+ height,
51
+ flexGrow: 1,
52
+ }}
53
+ onFocus={() => setFocused(true)}
54
+ onChangeText={onChangeText}
55
+ onBlur={() => setFocused(false)}
56
+ // Using 'default' for generic mode
57
+ keyboardType={mode}
58
+ secureTextEntry={secureTextEntry}
59
+ />
60
+ {right !== null && right}
61
+ </Inline>
62
+ </View>
63
+ );
64
+ };
65
+
66
+ const styles = StyleSheet.create({
67
+ input: {
68
+ flexGrow: 1,
69
+ borderWidth: 1,
70
+ borderRadius: 12,
71
+ paddingHorizontal: 20,
72
+ },
73
+ });
74
+
75
+ export default Input;
@@ -0,0 +1,52 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { View, StyleSheet, ViewStyle } from "react-native";
3
+ import config from "../config";
4
+
5
+ interface ProgressBarProps {
6
+ value: number;
7
+ from?: number;
8
+ size?: number;
9
+ style?: ViewStyle;
10
+ percentage?: number | null;
11
+ }
12
+
13
+ const ProgressBar: React.FC<ProgressBarProps> = ({
14
+ value,
15
+ from = 100,
16
+ size = 5,
17
+ style,
18
+ percentage = null,
19
+ }) => {
20
+ const [percent, setPercent] = useState<number | null>(null);
21
+
22
+ useEffect(() => {
23
+ if (percent == null) {
24
+ setPercent((value / from) * 100);
25
+ }
26
+ }, [value, from, percent]); // Add dependencies to useEffect
27
+
28
+ return (
29
+ <View style={{ ...barStyle.area, ...style, height: size }}>
30
+ <View
31
+ style={{
32
+ ...barStyle.value,
33
+ width: `${percent !== null ? percent : 0}%`,
34
+ height: size,
35
+ }}
36
+ ></View>
37
+ </View>
38
+ );
39
+ };
40
+
41
+ const barStyle = StyleSheet.create({
42
+ area: {
43
+ backgroundColor: config.colors.slate[200],
44
+ height: 5,
45
+ flexGrow: 1,
46
+ },
47
+ value: {
48
+ backgroundColor: config.colors.primary,
49
+ },
50
+ });
51
+
52
+ export default ProgressBar;
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import { Dimensions, StyleSheet, View, ViewStyle } from "react-native";
3
+ import Inline from "./Inline";
4
+ import config from "../config";
5
+ import Text from "./Text";
6
+
7
+ interface SeparatorProps {
8
+ width?: any;
9
+ space?: number;
10
+ height?: number;
11
+ color?: string;
12
+ textProps?: React.ComponentProps<typeof Text>;
13
+ style?: ViewStyle;
14
+ children?: React.ReactNode;
15
+ }
16
+
17
+ const Separator: React.FC<SeparatorProps> = ({
18
+ children,
19
+ width = "100%",
20
+ space = 20,
21
+ height = 1,
22
+ color = config.colors.slate[200],
23
+ textProps,
24
+ style,
25
+ }) => {
26
+ let theWidth = width;
27
+ if (typeof width === "string") {
28
+ let toReturn = [];
29
+ let w = width.split("");
30
+ w.map((wi, w) => {
31
+ if (!isNaN(wi)) {
32
+ toReturn.push(wi);
33
+ }
34
+ });
35
+ toReturn = parseInt(toReturn.join(""));
36
+ theWidth = toReturn / 100 * Dimensions.get('screen').width;
37
+ console.log(theWidth);
38
+ }
39
+ return (
40
+ <Inline justifyContent="center">
41
+ <View
42
+ style={{
43
+ backgroundColor: color,
44
+ width: theWidth,
45
+ height: height,
46
+ marginVertical: space,
47
+ ...style,
48
+ }}
49
+ ></View>
50
+ {children && (
51
+ <Text
52
+ size={12}
53
+ style={{
54
+ position: "absolute",
55
+ backgroundColor: "#fff",
56
+ padding: 10,
57
+ paddingHorizontal: 20,
58
+ }}
59
+ {...textProps}
60
+ >
61
+ {children}
62
+ </Text>
63
+ )}
64
+ </Inline>
65
+ );
66
+ };
67
+
68
+ const styles = StyleSheet.create({
69
+ // Define your styles here if needed
70
+ });
71
+
72
+ export default Separator;
@@ -0,0 +1,64 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Animated, StyleSheet, View, ViewStyle } from "react-native";
3
+ import config from "../config";
4
+
5
+ interface SkeletonProps {
6
+ color?: string;
7
+ rounded?: number;
8
+ width?: any;
9
+ height?: number;
10
+ aspectRatio?: number | null;
11
+ }
12
+
13
+ const Skeleton: React.FC<SkeletonProps> = ({
14
+ color = config.colors.slate[200],
15
+ rounded = 8,
16
+ width = `${Math.floor(Math.random() * (99 - 11 + 1)) + 11}%`,
17
+ height = 20,
18
+ aspectRatio = null,
19
+ }) => {
20
+ const aspectRatioStyles: ViewStyle = { aspectRatio: aspectRatio ?? 1 };
21
+ const heightStyles: ViewStyle = { height: height };
22
+ const opacity = useRef(new Animated.Value(1)).current;
23
+ const defaultStyles: ViewStyle = {
24
+ backgroundColor: color,
25
+ width,
26
+ borderRadius: rounded,
27
+ opacity,
28
+ };
29
+
30
+ useEffect(() => {
31
+ const bounce = () => {
32
+ opacity.setValue(1);
33
+ Animated.timing(opacity, {
34
+ toValue: 0.4,
35
+ duration: 1000,
36
+ useNativeDriver: true,
37
+ }).start(() => {
38
+ Animated.timing(opacity, {
39
+ toValue: 0.8,
40
+ duration: 1000,
41
+ useNativeDriver: true,
42
+ }).start(bounce);
43
+ });
44
+ };
45
+
46
+ bounce();
47
+ }, [opacity]);
48
+
49
+ return (
50
+ <Animated.View
51
+ style={
52
+ aspectRatio === null
53
+ ? [defaultStyles, heightStyles]
54
+ : [defaultStyles, aspectRatioStyles]
55
+ }
56
+ />
57
+ );
58
+ };
59
+
60
+ const styles = StyleSheet.create({
61
+ // Add your styles here if needed
62
+ });
63
+
64
+ export default Skeleton;
@@ -0,0 +1,64 @@
1
+ import React, { useState } from "react";
2
+ import { Pressable, StyleSheet, View, ViewStyle } from "react-native";
3
+ import config from "../config";
4
+ import Inline from "./Inline";
5
+
6
+ interface SwitchProps {
7
+ active?: boolean;
8
+ size?: number;
9
+ spacer?: number;
10
+ onChange?: () => void;
11
+ }
12
+
13
+ const Switch: React.FC<SwitchProps> = ({
14
+ active = false,
15
+ size = 24,
16
+ spacer = 5,
17
+ onChange = null,
18
+ }) => {
19
+ const [isActive, setActive] = useState(active);
20
+
21
+ return (
22
+ <Pressable
23
+ onPress={() => {
24
+ setActive(!isActive);
25
+ if (onChange) {
26
+ onChange();
27
+ }
28
+ console.log('hehe');
29
+ }}
30
+ >
31
+ <Inline
32
+ style={{
33
+ backgroundColor: isActive ? config.colors.green[500] : config.colors.slate[200],
34
+ padding: spacer,
35
+ borderRadius: 999,
36
+ width: size * 2 + spacer + 5,
37
+ }}
38
+ >
39
+ {isActive && <View style={{ flexGrow: 1 }}></View>}
40
+ <View
41
+ style={{
42
+ height: size,
43
+ ...styles.circle,
44
+ }}
45
+ >
46
+ {/* Add any inner content here if needed */}
47
+ </View>
48
+ </Inline>
49
+ </Pressable>
50
+ );
51
+ };
52
+
53
+ const styles = StyleSheet.create({
54
+ area: {
55
+ backgroundColor: config.colors.slate[100],
56
+ },
57
+ circle: {
58
+ backgroundColor: '#fff',
59
+ aspectRatio: 1,
60
+ borderRadius: 9999,
61
+ },
62
+ });
63
+
64
+ export default Switch;
@@ -0,0 +1,73 @@
1
+ import React, { FC } from "react";
2
+ import { Text as RNText, TextStyle } from "react-native";
3
+ import { useFonts, Poppins_300Light, Poppins_400Regular, Poppins_500Medium, Poppins_600SemiBold, Poppins_700Bold, Poppins_900Black } from "@expo-google-fonts/poppins";
4
+ import config from "../config";
5
+
6
+ interface TextProps {
7
+ children: React.ReactNode;
8
+ weight?: '300Light' | '400Regular' | '500Medium' | '600SemiBold' | '700Bold' | '900Black';
9
+ size?: number;
10
+ color?: string;
11
+ align?: 'left' | 'right' | 'center' | 'justify';
12
+ limit?: number;
13
+ spacing?: number;
14
+ lineHeight?: number | null;
15
+ style?: TextStyle;
16
+ }
17
+
18
+ const Text: FC<TextProps> = ({
19
+ children,
20
+ weight = "400Regular",
21
+ size,
22
+ color = config.colors.slate[500],
23
+ align = "left",
24
+ limit = 0,
25
+ spacing = 0,
26
+ lineHeight = null,
27
+ style
28
+ }) => {
29
+ const fontName = "Poppins";
30
+ const [fontsLoaded] = useFonts({
31
+ Poppins_300Light,
32
+ Poppins_400Regular,
33
+ Poppins_500Medium,
34
+ Poppins_600SemiBold,
35
+ Poppins_700Bold,
36
+ Poppins_900Black
37
+ });
38
+
39
+ if (config.appLangs.default !== null) {
40
+ const ogChildren = children;
41
+ if (typeof children === "string") {
42
+ children = children.split('.').reduce((acc, key) => {
43
+ return acc && acc[key] !== undefined ? acc[key] : ogChildren;
44
+ }, config.appLangs[config.appLangs.default]);
45
+ }
46
+ }
47
+
48
+ if (!fontsLoaded) {
49
+ return <RNText>{children}</RNText>; // Return statement added to properly handle loading state
50
+ }
51
+
52
+ if (limit > 0 && typeof children === "string" && children.length > limit) {
53
+ children = children.substr(0, limit) + '...';
54
+ }
55
+
56
+ return (
57
+ <RNText
58
+ style={{
59
+ fontSize: size,
60
+ color: color,
61
+ letterSpacing: spacing,
62
+ lineHeight: lineHeight,
63
+ ...style,
64
+ fontFamily: `${fontName}_${weight}`,
65
+ textAlign: align
66
+ }}
67
+ >
68
+ {children}
69
+ </RNText>
70
+ );
71
+ };
72
+
73
+ export default Text;
@@ -0,0 +1,65 @@
1
+ import React, { useEffect } from "react";
2
+ import { StyleSheet, View, ViewStyle } from "react-native";
3
+ import Inline from "./Inline";
4
+ import Text from "./Text";
5
+ import config from "../config";
6
+
7
+ interface ToastProps {
8
+ label?: string;
9
+ right?: React.ReactNode; // Allows for any valid React node
10
+ visible?: boolean;
11
+ setVisible: (visible: boolean) => void;
12
+ timeout?: number;
13
+ containerStyle?: ViewStyle; // Style for the container View
14
+ style?: ViewStyle; // Additional style for the Toast
15
+ textProps?: React.ComponentProps<typeof Text>; // Props for the Text component
16
+ }
17
+
18
+ const Toast: React.FC<ToastProps> = ({
19
+ label = "Percakapan berhasil dihapus",
20
+ right = null,
21
+ visible = true,
22
+ setVisible,
23
+ timeout = 3000,
24
+ containerStyle,
25
+ textProps,
26
+ }) => {
27
+ useEffect(() => {
28
+ if (visible) {
29
+ const timer = setTimeout(() => {
30
+ setVisible(false);
31
+ }, timeout);
32
+
33
+ return () => clearTimeout(timer);
34
+ }
35
+ }, [visible, setVisible, timeout]);
36
+
37
+ return visible ? (
38
+ <View style={{ ...styles.container, ...containerStyle }}>
39
+ <Inline style={styles.area}>
40
+ <Text size={12} style={{ flexGrow: 1 }} color="#fff" {...textProps}>
41
+ {label}
42
+ </Text>
43
+ {right}
44
+ </Inline>
45
+ </View>
46
+ ) : null;
47
+ };
48
+
49
+ const styles = StyleSheet.create({
50
+ container: {
51
+ position: 'absolute',
52
+ bottom: 0,
53
+ left: 0,
54
+ right: 0,
55
+ padding: 20,
56
+ },
57
+ area: {
58
+ backgroundColor: config.colors.slate[800],
59
+ borderRadius: 999,
60
+ padding: 20,
61
+ paddingHorizontal: 30,
62
+ },
63
+ });
64
+
65
+ export default Toast;
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { Pressable, StyleSheet, View, ViewStyle } from "react-native";
3
+ import config from "../config";
4
+ import Text from "./Text";
5
+ import Inline from "./Inline";
6
+
7
+ interface ToggleProps {
8
+ options: string[];
9
+ value: any;
10
+ setValue: (value: string) => void;
11
+ }
12
+
13
+ const Toggle: React.FC<ToggleProps> = ({ options, value, setValue }) => {
14
+ return (
15
+ <Inline style={styles.area}>
16
+ {options.map((opt, o) => {
17
+ const isActive = value === opt;
18
+ return (
19
+ <Pressable
20
+ key={o}
21
+ style={[styles.item, isActive ? styles.item_active : null]}
22
+ onPress={() => setValue(opt)}
23
+ >
24
+ <Text
25
+ color={isActive ? config.colors.primary : config.colors.slate[500]}
26
+ weight={isActive ? '600SemiBold' : '400Regular'}
27
+ >
28
+ {opt}
29
+ </Text>
30
+ </Pressable>
31
+ );
32
+ })}
33
+ </Inline>
34
+ );
35
+ };
36
+
37
+ const styles = StyleSheet.create({
38
+ area: {
39
+ padding: 10,
40
+ backgroundColor: config.colors.slate[100],
41
+ borderRadius: 12,
42
+ },
43
+ item: {
44
+ flexGrow: 1,
45
+ flexDirection: 'row',
46
+ alignItems: 'center',
47
+ justifyContent: 'center',
48
+ paddingVertical: 10,
49
+ borderRadius: 12,
50
+ },
51
+ item_active: {
52
+ backgroundColor: '#fff',
53
+ shadowColor: config.colors.slate[300],
54
+ shadowOpacity: 0.4,
55
+ shadowRadius: 12,
56
+ },
57
+ });
58
+
59
+ export default Toggle;
package/elements/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default as Button } from './Button';
2
- export { default as Dialog } from './Dialog';
2
+ export { Dialog, DialogActions } from './Dialog';
3
3
  export { default as Dropdown } from './Dropdown';
4
4
  export { default as Inline } from './Inline';
5
5
  export { default as Input } from './Input';
@@ -8,4 +8,5 @@ export { default as Separator } from './Separator';
8
8
  export { default as Skeleton } from './Skeleton';
9
9
  export { default as Switch } from './Switch';
10
10
  export { default as Text } from './Text';
11
+ export { default as Toast } from './Toast';
11
12
  export { default as Toggle } from './Toggle';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripal-ui",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A collection of React elements and components",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,12 +9,15 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "react": "^18.0.0",
12
- "react-native": "^0.70.0"
12
+ "react-native": "^0.74.2"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@babel/cli": "^7.25.6",
16
16
  "@babel/core": "^7.25.2",
17
17
  "@babel/preset-env": "^7.25.4",
18
- "@babel/preset-react": "^7.24.7"
18
+ "@babel/preset-react": "^7.24.7",
19
+ "@types/react": "^18.3.10",
20
+ "@types/react-native": "^0.73.0",
21
+ "typescript": "^5.6.2"
19
22
  }
20
23
  }