Component

Add :
- Checkbox

Fix :
- Drawer: tinggi bisa auto
- AvataraAndOtherHeaderComponent : View pembungkus di hapus

Feature : Collaboration
Fix:
- detail-participant
- detail-project-main
(fix tampilan)

# No Issue
This commit is contained in:
2025-07-24 15:00:35 +08:00
parent 360ac5807c
commit 4f8ae2d7e0
6 changed files with 298 additions and 154 deletions

View File

@@ -1,31 +1,20 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { Feather } from "@expo/vector-icons";
import { Image } from "expo-image";
import { Feather, MaterialIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { View, ScrollView, StyleSheet, Text } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { useState } from "react";
export default function CollaborationDetailParticipant() {
const { id } = useLocalSearchParams();
// Mock data
const participantData = [
{ id: "1", name: "Inno Insani", avatar: "https://via.placeholder.com/40 " },
{
id: "2",
name: "Amalia Dwi Y",
avatar: "https://via.placeholder.com/40 ",
},
{ id: "3", name: "Bagas", avatar: "https://via.placeholder.com/40 " },
];
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
return (
<>
@@ -36,111 +25,40 @@ export default function CollaborationDetailParticipant() {
<TextCustom align="center" bold size="large">
Partisipan
</TextCustom>
</StackCustom>
{/* <View>
<FlatList
data={participantData}
renderItem={({ item }) => (
<AvatarUsernameAndOtherComponent
avatarHref={`/profile/${item.id}`}
/>
)}
keyExtractor={(item) => item.id}
/>
</View> */}
{/* <ScrollView style={{ height: "100%" }}>
<View style={{ backgroundColor: "transparent" }}>
{Array.from({ length: 5 }).map((_, index) => (
<AvatarUsernameAndOtherComponent
key={index}
avatarHref={`/profile/${index}`}
rightComponent={
<Feather name="chevron-right" size={24} color="white" />
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
onPress={() => setOpenDrawerParticipant(true)}
/>
}
/>
</View>
</ScrollView> */}
))}
</StackCustom>
</BaseBox>
</ViewWrapper>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
>
<StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom>
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
backgroundColor: "#1A202C",
},
header: {
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
},
avatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 10,
},
name: {
color: "#fff",
fontSize: 18,
fontWeight: "bold",
},
detailsContainer: {
backgroundColor: "#2B3442",
borderRadius: 8,
padding: 16,
},
title: {
color: "#fff",
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
marginBottom: 16,
},
infoRow: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 8,
},
label: {
color: "#fff",
fontSize: 14,
fontWeight: "bold",
},
value: {
color: "#fff",
fontSize: 14,
},
participantsContainer: {
marginTop: 20,
},
participantsTitle: {
color: "#fff",
fontSize: 16,
fontWeight: "bold",
marginBottom: 10,
},
participantItem: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: "#374151",
},
participantAvatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 10,
},
participantName: {
flex: 1,
color: "#fff",
fontSize: 16,
},
arrow: {
color: "#fff",
fontSize: 16,
},
});

View File

