feature & fix

deskripsi:
- fix Text input
- feature Box footer & button center
This commit is contained in:
2025-07-09 10:19:02 +08:00
parent 0698e14d36
commit 16559b94fe
27 changed files with 1284 additions and 71 deletions

View File

@@ -95,12 +95,12 @@ const styles = StyleSheet.create({
fontSize: TEXT_SIZE_LARGE,
fontWeight: "bold",
marginBottom: 20,
color: MainColor.white,
color: MainColor.white_gray,
},
alertMessage: {
textAlign: "center",
marginBottom: 20,
color: MainColor.white,
color: MainColor.white_gray,
},
alertButtons: {
flexDirection: "row",

View File

@@ -0,0 +1,16 @@
import { GStyles } from "@/styles/global-styles";
import { StyleProp, View, ViewStyle } from "react-native";
export default function BoxButtonOnFooter({
children,
style,
}: {
children: React.ReactNode;
style?: StyleProp<ViewStyle>;
}) {
return (
<View style={GStyles.bottomBar}>
<View style={[GStyles.bottomBarContainer, style]}>{children}</View>
</View>
);
}

View File

@@ -16,7 +16,7 @@ export default function InformationBox({ text }: { text: string }) {
<Ionicons
name="information-circle-outline"
size={24}
color={MainColor.white}
color={MainColor.white_gray}
/>
</Grid.Col>
<Grid.Col span={10} style={{ justifyContent: "center" }}>

View File

@@ -0,0 +1,29 @@
import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_BUTTON } from "@/constants/constans-value";
import { GStyles } from "@/styles/global-styles";
import { Feather } from "@expo/vector-icons";
import React from "react";
import ButtonCustom from "./ButtonCustom";
interface ButtonCenteredOnlyProps {
children?: React.ReactNode;
icon?: "plus" | "upload";
onPress: () => void;
}
export default function ButtonCenteredOnly({
onPress,
children,
icon = "plus"
}: ButtonCenteredOnlyProps) {
return (
<ButtonCustom
onPress={onPress}
iconLeft={
<Feather name={icon} size={ICON_SIZE_BUTTON} color={MainColor.black} />
}
style={[GStyles.buttonCentered50Percent]}
>
{children}
</ButtonCustom>
);
}

View File

@@ -1,6 +1,7 @@
// components/Button/buttonStyles.js
import { MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import { StyleSheet } from "react-native";
export default function buttonStyles({
@@ -21,7 +22,7 @@ export default function buttonStyles({
},
buttonText: {
color: textColor,
fontSize: 16,
fontSize: TEXT_SIZE_MEDIUM,
fontWeight: "600",
},
disabled: {

View File

@@ -1,10 +1,10 @@
import React, { useRef } from "react";
import {
Animated,
PanResponder,
StyleSheet,
View,
InteractionManager,
Animated,
InteractionManager,
PanResponder,
StyleSheet,
View,
} from "react-native";
import { AccentColor, MainColor } from "@/constants/color-palet";
@@ -86,7 +86,7 @@ DrawerCustomProps) {
{...panResponder.panHandlers}
>
<View
style={[styles.headerBar, { backgroundColor: MainColor.white }]}
style={[styles.headerBar, { backgroundColor: MainColor.white_gray }]}
/>
{children}
@@ -152,7 +152,7 @@ const styles = StyleSheet.create({
headerBar: {
width: 40,
height: 5,
backgroundColor: MainColor.white,
backgroundColor: MainColor.white_gray,
borderRadius: 5,
alignSelf: "center",
marginVertical: 10,

View File

@@ -1,7 +1,7 @@
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_MEDIUM, TEXT_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons";
import { View, TouchableOpacity, Text, StyleSheet } from "react-native";
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
const MenuDrawerDynamicGrid = ({ data, columns = 3, onPressItem }: any) => {
const numColumns = columns;
@@ -18,7 +18,7 @@ const MenuDrawerDynamicGrid = ({ data, columns = 3, onPressItem }: any) => {
<Ionicons
name={item.icon}
size={ICON_SIZE_MEDIUM}
color={item.color || MainColor.white}
color={item.color || MainColor.white_gray}
/>
</View>
<Text style={styles.label}>{item.label}</Text>
@@ -52,6 +52,6 @@ const styles = StyleSheet.create({
marginTop: 10,
fontSize: TEXT_SIZE_SMALL,
textAlign: "center",
color: MainColor.white,
color: MainColor.white_gray,
},
});

View File

@@ -109,5 +109,6 @@ const styles = StyleSheet.create({
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "flex-start",
marginInline: 0.1
},
});

View File

@@ -47,7 +47,7 @@ const styles = StyleSheet.create({
overlappingAvatar: {
borderWidth: 2,
borderColor: "#fff",
backgroundColor: MainColor.white,
backgroundColor: MainColor.white_gray,
// shadowColor: "#000",
// shadowOffset: { width: 0, height: 2 },
// shadowOpacity: 0.2,

View File

@@ -3,13 +3,13 @@ import { MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import React, { useState } from "react";
import {
View,
Text,
Pressable,
Modal,
FlatList,
Modal,
Pressable,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
type SelectItem = {
@@ -100,7 +100,7 @@ const styles = StyleSheet.create({
label: {
fontSize: TEXT_SIZE_MEDIUM,
marginBottom: 4,
color: MainColor.white,
color: MainColor.white_gray,
fontWeight: "500",
},
requiredIndicator: {
@@ -109,7 +109,7 @@ const styles = StyleSheet.create({
},
input: {
borderWidth: 1,
borderColor: "#ccc",
borderColor: MainColor.white_gray,
padding: 12,
borderRadius: 8,
minHeight: 48,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// components/Stack.tsx
import React from "react";
@@ -18,7 +19,7 @@ const StackCustom: React.FC<StackProps> = ({
children,
align = "stretch",
justify = "flex-start",
gap = "md",
gap = "xs",
direction = "column",
style,
}) => {
@@ -57,10 +58,10 @@ const convertToSpacing = (value: GapSizeType): number => {
return sizes[value] || 16; // default md
};
const styles = StyleSheet.create({
stack: {
flex: 1,
},
});
// const styles = StyleSheet.create({
// stack: {
// flex: 1,
// },
// });
export default StackCustom;

View File

@@ -0,0 +1,144 @@
import React, { useEffect, useState } from "react";
import {
TextInput as RNTextInput,
StyleProp,
Text,
View,
ViewStyle,
} from "react-native";
import { textInputStyles } from "../TextInput/textInputStyles";
type IconType = React.ReactNode | string;
type BaseProps = {
iconLeft?: IconType;
iconRight?: IconType;
label?: string;
required?: boolean;
error?: string;
fontColor?: string;
disabled?: boolean;
borderRadius?: number;
autosize?: boolean;
minRows?: number;
maxRows?: number;
showCount?: boolean;
maxLength?: number;
style?: StyleProp<ViewStyle>;
};
type NativeTextInputProps = Omit<
React.ComponentProps<typeof RNTextInput>,
"style"
>;
export type TextAreaCustomProps = BaseProps & NativeTextInputProps;
const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
iconLeft,
iconRight,
label,
required = false,
error = "",
fontColor = "#000",
disabled = false,
borderRadius = 8,
autosize = false,
minRows = 3,
maxRows = 6,
showCount = false,
maxLength,
value,
onChangeText,
style,
...rest
}) => {
const [numberOfLines, setNumberOfLines] = useState(minRows);
// Autosizing logic
useEffect(() => {
if (!autosize || !value) return;
const text = value as string;
const lines = text.split("\n").length;
const newLines = Math.max(minRows, Math.min(maxRows, lines));
setNumberOfLines(newLines);
}, [value, autosize, minRows, maxRows]);
const hasError = Boolean(error);
const renderIcon = (icon: IconType) => {
if (!icon) return null;
return typeof icon === "string" ? (
<Text style={textInputStyles.iconText}>{icon}</Text>
) : (
icon
);
};
return (
<View style={textInputStyles.container}>
{label && (
<Text style={textInputStyles.label}>
{label}
{required && <Text style={textInputStyles.required}> *</Text>}
</Text>
)}
<View
style={[
textInputStyles.inputContainer,
disabled && textInputStyles.disabled,
hasError ? textInputStyles.errorBorder : {},
{ borderRadius },
style,
]}
>
{iconLeft && (
<View style={textInputStyles.icon}>{renderIcon(iconLeft)}</View>
)}
<RNTextInput
maxLength={maxLength}
multiline
numberOfLines={numberOfLines}
style={[
textInputStyles.input,
textInputStyles.textArea,
{ color: fontColor },
]}
editable={!disabled}
value={value as string}
onChangeText={onChangeText}
{...rest}
/>
{iconRight && (
<View style={textInputStyles.icon}>{renderIcon(iconRight)}</View>
)}
</View>
{/* Error Message atau Counter */}
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 4,
minHeight: 16,
}}
>
{hasError ? (
<Text style={textInputStyles.errorMessage}>{error}</Text>
) : null}
{showCount && maxLength ? (
<Text style={textInputStyles.inputLength}>
{(value as string)?.length || 0}/{maxLength}
</Text>
) : null}
</View>
</View>
);
};
export default TextAreaCustom;

View File

@@ -23,6 +23,7 @@ type Props = {
disabled?: boolean;
borderRadius?: number;
style?: StyleProp<ViewStyle>;
maxLength?: number;
} & Omit<React.ComponentProps<typeof RNTextInput>, "style">;
export const TextInputCustom = ({
@@ -38,6 +39,7 @@ export const TextInputCustom = ({
style,
keyboardType,
onChangeText,
maxLength,
...rest
}: Props) => {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
@@ -96,6 +98,7 @@ export const TextInputCustom = ({
secureTextEntry={secureTextEntry && !isPasswordVisible}
keyboardType={keyboardType}
onChangeText={handleTextChange}
maxLength={maxLength}
{...rest}
/>
{secureTextEntry && (

View File

@@ -1,5 +1,6 @@
// components/text-input.styles.ts
import { AccentColor, MainColor } from "@/constants/color-palet";
import { MainColor } from "@/constants/color-palet";
import { TEXT_SIZE_LARGE, TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import { StyleSheet } from "react-native";
export const textInputStyles = StyleSheet.create({
@@ -13,7 +14,7 @@ export const textInputStyles = StyleSheet.create({
fontSize: 14,
marginBottom: 6,
fontWeight: "500",
color: MainColor.white,
color: MainColor.white_gray,
},
// Tanda bintang merah untuk required
@@ -28,12 +29,18 @@ export const textInputStyles = StyleSheet.create({
color: MainColor.red,
},
// Input Length
inputLength: {
fontSize: 12,
color: MainColor.white_gray,
},
// Wrapper input (View pembungkus TextInput)
inputContainer: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: AccentColor.white,
borderColor: MainColor.white_gray,
backgroundColor: MainColor.white,
paddingHorizontal: 12,
height: 50,
@@ -48,7 +55,7 @@ export const textInputStyles = StyleSheet.create({
// Input utama (TextInput)
input: {
flex: 1,
fontSize: 16,
fontSize: TEXT_SIZE_MEDIUM,
paddingVertical: 0,
},
@@ -60,7 +67,7 @@ export const textInputStyles = StyleSheet.create({
// Teks ikon jika berupa string
iconText: {
fontSize: 16,
fontSize: TEXT_SIZE_LARGE,
color: "#000",
},
@@ -68,4 +75,11 @@ export const textInputStyles = StyleSheet.create({
errorBorder: {
borderColor: "red",
},
// Untuk TextArea tambahan
textArea: {
textAlignVertical: "top",
padding: 12,
height: undefined, // biar multiline bebas tinggi
},
});

View File

@@ -2,7 +2,9 @@
import AlertCustom from "./Alert/AlertCustom";
// Button
import LeftButtonCustom from "./Button/BackButton";
import ButtonCenteredOnly from "./Button/ButtonCenteredOnly";
import ButtonCustom from "./Button/ButtonCustom";
import ButtonUpload from "./Button/ButtonUpload";
// Drawer
import DrawerCustom from "./Drawer/DrawerCustom";
import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird";
@@ -13,6 +15,8 @@ import ViewWrapper from "./_ShareComponent/ViewWrapper";
import TextCustom from "./Text/TextCustom";
// TextInput
import { TextInputCustom } from "./TextInput/TextInputCustom";
// TextArea
import TextAreaCustom from "./TextArea/TextAreaCustom";
// Grid
import Grid from "./Grid/GridCustom";
// Box
@@ -31,7 +35,7 @@ export {
// Button
LeftButtonCustom as BackButton,
// Box
BaseBox, ButtonCustom,
BaseBox, ButtonCenteredOnly, ButtonCustom, ButtonUpload,
// Drawer
DrawerCustom,
// Grid
@@ -42,9 +46,13 @@ export {
Spacing,
// Stack
StackCustom,
// TextArea
TextAreaCustom,
// Text
TextCustom,
// TextInput
TextInputCustom, ViewWrapper
TextInputCustom,
// ViewWrapper
ViewWrapper
};