Component

Add:
- Badge
- Container

Voting
Add:
- screens/Voting

Fix:
- (tabs)/ index, contribution, status

# No Issue
This commit is contained in:
2025-07-28 12:24:35 +08:00
parent 20258d1fe5
commit b18044f53f
10 changed files with 467 additions and 19 deletions

View File

@@ -165,14 +165,8 @@ export default function UserLayout() {
headerLeft: () => <BackButton path="/home" />, headerLeft: () => <BackButton path="/home" />,
}} }}
/> />
{/* <Stack.Screen {/* <Stack.Screen
name="voting/[id]/index"
options={{
title: "Detail Voting",
headerLeft: () => <BackButton />,
}}
/>
<Stack.Screen
name="voting/[id]/edit" name="voting/[id]/edit"
options={{ options={{
title: "Edit Voting", title: "Edit Voting",

View File

@@ -1,9 +1,27 @@
import { TextCustom, ViewWrapper } from "@/components"; import {
BadgeCustom,
StackCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { GStyles } from "@/styles/global-styles";
export default function VotingContribution() { export default function VotingContribution() {
const bottomComponent = (
<StackCustom>
<TextCustom align="center">Pilihan Anda:</TextCustom>
<BadgeCustom style={[GStyles.alignSelfCenter]}>Pilihan 1</BadgeCustom>
</StackCustom>
);
return ( return (
<ViewWrapper> <ViewWrapper hideFooter>
<TextCustom>Voting Contribution</TextCustom> {Array.from({ length: 5 }).map((_, index) => (
<Voting_BoxPublishSection
key={index}
bottomComponent={bottomComponent}
/>
))}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,9 +1,23 @@
import { TextCustom, ViewWrapper } from "@/components"; import {
FloatingButton,
SearchInput,
ViewWrapper
} from "@/components";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { router } from "expo-router";
export default function VotingBeranda() { export default function VotingBeranda() {
return ( return (
<ViewWrapper> <ViewWrapper
<TextCustom>Voting Beranda</TextCustom> hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={<SearchInput placeholder="Cari voting" />}
>
{Array.from({ length: 5 }).map((_, index) => (
<Voting_BoxPublishSection key={index} />
))}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,9 +1,61 @@
import { TextCustom, ViewWrapper } from "@/components"; import {
BadgeCustom,
BaseBox,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { masterStatus } from "@/lib/dummy-data/_master/status";
import dayjs from "dayjs";
import { useState } from "react";
export default function VotingStatus() { export default function VotingStatus() {
const [activeCategory, setActiveCategory] = useState<string | null>(
"publish"
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
const scrollComponent = (
<ScrollableCustom
data={masterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return ( return (
<ViewWrapper> <ViewWrapper headerComponent={scrollComponent} hideFooter>
<TextCustom>Voting Status</TextCustom> {Array.from({ length: 10 }).map((_, i) => (
<BaseBox
key={i}
paddingTop={20}
paddingBottom={20}
// href={`/job/${e.id}/${activeCategory}/detail`}
// onPress={() => console.log("pressed")}
>
<StackCustom>
<TextCustom align="center" bold truncate size="large">
Lorem ipsum dolor sit {activeCategory}
</TextCustom>
<BadgeCustom
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
</StackCustom>
</BaseBox>
))}
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -1,9 +1,78 @@
import { TextInputCustom, ViewWrapper } from "@/components"; import {
BoxButtonOnFooter,
ButtonCenteredOnly,
ButtonCustom,
Grid,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { MainColor } from "@/constants/color-palet";
import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import { TouchableOpacity } from "react-native";
export default function CreateVoting() { export default function CreateVoting() {
const buttonSubmit = () => {
return (
<>
<BoxButtonOnFooter>
<ButtonCustom
onPress={() =>
router.replace("/(application)/(user)/voting/(tabs)/status")
}
>
Simpan
</ButtonCustom>
</BoxButtonOnFooter>
</>
);
};
return ( return (
<ViewWrapper> <ViewWrapper footerComponent={buttonSubmit()}>
<TextInputCustom>Create Voting</TextInputCustom> <StackCustom gap={"xs"}>
<TextInputCustom
label="Judul Voting"
placeholder="MasukanJudul Voting"
required
/>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukan Deskripsi"
required
showCount
maxLength={1000}
/>
<DateTimePickerCustom label="Mulai Voting" required />
<DateTimePickerCustom label="Voting Berakhir" required />
<Grid>
<Grid.Col span={10}>
<TextInputCustom
label="Pilihan"
placeholder="Masukan Pilihan"
required
/>
</Grid.Col>
<Grid.Col
span={2}
style={{ alignItems: "center", justifyContent: "center" }}
>
<TouchableOpacity onPress={() => console.log("delete")}>
<Ionicons name="trash" size={24} color={MainColor.red} />
</TouchableOpacity>
</Grid.Col>
</Grid>
<ButtonCenteredOnly onPress={() => console.log("add")}>
Tambah Pilihan
</ButtonCenteredOnly>
<Spacing />
</StackCustom>
</ViewWrapper> </ViewWrapper>
); );
} }

View File

@@ -0,0 +1,189 @@
import React from "react";
import {
StyleSheet,
Text,
TextStyle,
View,
ViewProps,
ViewStyle,
} from "react-native";
type BadgeVariant = "filled" | "light" | "outline" | "dot";
type BadgeColor =
| "primary"
| "success"
| "warning"
| "danger"
| "gray"
| "dark";
type BadgeSize = "xs" | "sm" | "md" | "lg";
interface BadgeProps extends ViewProps {
children: React.ReactNode;
variant?: BadgeVariant;
color?: BadgeColor;
size?: BadgeSize;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
radius?: number;
fullWidth?: boolean;
textColor?: string;
}
const BadgeCustom: React.FC<BadgeProps> = ({
children,
variant = "filled",
color = "primary",
size = "md",
leftIcon,
rightIcon,
radius = 50,
fullWidth = false,
textColor = "#fff",
style,
...props
}) => {
const colors = {
primary: "#339AF0",
success: "#40C057",
warning: "#FAB005",
danger: "#FA5252",
gray: "#868E96",
dark: "#212529",
};
const themeColor = colors[color];
// Ganti bagian sizeStyles dan styles.container
const sizeStyles = {
xs: {
fontSize: 10,
paddingHorizontal: 6,
paddingVertical: 2,
height: 18, // Dinaikkan dari 16 → 18 agar teks tidak terpotong
lineHeight: 10, // 👈 Penting: match fontSize agar kontrol vertikal lebih baik
},
sm: {
fontSize: 11,
paddingHorizontal: 8,
paddingVertical: 3,
height: 20,
lineHeight: 11,
},
md: {
fontSize: 12,
paddingHorizontal: 10,
paddingVertical: 4,
height: 24,
lineHeight: 12,
},
lg: {
fontSize: 14,
paddingHorizontal: 12,
paddingVertical: 6,
height: 30,
lineHeight: 14,
},
};
const currentSize = sizeStyles[size];
let variantStyles: ViewStyle & { text: TextStyle } = {
backgroundColor: themeColor,
borderColor: themeColor,
borderWidth: 1,
borderRadius: radius,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
text: { color: textColor, fontWeight: "600" },
};
switch (variant) {
case "light":
variantStyles.backgroundColor = `${themeColor}20`;
variantStyles.text.color = themeColor;
break;
case "outline":
variantStyles.backgroundColor = "transparent";
variantStyles.text.color = themeColor;
break;
case "dot":
variantStyles.backgroundColor = themeColor;
variantStyles.paddingHorizontal = 0;
variantStyles.paddingVertical = 0;
variantStyles.height = currentSize.fontSize * 2;
variantStyles.width = currentSize.fontSize * 2;
variantStyles.borderRadius = currentSize.fontSize;
break;
default:
break;
}
if (variant === "dot") {
return (
<View
style={[variantStyles, fullWidth && styles.fullWidth, style]}
{...props}
/>
);
}
return (
<View
style={[
styles.container,
variantStyles,
currentSize,
{ borderRadius: radius },
fullWidth && styles.fullWidth,
style,
]}
{...props}
>
{leftIcon && <View style={styles.iconContainer}>{leftIcon}</View>}
<Text
style={[
styles.text,
variantStyles.text,
{ fontSize: currentSize.fontSize },
]}
>
{children}
</Text>
{rightIcon && <View style={styles.iconContainer}>{rightIcon}</View>}
</View>
);
};
const styles = StyleSheet.create({
container: {
alignSelf: "flex-start",
flexDirection: "row",
alignItems: "center", // Vertikal center anak-anak (termasuk teks)
justifyContent: "center", // Horizontal center
paddingHorizontal: 10,
paddingVertical: 4,
minWidth: 20,
borderRadius: 6,
// ❌ Jangan gunakan `height` fix di sini — kita override per size
},
text: {
fontWeight: "600",
textAlign: "center",
// ❌ Hapus marginHorizontal jika mengganggu alignment
// marginHorizontal: 2, // Opsional, bisa dihapus atau dikurangi
includeFontPadding: false, // 👈 Ini penting untuk Android!
padding: 0, // Bersihkan padding tambahan dari font
},
iconContainer: {
marginHorizontal: 2, // Lebih kecil dari sebelumnya agar tidak ganggu ukuran kecil
},
fullWidth: {
width: "100%",
alignSelf: "stretch",
justifyContent: "center",
},
});
export default BadgeCustom;

View File

@@ -0,0 +1,44 @@
import { MainColor } from "@/constants/color-palet";
import React from "react";
import { StyleSheet, TextInput, View } from "react-native";
interface CircularInputProps {
value: string | number;
onChange?: (value: string) => void;
}
const CircularInput: React.FC<CircularInputProps> = ({ value, onChange }) => {
return (
<View style={styles.circleContainer}>
<TextInput
value={String(value)}
onChangeText={onChange}
style={styles.input}
keyboardType="numeric"
maxLength={2} // Batasan maksimal karakter
/>
</View>
);
};
const styles = StyleSheet.create({
circleContainer: {
width: 60,
height: 60,
borderRadius: 40, // Setiap setengah dari lebar/tinggi
borderWidth: 2,
borderColor: MainColor.yellow, // Warna kuning
justifyContent: "center",
alignItems: "center",
},
input: {
color: MainColor.yellow, // Warna kuning
fontSize: 24,
fontWeight: "bold",
textAlign: "center",
padding: 0,
backgroundColor: "transparent",
},
});
export default CircularInput;

View File

@@ -7,6 +7,10 @@ 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";
// Badge
import BadgeCustom from "./Badge/BadgeCustom";
// Container
import CircleContainer from "./Container/CircleContainer";
// Checkbox // Checkbox
import CheckboxCustom from "./Checkbox/CheckboxCustom"; import CheckboxCustom from "./Checkbox/CheckboxCustom";
import CheckboxGroup from "./Checkbox/CheckboxGroup"; import CheckboxGroup from "./Checkbox/CheckboxGroup";
@@ -68,6 +72,8 @@ export {
ButtonCenteredOnly, ButtonCenteredOnly,
ButtonCustom, ButtonCustom,
DotButton, DotButton,
// Badge
BadgeCustom,
// Center // Center
CenterCustom, CenterCustom,
// Checkbox // Checkbox
@@ -75,6 +81,8 @@ export {
CheckboxGroup, CheckboxGroup,
// Clickable // Clickable
ClickableCustom, ClickableCustom,
// Container
CircleContainer,
// Divider // Divider
Divider, Divider,
DividerCustom, DividerCustom,

View File

@@ -0,0 +1,55 @@
import {
BoxWithHeaderSection,
AvatarUsernameAndOtherComponent,
Spacing,
StackCustom,
TextCustom,
BadgeCustom,
Grid,
CircleContainer,
} from "@/components";
import dayjs from "dayjs";
export default function Voting_BoxPublishSection({
bottomComponent,
}: {
bottomComponent?: React.ReactNode;
}) {
return (
<>
<BoxWithHeaderSection>
<AvatarUsernameAndOtherComponent avatarHref="/profile/1" />
<Spacing />
<StackCustom>
<TextCustom align="center" bold truncate size="large">
Voting Title Here
</TextCustom>
<BadgeCustom
style={{ width: "70%", alignSelf: "center" }}
variant="light"
>
{dayjs().format("DD/MM/YYYY")} -{" "}
{dayjs().add(1, "day").format("DD/MM/YYYY")}
</BadgeCustom>
<Grid>
<Grid.Col span={3} style={{ alignItems: "center" }}>
<CircleContainer value={"10"} />
</Grid.Col>
<Grid.Col span={3} style={{ alignItems: "center" }}>
<CircleContainer value={"9"} />
</Grid.Col>
<Grid.Col span={3} style={{ alignItems: "center" }}>
<CircleContainer value={"10"} />
</Grid.Col>
<Grid.Col span={3} style={{ alignItems: "center" }}>
<CircleContainer value={"9"} />
</Grid.Col>
</Grid>
{bottomComponent}
</StackCustom>
</BoxWithHeaderSection>
</>
);
}

View File

@@ -315,4 +315,9 @@ export const GStyles = StyleSheet.create({
}, },
// =============== TEXT INPUT , TEXT AREA , SELECT =============== // // =============== TEXT INPUT , TEXT AREA , SELECT =============== //
// =============== Alignment =============== //
alignSelfCenter: {
alignSelf: "center",
},
}); });