Component

Add :
- CheckboxGroup
- components/_Icon/

Fix:
- CheckboxCustom: penambahan fitur untuk checkbox group

Feature:
Collaboration
Add :
- ProjectMainSelectedSection

Fix :
- detail-participant
- detail-project-main

# No Issue
This commit is contained in:
2025-07-24 16:47:16 +08:00
parent 4f8ae2d7e0
commit e02ae8e35d
11 changed files with 346 additions and 93 deletions

View File

@@ -140,13 +140,14 @@ export default function UserLayout() {
/> />
<Stack.Screen <Stack.Screen
name="collaboration/[id]/detail-project-main" name="collaboration/[id]/edit"
options={{ options={{
title: "Proyek Saya", title: "Edit Proyek",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
}} }}
/> />
{/* ========== End Collaboration Section ========= */} {/* ========== End Collaboration Section ========= */}
{/* ========== Forum Section ========= */} {/* ========== Forum Section ========= */}

View File

@@ -8,7 +8,7 @@ import {
} from "@/components"; } from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { Feather, MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { useLocalSearchParams } from "expo-router"; import { useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useState } from "react";
@@ -21,7 +21,6 @@ export default function CollaborationDetailParticipant() {
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> <Collaboration_BoxDetailSection id={id as string} />
<BaseBox style={{ height: 500 }}> <BaseBox style={{ height: 500 }}>
<StackCustom>
<TextCustom align="center" bold size="large"> <TextCustom align="center" bold size="large">
Partisipan Partisipan
</TextCustom> </TextCustom>
@@ -40,7 +39,6 @@ export default function CollaborationDetailParticipant() {
} }
/> />
))} ))}
</StackCustom>
</BaseBox> </BaseBox>
</ViewWrapper> </ViewWrapper>

View File