@@ -1,13 +1,93 @@
import { TextCustom, ViewWrapper } from "@/components";
import {
AvatarCustom,
AvatarUsernameAndOtherComponent,
BaseBox,
DrawerCustom,
Grid,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import CheckboxCustom from "@/components/Checkbox/CheckboxCustom";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { Feather, MaterialIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router";
import { useState } from "react";
import { View } from "react-native";
export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams();
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [agreed, setAgreed] = useState(false);
const [newsletter, setNewsletter] = useState(false);
return (
<>
<ViewWrapper>
<TextCustom bold size="large">
Detail Proyek
</TextCustom>
<Collaboration_BoxDetailSection id={id as string} />
<BaseBox style={{ height: 500 }}>
<StackCustom>
<TextCustom size="default" color="red" bold>
*{" "}
<TextCustom size="small" semiBold>
Pilih user yang akan menjadi tim proyek anda
</TextCustom>
</TextCustom>
{Array.from({ length: 5 }).map((_, index) => (
<Grid key={index}>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<CheckboxCustom
value={newsletter}
onChange={() => {
console.log("newsletter", newsletter);
setNewsletter(!newsletter);
}}
/>
</Grid.Col>
<Grid.Col span={2} style={{ alignItems: "center" }}>
<AvatarCustom />
</Grid.Col>
<Grid.Col span={6} style={{ justifyContent: "center" }}>
<TextCustom bold truncate>
Username
</TextCustom>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<MaterialIcons
name="notes"
size={ICON_SIZE_SMALL}
color="white"
/>
</Grid.Col>
</Grid>
))}
</StackCustom>
</BaseBox>
</ViewWrapper>
<DrawerCustom
isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)}
height={"auto"}
>
<StackCustom>
<TextCustom bold>Deskripsi Diri</TextCustom>
<TextCustom>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Commodi,
itaque adipisci. Voluptas, sed quod! Ad facere labore voluptates,
neque quidem aut reprehenderit ducimus mollitia quisquam temporibus!
Temporibus iusto soluta necessitatibus.
</TextCustom>
</StackCustom>
</DrawerCustom>
</>
);
}

View File

@@ -0,0 +1,109 @@
import React, { useState } from "react";
import { View, Text, TouchableOpacity, Animated, Easing } from "react-native";
import { MaterialIcons } from "@expo/vector-icons"; // Bisa diganti dengan ikon lain
import { checkboxStyles } from "./checkbox-styles";
// Tipe props
interface CheckboxProps {
label?: string;
description?: string;
error?: string;
value?: boolean;
onChange?: (checked: boolean) => void;
disabled?: boolean;
size?: number; // ukuran checkbox (default: 20)
color?: string; // warna utama (default: '#3b82f6' - biru tailwind)
style?: object;
component?: React.ReactNode;
}
export const CheckboxCustom: React.FC<CheckboxProps> = ({
label,
description,
error,
value: controlledValue,
onChange,
disabled = false,
size = 20,
color = "#3b82f6",
style,
component,
}) => {
const [uncontrolledChecked, setUncontrolledChecked] = useState(false);
const isChecked = controlledValue ?? uncontrolledChecked;
const scaleValue = new Animated.Value(isChecked ? 1 : 0);
const toggle = () => {
if (disabled) return;
const newValue = !isChecked;
if (onChange) onChange(newValue);
if (controlledValue === undefined) {
setUncontrolledChecked(newValue);
}
// Animasi scale
Animated.spring(scaleValue, {
toValue: newValue ? 1 : 0,
friction: 7,
tension: 40,
useNativeDriver: true,
}).start();
};
const styles = checkboxStyles({
size,
color,
disabled,
error: !!error,
});
return (
<TouchableOpacity
activeOpacity={disabled ? 1 : 0.7}
onPress={toggle}
style={[styles.container, style]}
disabled={disabled}
>
<View style={styles.innerContainer}>
<View style={[styles.box, isChecked && !disabled && styles.checked]}>
{isChecked && (
<Animated.View
style={{
transform: [
{
scale: scaleValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
},
],
}}
>
<MaterialIcons name="check" size={size * 0.6} color="#fff" />
</Animated.View>
)}
</View>
{component}
{(label || description) && (
<View style={styles.labelWrapper}>
{label ? (
<Text style={styles.label} numberOfLines={1}>
{label}
</Text>
) : null}
{description ? (
<Text style={styles.description} numberOfLines={2}>
{description}
</Text>
) : null}
{error ? <Text style={styles.errorText}>{error}</Text> : null}
</View>
)}
</View>
</TouchableOpacity>
);
};
export default CheckboxCustom

View File

