dekripsi:
- fix styles Select
This commit is contained in:
2025-07-09 11:40:22 +08:00
parent 6ac122c631
commit 5c4dadbe7c
5 changed files with 103 additions and 120 deletions

View File

@@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { import {
ButtonCustom, ButtonCustom,
Grid, Grid,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom, TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper, ViewWrapper,
} from "@/components"; } from "@/components";
import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter"; import BoxButtonOnFooter from "@/components/Box/BoxButtonOnFooter";
import InformationBox from "@/components/Box/InformationBox"; import InformationBox from "@/components/Box/InformationBox";
@@ -71,9 +71,12 @@ export default function PortofolioCreate() {
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))}
value="" value={data.bidang_usaha}
onChange={(value) => console.log(value)} onChange={(value) => {
setData({ ...(data as any), bidang_usaha: value });
}}
/> />
<Grid> <Grid>
<Grid.Col span={10}> <Grid.Col span={10}>
<SelectCustom <SelectCustom
@@ -83,8 +86,10 @@ export default function PortofolioCreate() {
label: item.name, label: item.name,
value: item.id, value: item.id,
}))} }))}
value="" value={data.sub_bidang_usaha}
onChange={(value) => console.log(value)} onChange={(value) => {
setData({ ...(data as any), sub_bidang_usaha: value });
}}
/> />
</Grid.Col> </Grid.Col>
<Grid.Col <Grid.Col
@@ -96,6 +101,7 @@ export default function PortofolioCreate() {
</TouchableOpacity> </TouchableOpacity>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}> <ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan Tambah Pilihan
</ButtonCenteredOnly> </ButtonCenteredOnly>

View File