@@ -1,78 +1,91 @@
import { import {
AvatarCustom, AlertDefaultSystem,
AvatarUsernameAndOtherComponent, BackButton,
BaseBox, ButtonCustom,
DotButton,
DrawerCustom, DrawerCustom,
Grid, MenuDrawerDynamicGrid,
Spacing,
StackCustom, StackCustom,
TextCustom, TextCustom,
ViewWrapper, ViewWrapper
} from "@/components"; } from "@/components";
import CheckboxCustom from "@/components/Checkbox/CheckboxCustom"; import { IconEdit } from "@/components/_Icon";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection"; import Collaboration_BoxDetailSection from "@/screens/Collaboration/BoxDetailSection";
import { Feather, MaterialIcons } from "@expo/vector-icons"; import Collaboration_MainParticipanSelectedSection from "@/screens/Collaboration/ProjectMainSelectedSection";
import { useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useState } from "react";
import { View } from "react-native";
export default function CollaborationDetailProjectMain() { export default function CollaborationDetailProjectMain() {
const { id } = useLocalSearchParams(); const { id } = useLocalSearchParams();
const [openDrawer, setOpenDrawer] = useState(false);
const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false); const [openDrawerParticipant, setOpenDrawerParticipant] = useState(false);
const [agreed, setAgreed] = useState(false); const [selected, setSelected] = useState<(string | number)[]>([]);
const [newsletter, setNewsletter] = useState(false);
const handleEdit = () => {
console.log("Edit collaboration");
router.push("/(application)/(user)/collaboration/(id)/edit");
};
return ( return (
<> <>
<Stack.Screen
options={{
title: "Proyek Saya",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper> <ViewWrapper>
<Collaboration_BoxDetailSection id={id as string} /> <Collaboration_BoxDetailSection id={id as string} />
<BaseBox style={{ height: 500 }}> <Collaboration_MainParticipanSelectedSection
<StackCustom> selected={selected}
<TextCustom size="default" color="red" bold> setSelected={setSelected}
*{" "} setOpenDrawerParticipant={setOpenDrawerParticipant}
<TextCustom size="small" semiBold> />
Pilih user yang akan menjadi tim proyek anda
</TextCustom>
</TextCustom>
{Array.from({ length: 5 }).map((_, index) => ( <ButtonCustom
<Grid key={index}> onPress={() => {
<Grid.Col AlertDefaultSystem({
span={2} title: "Buat Grup",
style={{ alignItems: "center", justifyContent: "center" }} message:
> "Apakah anda yakin ingin membuat grup untuk proyek ini ?",
<CheckboxCustom textLeft: "Tidak",
value={newsletter} textRight: "Ya",
onChange={() => { onPressLeft: () => {},
console.log("newsletter", newsletter); onPressRight: () => {
setNewsletter(!newsletter); router.navigate(
}} "/(application)/(user)/collaboration/(tabs)/group"
/> );
</Grid.Col> console.log("selected :", selected);
<Grid.Col span={2} style={{ alignItems: "center" }}> },
<AvatarCustom /> });
</Grid.Col> }}
<Grid.Col span={6} style={{ justifyContent: "center" }}> >
<TextCustom bold truncate> Buat Grup
Username </ButtonCustom>
</TextCustom> <Spacing />
</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> </ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Edit",
path: "/(application)/(user)/collaboration/(tabs)/group",
icon: <IconEdit />,
},
]}
onPressItem={(item) => {
handleEdit();
}}
/>
</DrawerCustom>
<DrawerCustom <DrawerCustom
isVisible={openDrawerParticipant} isVisible={openDrawerParticipant}
closeDrawer={() => setOpenDrawerParticipant(false)} closeDrawer={() => setOpenDrawerParticipant(false)}

View File

@@ -0,0 +1,53 @@
import {
ButtonCustom,
SelectCustom,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import { router } from "expo-router";
export default function CollaborationEdit() {
return (
<ViewWrapper>
<StackCustom gap={"xs"}>
<TextInputCustom label="Judul" placeholder="Masukan judul" required />
<TextInputCustom label="Lokasi" placeholder="Masukan lokasi" required />
<SelectCustom
label="Pilih Industri"
data={[
{ label: "Industri 1", value: "industri-1" },
{ label: "Industri 2", value: "industri-2" },
{ label: "Industri 3", value: "industri-3" },
]}
onChange={(value) => console.log(value)}
/>
<TextAreaCustom
required
label="Tujuan Proyek"
placeholder="Masukan tujuan proyek"
showCount
maxLength={1000}
/>
<TextAreaCustom
required
label="Keuntungan Proyek"
placeholder="Masukan keuntungan proyek"
showCount
maxLength={1000}
/>
<ButtonCustom
title="Update"
onPress={() => {
console.log("Update proyek");
router.back();
}}
/>
</StackCustom>
</ViewWrapper>
);
}

View File

@@ -1,61 +1,82 @@
import React, { useState } from "react"; import { AccentColor } from "@/constants/color-palet";
import { View, Text, TouchableOpacity, Animated, Easing } from "react-native";
import { MaterialIcons } from "@expo/vector-icons"; // Bisa diganti dengan ikon lain import { MaterialIcons } from "@expo/vector-icons"; // Bisa diganti dengan ikon lain
import React, { useContext } from "react";
import { Animated, Text, TouchableOpacity, View } from "react-native";
import { checkboxStyles } from "./checkbox-styles"; import { checkboxStyles } from "./checkbox-styles";
// Context untuk Group
interface CheckboxGroupContextType {
value: (string | number)[];
onChange: (value: (string | number)[]) => void;
disabled?: boolean;
}
const CheckboxGroupContext =
React.createContext<CheckboxGroupContextType | null>(null);
// Tipe props // Tipe props
// Tambahkan prop baru: groupValueKey
interface CheckboxProps { interface CheckboxProps {
label?: string; label?: string;
description?: string; description?: string;
error?: string; error?: string;
value?: boolean; value?: boolean; // controlled value (untuk standalone)
onChange?: (checked: boolean) => void; onChange?: (checked: boolean) => void;
disabled?: boolean; disabled?: boolean;
size?: number; // ukuran checkbox (default: 20) size?: number;
color?: string; // warna utama (default: '#3b82f6' - biru tailwind) color?: string;
style?: object; style?: object;
component?: React.ReactNode; component?: React.ReactNode;
// Prop tambahan untuk Group
valueKey?: string | number; // nilai unik untuk identifikasi di group
} }
export const CheckboxCustom: React.FC<CheckboxProps> = ({ const CheckboxCustom: React.FC<CheckboxProps> = ({
label, label,
description, description,
error, error,
value: controlledValue, value: controlledValue,
onChange, onChange,
disabled = false, disabled: propDisabled,
size = 20, size = 20,
color = "#3b82f6", color = AccentColor.softblue,
style, style,
component, component,
valueKey,
}) => { }) => {
const [uncontrolledChecked, setUncontrolledChecked] = useState(false); // const [uncontrolledChecked, setUncontrolledChecked] = useState(false);
const isChecked = controlledValue ?? uncontrolledChecked; // const isChecked = controlledValue ?? uncontrolledChecked;
// const scaleValue = new Animated.Value(isChecked ? 1 : 0);
const group = useContext(CheckboxGroupContext);
const isInsideGroup = !!group && valueKey !== undefined;
// Jika di dalam group, gunakan logika group
const isChecked = isInsideGroup
? group.value.includes(valueKey!)
: controlledValue ?? false;
const disabled = propDisabled || (isInsideGroup && group.disabled);
const scaleValue = new Animated.Value(isChecked ? 1 : 0); const scaleValue = new Animated.Value(isChecked ? 1 : 0);
const toggle = () => { const toggle = () => {
if (disabled) return; if (disabled) return;
const newValue = !isChecked; if (isInsideGroup) {
if (onChange) onChange(newValue); const newValue = isChecked
if (controlledValue === undefined) { ? group.value.filter((v) => v !== valueKey)
setUncontrolledChecked(newValue); : [...group.value, valueKey!];
group.onChange(newValue);
} else if (onChange) {
onChange(!controlledValue);
} }
// Animasi scale
Animated.spring(scaleValue, {
toValue: newValue ? 1 : 0,
friction: 7,
tension: 40,
useNativeDriver: true,
}).start();
}; };
const styles = checkboxStyles({ const styles = checkboxStyles({
size, size,
color, color,
disabled, disabled: disabled as boolean,
error: !!error, error: !!error,
}); });
@@ -106,4 +127,7 @@ export const CheckboxCustom: React.FC<CheckboxProps> = ({
); );
}; };
export default CheckboxCustom export default CheckboxCustom;
// Export context agar bisa digunakan
export { CheckboxGroupContext };

View File

@@ -0,0 +1,75 @@
import React, { useState, useMemo } from "react";
import { View, Text, StyleSheet } from "react-native";
import { CheckboxGroupContext } from "./CheckboxCustom";
interface CheckboxGroupProps {
value?: (string | number)[];
onChange?: (values: (string | number)[]) => void;
defaultValue?: (string | number)[];
label?: string;
description?: string;
error?: string;
disabled?: boolean;
children: React.ReactNode;
style?: object;
}
const CheckboxGroup: React.FC<CheckboxGroupProps> = ({
value: controlledValue,
onChange,
defaultValue = [],
label,
description,
error,
disabled = false,
children,
style,
}) => {
const [uncontrolledValue, setUncontrolledValue] =
useState<(string | number)[]>(defaultValue);
const value = controlledValue ?? uncontrolledValue;
const handleChange = onChange ?? setUncontrolledValue;
const contextValue = useMemo(
() => ({ value, onChange: handleChange, disabled }),
[value, handleChange, disabled]
);
return (
<CheckboxGroupContext.Provider value={contextValue}>
<View style={[styles.container, style]}>
{label ? <Text style={styles.label}>{label}</Text> : null}
{description ? (
<Text style={styles.description}>{description}</Text>
) : null}
{children}
{error ? <Text style={styles.errorText}>{error}</Text> : null}
</View>
</CheckboxGroupContext.Provider>
);
};
const styles = StyleSheet.create({
container: {
gap: 8,
},
label: {
fontSize: 16,
fontWeight: "600",
color: "#f8f9fa",
marginBottom: 4,
},
description: {
fontSize: 14,
color: "#ced4da",
marginBottom: 8,
},
errorText: {
color: "#e03131",
fontSize: 14,
marginTop: 4,
},
});
export default CheckboxGroup;

View File

@@ -11,8 +11,7 @@ export const checkboxStyles = (props: {
container: { container: {
flexDirection: "row", flexDirection: "row",
alignItems: "flex-start", alignItems: "flex-start",
// marginBottom: 12, // marginBottom: 12,
}, },
innerContainer: { innerContainer: {
flexDirection: "row", flexDirection: "row",

View File

@@ -0,0 +1,10 @@
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { FontAwesome5 } from "@expo/vector-icons";
export default function IconEdit() {
return (
<>
<FontAwesome5 name="edit" size={ICON_SIZE_SMALL} color="white" />
</>
);
}

View File

@@ -0,0 +1,3 @@
import IconEdit from "./IconEdit";
export { IconEdit };

View File

@@ -7,6 +7,9 @@ import ButtonCenteredOnly from "./Button/ButtonCenteredOnly";
import ButtonCustom from "./Button/ButtonCustom"; import ButtonCustom from "./Button/ButtonCustom";
import DotButton from "./Button/DotButton"; import DotButton from "./Button/DotButton";
import FloatingButton from "./Button/FloatingButton"; import FloatingButton from "./Button/FloatingButton";
// Checkbox
import CheckboxCustom from "./Checkbox/CheckboxCustom";
import CheckboxGroup from "./Checkbox/CheckboxGroup";
// Drawer // Drawer
import DrawerCustom from "./Drawer/DrawerCustom"; import DrawerCustom from "./Drawer/DrawerCustom";
import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird"; import MenuDrawerDynamicGrid from "./Drawer/MenuDrawerDynamicGird";
@@ -59,18 +62,23 @@ export {
// Box // Box
BaseBox, BaseBox,
BoxButtonOnFooter, BoxButtonOnFooter,
BoxWithHeaderSection, ButtonCenteredOnly, BoxWithHeaderSection,
ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
// Center // Center
CenterCustom, CenterCustom,
// Checkbox
CheckboxCustom,
CheckboxGroup,
// Clickable // Clickable
ClickableCustom, ClickableCustom,
// Divider // Divider
Divider, Divider,
DividerCustom, DividerCustom,
// Drawer // Drawer
DrawerCustom, FloatingButton, DrawerCustom,
FloatingButton,
// Grid // Grid
Grid, Grid,
InformationBox, InformationBox,
@@ -85,7 +93,8 @@ export {
// ShareComponent // ShareComponent
Spacing, Spacing,
// Stack // Stack
StackCustom, TabBarBackground, StackCustom,
TabBarBackground,
// TextArea // TextArea
TextAreaCustom, TextAreaCustom,
// Text // Text
@@ -93,6 +102,5 @@ export {
// TextInput // TextInput
TextInputCustom, TextInputCustom,
// ViewWrapper // ViewWrapper
ViewWrapper ViewWrapper,
}; };

View File

@@ -0,0 +1,69 @@
import {
AvatarCustom,
BaseBox,
CheckboxCustom,
CheckboxGroup,
Grid,
StackCustom,
TextCustom
} from "@/components";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { MaterialIcons } from "@expo/vector-icons";
import { View } from "react-native";
export default function Collaboration_ProjectMainSelectedSection({
selected,
setSelected,
setOpenDrawerParticipant,
}: {
selected: (string | number)[];
setSelected: (value: (string | number)[]) => void;
setOpenDrawerParticipant: (value: boolean) => void;
}) {
return (
<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>
<CheckboxGroup value={selected} onChange={setSelected}>
{Array.from({ length: 5 }).map((_, index) => (
<View key={index}>
<Grid key={index}>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<CheckboxCustom valueKey={`user-${index}`} />
</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"
onPress={() => setOpenDrawerParticipant(true)}
/>
</Grid.Col>
</Grid>
</View>
))}
</CheckboxGroup>
</StackCustom>
</BaseBox>
);
}