@@ -0,0 +1,62 @@
import { MainColor } from "@/constants/color-palet";
import { StyleSheet } from "react-native";
export const checkboxStyles = (props: {
size: number;
color: string;
disabled: boolean;
error: boolean;
}) =>
StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "flex-start",
// marginBottom: 12,
},
innerContainer: {
flexDirection: "row",
alignItems: "center",
},
box: {
width: props.size,
height: props.size,
borderRadius: 6,
borderWidth: 2,
borderColor: props.error
? "#fff"
: props.disabled
? "#ced4da"
: props.color,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
},
checked: {
backgroundColor: props.color,
borderColor: props.color,
},
checkIcon: {
color: MainColor.white,
fontWeight: "bold",
fontSize: props.size * 0.6,
},
labelWrapper: {
flex: 1,
},
label: {
fontSize: props.size * 0.6,
color: props.disabled ? MainColor.disabled : MainColor.white,
fontWeight: "500",
},
description: {
fontSize: props.size * 0.5,
color: props.disabled ? MainColor.disabled : MainColor.white,
marginTop: 2,
},
errorText: {
color: MainColor.red,
fontSize: props.size * 0.5,
marginTop: 2,
},
});

View File

@@ -9,20 +9,20 @@ import {
import { AccentColor, MainColor } from "@/constants/color-palet";
import { DRAWER_HEIGHT } from "@/constants/constans-value";
import { SafeAreaView } from "react-native-safe-area-context";
interface DrawerCustomProps {
children?: React.ReactNode;
height?: number;
height?: number | "auto";
isVisible: boolean;
drawerAnim?: Animated.Value;
closeDrawer: () => void;
// openLogoutAlert: () => void;
}
/**
*
* @param drawerAnim
*
* @param drawerAnim
* @example const drawerAnim = useRef(new Animated.Value(DRAWER_HEIGHT)).current; // mulai di luar bawah layar
*/
export default function DrawerCustom({
@@ -34,7 +34,9 @@ export default function DrawerCustom({
}: // openLogoutAlert,
DrawerCustomProps) {
const drawerAnima = useRef(
new Animated.Value(height || DRAWER_HEIGHT)
new Animated.Value(
height === "auto" ? DRAWER_HEIGHT : height || DRAWER_HEIGHT
)
).current;
// Efek untuk handle open/close drawer
useEffect(() => {
@@ -46,7 +48,7 @@ DrawerCustomProps) {
}).start();
} else {
Animated.timing(drawerAnima, {
toValue: height || DRAWER_HEIGHT,
toValue: height === "auto" ? DRAWER_HEIGHT : height || DRAWER_HEIGHT,
duration: 300,
useNativeDriver: true,
}).start();
@@ -99,7 +101,7 @@ DrawerCustomProps) {
style={[
styles.drawer,
{
height: height || DRAWER_HEIGHT,
height: height === "auto" ? "auto" : height || DRAWER_HEIGHT,
transform: [{ translateY: drawerAnima }],
},
]}
@@ -110,34 +112,7 @@ DrawerCustomProps) {
/>
{children}
{/* <TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 1 diklik");
closeDrawer();
}}
>
<Text>Menu Item 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => {
alert("Pilihan 2 diklik");
closeDrawer();
}}
>
<Text>Menu Item 2</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.menuItem}
onPress={() => alert("Logout via Alert bawaan")}
>
<Text style={{ color: "red" }}>Keluar</Text>
</TouchableOpacity> */}
{height === "auto" && <SafeAreaView edges={["bottom"]} />}
</Animated.View>
</>
);

View File

@@ -19,7 +19,6 @@ const AvatarUsernameAndOtherComponent = ({
}) => {
return (
<>
<View>
<Grid containerStyle={{ zIndex: 10 }}>
<Grid.Col span={2}>
<AvatarCustom source={avatar} href={avatarHref as any} />
@@ -42,6 +41,7 @@ const AvatarUsernameAndOtherComponent = ({
)}
</Grid>
{withBottomLine && <Divider marginTop={0} />}
<View>
</View>
</>
);