@@ -1,15 +1,13 @@
// components/Select.tsx // components/Select.tsx
import { MainColor } from "@/constants/color-palet"; import { GStyles } from "@/styles/global-styles";
import { TEXT_SIZE_MEDIUM } from "@/constants/constans-value";
import React, { useState } from "react"; import React, { useState } from "react";
import { import {
FlatList, FlatList,
Modal, Modal,
Pressable, Pressable,
StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
View, View
} from "react-native"; } from "react-native";
type SelectItem = { type SelectItem = {
@@ -24,6 +22,7 @@ type SelectProps = {
value?: string | number | null; value?: string | number | null;
required?: boolean; // <-- new prop required?: boolean; // <-- new prop
onChange: (value: string | number) => void; onChange: (value: string | number) => void;
borderRadius?: number;
}; };
const SelectCustom: React.FC<SelectProps> = ({ const SelectCustom: React.FC<SelectProps> = ({
@@ -33,6 +32,7 @@ const SelectCustom: React.FC<SelectProps> = ({
value, value,
required = false, // <-- default false required = false, // <-- default false
onChange, onChange,
borderRadius = 8,
}) => { }) => {
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
@@ -41,35 +41,41 @@ const SelectCustom: React.FC<SelectProps> = ({
const hasError = required && value === null; // <-- check if empty and required const hasError = required && value === null; // <-- check if empty and required
return ( return (
<View style={styles.container}> <View style={GStyles.inputContainerArea}>
{label && ( {label && (
<Text style={styles.label}> <Text style={GStyles.inputLabel}>
{label} {label}
{required && <Text style={styles.requiredIndicator}> *</Text>} {required && <Text style={GStyles.inputRequired}> *</Text>}
</Text> </Text>
)} )}
<Pressable <Pressable
style={[styles.input, hasError ? styles.inputError : null]} // <-- add error style style={[
{ borderRadius },
GStyles.inputContainerInput,
hasError ? GStyles.inputErrorBorder : null,
]} // <-- add error style
onPress={() => setModalVisible(true)} onPress={() => setModalVisible(true)}
> >
<Text style={selectedItem ? styles.text : styles.placeholder}> <Text
style={selectedItem ? GStyles.inputText : GStyles.inputPlaceholder}
>
{selectedItem?.label || placeholder} {selectedItem?.label || placeholder}
</Text> </Text>
</Pressable> </Pressable>
<Modal visible={modalVisible} transparent animationType="fade"> <Modal visible={modalVisible} transparent animationType="fade">
<TouchableOpacity <TouchableOpacity
style={styles.modalOverlay} style={GStyles.selectModalOverlay}
activeOpacity={1} activeOpacity={1}
onPressOut={() => setModalVisible(false)} onPressOut={() => setModalVisible(false)}
> >
<View style={styles.modalContent}> <View style={GStyles.selectModalContent}>
<FlatList <FlatList
data={data} data={data}
keyExtractor={(item) => String(item.value)} keyExtractor={(item) => String(item.value)}
renderItem={({ item }) => ( renderItem={({ item }) => (
<TouchableOpacity <TouchableOpacity
style={styles.option} style={GStyles.selectOption}
onPress={() => { onPress={() => {
onChange(item.value); onChange(item.value);
setModalVisible(false); setModalVisible(false);
@@ -85,68 +91,10 @@ const SelectCustom: React.FC<SelectProps> = ({
{/* Optional Error Message */} {/* Optional Error Message */}
{hasError && ( {hasError && (
<Text style={styles.errorMessage}>Harap pilih salah satu</Text> <Text style={GStyles.inputErrorMessage}>Harap pilih salah satu</Text>
)} )}
</View> </View>
); );
}; };
export default SelectCustom; export default SelectCustom;
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: TEXT_SIZE_MEDIUM,
marginBottom: 4,
color: MainColor.white_gray,
fontWeight: "500",
},
requiredIndicator: {
color: "red",
fontWeight: "bold",
},
input: {
borderWidth: 1,
borderColor: MainColor.white_gray,
padding: 12,
borderRadius: 8,
minHeight: 48,
justifyContent: "center",
backgroundColor: MainColor.white,
},
inputError: {
borderColor: "red",
},
text: {
fontSize: TEXT_SIZE_MEDIUM,
},
placeholder: {
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.placeholder,
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.3)",
justifyContent: "center",
alignItems: "center",
},
modalContent: {
width: "80%",
maxHeight: 300,
backgroundColor: "white",
borderRadius: 8,
overflow: "hidden",
},
option: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: MainColor.white_gray,
},
errorMessage: {
marginTop: 4,
fontSize: 12,
color: "red",
},
});

View File

@@ -1,3 +1,4 @@
import { GStyles } from "@/styles/global-styles";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import {
TextInput as RNTextInput, TextInput as RNTextInput,
@@ -6,7 +7,6 @@ import {
View, View,
ViewStyle, ViewStyle,
} from "react-native"; } from "react-native";
import { GStyles } from "@/styles/global-styles";
type IconType = React.ReactNode | string; type IconType = React.ReactNode | string;
@@ -70,31 +70,31 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
const renderIcon = (icon: IconType) => { const renderIcon = (icon: IconType) => {
if (!icon) return null; if (!icon) return null;
return typeof icon === "string" ? ( return typeof icon === "string" ? (
<Text style={GStyles.iconTextInput}>{icon}</Text> <Text style={GStyles.inputIconText}>{icon}</Text>
) : ( ) : (
icon icon
); );
}; };
return ( return (
<View style={GStyles.containerAreaInput}> <View style={GStyles.inputContainerArea}>
{label && ( {label && (
<Text style={GStyles.labelInput}> <Text style={GStyles.inputLabel}>
{label} {label}
{required && <Text style={GStyles.requiredInput}> *</Text>} {required && <Text style={GStyles.inputRequired}> *</Text>}
</Text> </Text>
)} )}
<View <View
style={[ style={[
GStyles.inputContainerInput, GStyles.inputContainerInput,
disabled && GStyles.disabledInput, disabled && GStyles.inputDisabled,
hasError ? GStyles.errorBorderInput : {}, hasError ? GStyles.inputErrorBorder : {},
{ borderRadius }, { borderRadius },
style, style,
]} ]}
> >
{iconLeft && ( {iconLeft && (
<View style={GStyles.iconInput}>{renderIcon(iconLeft)}</View> <View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)} )}
<RNTextInput <RNTextInput
@@ -102,7 +102,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
multiline multiline
numberOfLines={numberOfLines} numberOfLines={numberOfLines}
style={[ style={[
GStyles.inputInput, GStyles.inputText,
GStyles.textAreaInput, GStyles.textAreaInput,
{ color: fontColor }, { color: fontColor },
]} ]}
@@ -113,7 +113,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
/> />
{iconRight && ( {iconRight && (
<View style={GStyles.iconInput}>{renderIcon(iconRight)}</View> <View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)} )}
</View> </View>
@@ -127,7 +127,7 @@ const TextAreaCustom: React.FC<TextAreaCustomProps> = ({
}} }}
> >
{hasError ? ( {hasError ? (
<Text style={GStyles.errorMessageInput}>{error}</Text> <Text style={GStyles.inputErrorMessage}>{error}</Text>
) : null} ) : null}
{showCount && maxLength ? ( {showCount && maxLength ? (

View File

@@ -50,7 +50,7 @@ export const TextInputCustom = ({
const renderIcon = (icon: IconType) => { const renderIcon = (icon: IconType) => {
if (!icon) return null; if (!icon) return null;
return typeof icon === "string" ? ( return typeof icon === "string" ? (
<Text style={GStyles.iconTextInput}>{icon}</Text> <Text style={GStyles.inputIconText}>{icon}</Text>
) : ( ) : (
icon icon
); );
@@ -74,27 +74,27 @@ export const TextInputCustom = ({
}; };
return ( return (
<View style={GStyles.containerAreaInput}> <View style={GStyles.inputContainerArea}>
{label && ( {label && (
<Text style={GStyles.labelInput}> <Text style={GStyles.inputLabel}>
{label} {label}
{required && <Text style={GStyles.requiredInput}> *</Text>} {required && <Text style={GStyles.inputRequired}> *</Text>}
</Text> </Text>
)} )}
<View <View
style={[ style={[
GStyles.inputContainerInput, GStyles.inputContainerInput,
disabled && GStyles.disabledInput, disabled && GStyles.inputDisabled,
{ borderRadius }, { borderRadius },
externalError || internalError ? GStyles.errorBorderInput : null, externalError || internalError ? GStyles.inputErrorBorder : null,
style, style,
]} ]}
> >
{iconLeft && ( {iconLeft && (
<View style={GStyles.iconInput}>{renderIcon(iconLeft)}</View> <View style={GStyles.inputIcon}>{renderIcon(iconLeft)}</View>
)} )}
<RNTextInput <RNTextInput
style={[GStyles.inputInput, { color: fontColor }]} style={[GStyles.inputText, { color: fontColor }]}
editable={!disabled} editable={!disabled}
secureTextEntry={secureTextEntry && !isPasswordVisible} secureTextEntry={secureTextEntry && !isPasswordVisible}
keyboardType={keyboardType} keyboardType={keyboardType}
@@ -105,7 +105,7 @@ export const TextInputCustom = ({
{secureTextEntry && ( {secureTextEntry && (
<TouchableOpacity <TouchableOpacity
onPress={() => setIsPasswordVisible((prev) => !prev)} onPress={() => setIsPasswordVisible((prev) => !prev)}
style={GStyles.iconInput} style={GStyles.inputIcon}
> >
<Ionicons <Ionicons
name={isPasswordVisible ? "eye-off" : "eye"} name={isPasswordVisible ? "eye-off" : "eye"}
@@ -115,12 +115,12 @@ export const TextInputCustom = ({
</TouchableOpacity> </TouchableOpacity>
)} )}
{iconRight && ( {iconRight && (
<View style={GStyles.iconInput}>{renderIcon(iconRight)}</View> <View style={GStyles.inputIcon}>{renderIcon(iconRight)}</View>
)} )}
</View> </View>
{/* Prioritaskan error eksternal */} {/* Prioritaskan error eksternal */}
{externalError || internalError ? ( {externalError || internalError ? (
<Text style={GStyles.errorMessageInput}> <Text style={GStyles.inputErrorMessage}>
{externalError || internalError} {externalError || internalError}
</Text> </Text>
) : null} ) : null}

View File

@@ -155,27 +155,27 @@ export const GStyles = StyleSheet.create({
}, },
// =============== BUTTON =============== // // =============== BUTTON =============== //
// =============== TEXT INPUT =============== // // =============== TEXT INPUT , TEXT AREA , SELECT =============== //
// Container utama input (View luar) // Container utama input (View luar)
containerAreaInput: { inputContainerArea: {
marginBottom: 16, marginBottom: 16,
}, },
// Label di atas input // Label di atas input
labelInput: { inputLabel: {
fontSize: TEXT_SIZE_MEDIUM, fontSize: TEXT_SIZE_MEDIUM,
marginBottom: 6, marginBottom: 4,
fontWeight: "500", fontWeight: "500",
color: MainColor.white_gray, color: MainColor.white_gray,
}, },
// Tanda bintang merah untuk required // Tanda bintang merah untuk required
requiredInput: { inputRequired: {
color: "red", color: "red",
}, },
// Pesan error di bawah input // Pesan error di bawah input
errorMessageInput: { inputErrorMessage: {
marginTop: 4, marginTop: 4,
fontSize: TEXT_SIZE_SMALL, fontSize: TEXT_SIZE_SMALL,
color: MainColor.red, color: MainColor.red,
@@ -189,49 +189,78 @@ export const GStyles = StyleSheet.create({
// Wrapper input (View pembungkus TextInput) // Wrapper input (View pembungkus TextInput)
inputContainerInput: { inputContainerInput: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1, borderWidth: 1,
borderColor: MainColor.white_gray, borderColor: MainColor.white_gray,
backgroundColor: MainColor.white, backgroundColor: MainColor.white,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10, paddingHorizontal: 10,
height: 50, height: 50,
}, },
// Style saat disabled // Style saat disabled
disabledInput: { inputDisabled: {
backgroundColor: "#f9f9f9", backgroundColor: "#f9f9f9",
borderColor: "#e0e0e0", borderColor: "#e0e0e0",
}, },
// Input utama (TextInput) // Input utama (TextInput)
inputInput: { inputText: {
flex: 1, flex: 1,
fontSize: TEXT_SIZE_MEDIUM, fontSize: TEXT_SIZE_MEDIUM,
paddingVertical: 0, paddingVertical: 0,
}, },
// Ikon di kiri/kanan // Ikon di kiri/kanan
iconInput: { inputIcon: {
marginHorizontal: 4, marginHorizontal: 4,
justifyContent: "center", justifyContent: "center",
}, },
// Teks ikon jika berupa string // Teks ikon jika berupa string
iconTextInput: { inputIconText: {
fontSize: TEXT_SIZE_LARGE, fontSize: TEXT_SIZE_LARGE,
color: "#000", color: "#000",
}, },
// Border merah jika ada error // Border merah jika ada error
errorBorderInput: { inputErrorBorder: {
borderColor: "red", borderColor: "red",
borderWidth: 1,
}, },
// Untuk TextArea tambahan // Placeholder input
inputPlaceholder: {
fontSize: TEXT_SIZE_MEDIUM,
color: MainColor.placeholder,
},
// TextArea untuk tambahan
textAreaInput: { textAreaInput: {
textAlignVertical: "top", textAlignVertical: "top",
padding: 5, padding: 5,
height: undefined, // biar multiline bebas tinggi height: undefined, // biar multiline bebas tinggi
}, },
// Select
selectModalOverlay: {
flex: 1,
backgroundColor: "rgba(0,0,0,0.3)",
justifyContent: "center",
alignItems: "center",
},
selectModalContent: {
width: "80%",
maxHeight: 300,
backgroundColor: "white",
borderRadius: 8,
overflow: "hidden",
},
selectOption: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: MainColor.white_gray,
},
// =============== TEXT INPUT , TEXT AREA , SELECT =============== //
}); });