feature & fix
deskripsi: - fix Text input - feature Box footer & button center
This commit is contained in:
@@ -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",
|
||||
|
||||
16
components/Box/BoxButtonOnFooter.tsx
Normal file
16
components/Box/BoxButtonOnFooter.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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" }}>
|
||||
|
||||
29
components/Button/ButtonCenteredOnly.tsx
Normal file
29
components/Button/ButtonCenteredOnly.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -109,5 +109,6 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "flex-start",
|
||||
marginInline: 0.1
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
144
components/TextArea/TextAreaCustom.tsx
Normal file
144
components/TextArea/TextAreaCustom.tsx
Normal 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;
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user