feat: implement comprehensive Form UI library with React Hook Form integration, Zod validation, and i18n support.

This commit is contained in:
Firman Ramdhani
2026-06-15 18:40:22 +07:00
parent 00a2f218cc
commit a72b73bac4
45 changed files with 2064 additions and 10 deletions
+4 -1
View File
@@ -13,6 +13,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@repo/core-api": "workspace:*",
"@repo/core-events": "workspace:*",
"@repo/core-i18n": "workspace:*",
@@ -26,9 +27,11 @@
"lucide-react": "^1.17.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-hook-form": "^7.56.4",
"react-i18next": "^15.4.0",
"react-router-dom": "^7.11.0",
"tailwindcss": "^4.1.18"
"tailwindcss": "^4.1.18",
"zod": "^3.25.36"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
@@ -0,0 +1,169 @@
import { useState } from 'react';
import { z } from 'zod';
import { useForm, FormProvider, type SubmitHandler } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button, Paper, Title, Group, Stack, Code, Text } from '@repo/ui/components';
// Import our RHF-connected Field components from packages/ui
import {
FieldTextInput,
FieldNumberInput,
FieldSelect,
FieldColorInput,
FieldCheckbox,
FieldSegmentedControl
} from '@repo/ui/form';
// ─── 1. VALIDATOR SCHEMA WITH JSON I18N PAYLOAD ───────────────────
const erpOrderSchema = z.object({
customerName: z.string().min(3, {
message: JSON.stringify({ key: 'validation:min_length', values: { field: 'Nama Pelanggan', min: 3 } })
}),
email: z.string().email({
message: JSON.stringify({ key: 'validation:invalid_email' })
}),
orderType: z.enum(['BULK', 'RETAIL'], {
required_error: JSON.stringify({ key: 'validation:required', values: { field: 'Tipe Pesanan' } })
}),
quantity: z.number({
required_error: JSON.stringify({ key: 'validation:required', values: { field: 'Jumlah Roll' } })
}).min(5, {
message: JSON.stringify({ key: 'validation:min_length', values: { field: 'Jumlah Roll', min: 5 } })
}),
fabricColor: z.string().min(1, {
message: JSON.stringify({ key: 'validation:required', values: { field: 'Kode Warna Kain' } })
}),
priority: z.string(),
termsAccepted: z.literal(true, {
errorMap: () => ({ message: JSON.stringify({ key: 'validation:required', values: { field: 'Persetujuan Syarat & Ketentuan' } }) })
})
});
type ErpOrderPayload = z.infer<typeof erpOrderSchema>;
export default function FormShowcase() {
const [submittedData, setSubmittedData] = useState<ErpOrderPayload | null>(null);
// ─── 2. INITIALIZE REACT HOOK FORM WITH ZOD RESOLVER ─────────────
const methods = useForm<ErpOrderPayload>({
resolver: zodResolver(erpOrderSchema),
defaultValues: {
customerName: '',
email: '',
orderType: 'BULK',
quantity: 5,
fabricColor: '#228be6',
priority: 'normal',
// @ts-ignore - literal true is required but we start with false
termsAccepted: false,
},
});
const onSubmit: SubmitHandler<ErpOrderPayload> = (data) => {
setSubmittedData(data);
};
return (
<Stack gap="md" style={{ maxWidth: 800, margin: '0 auto', width: '100%' }}>
<Paper withBorder p="xl" radius="md" bg="var(--mantine-color-body)">
<Title order={3} mb="xs">📦 RHF + Zod + i18n Enterprise Demo</Title>
<Text c="dimmed" size="sm" mb="xl">
This form demonstrates the integration of our generated Mantine UI wrappers, React Hook Form micro-subscriptions, and Zod validation using JSON i18n payloads.
</Text>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit as any)}>
<Stack gap="md">
<Group grow align="flex-start">
<FieldTextInput
name="customerName"
label="Nama Pelanggan"
placeholder="Masukkan nama perusahaan atau perorangan"
withAsterisk
/>
<FieldTextInput
name="email"
label="Email Korespondensi"
placeholder="billing@company.com"
withAsterisk
/>
</Group>
<FieldSegmentedControl
name="priority"
label="Prioritas Produksi"
data={[
{ label: 'Normal', value: 'normal' },
{ label: 'Urgent', value: 'urgent' },
{ label: 'Critical', value: 'critical' }
]}
/>
<Group grow align="flex-start">
<FieldSelect
name="orderType"
label="Tipe Pesanan"
data={[
{ value: 'BULK', label: 'Grosir (Bulk)' },
{ value: 'RETAIL', label: 'Eceran (Retail)' }
]}
withAsterisk
/>
<FieldNumberInput
name="quantity"
label="Jumlah Produksi (Roll)"
placeholder="Minimal 5 roll"
withAsterisk
/>
</Group>
<FieldColorInput
name="fabricColor"
label="Spesifikasi Warna Bahan"
placeholder="Pilih atau masukkan kode HEX warna"
withAsterisk
/>
<FieldCheckbox
name="termsAccepted"
label="Saya menyetujui syarat & ketentuan produksi"
mt="md"
/>
<Group justify="flex-end" mt="xl">
<Button
type="button"
variant="subtle"
color="gray"
onClick={() => {
methods.reset();
setSubmittedData(null);
}}
>
Reset Form
</Button>
<Button type="submit" color="brand">
Submit Payload
</Button>
</Group>
</Stack>
</form>
</FormProvider>
</Paper>
{/* Output Panel untuk pembuktian Payload akhir */}
{submittedData && (
<Paper withBorder p="md" radius="md" bg="dark.8">
<Title order={5} c="green.4" mb="xs"> Validated Payload Output (PATCH/POST Ready):</Title>
<Code block color="dark" style={{ fontSize: '13px' }}>
{JSON.stringify(submittedData, null, 2)}
</Code>
</Paper>
)}
</Stack>
);
}
+14 -1
View File
@@ -22,11 +22,12 @@ import {
Box,
Paper,
} from '@repo/ui/components';
import { ShieldCheck, Database, Lock, Layout, Activity, Printer } from 'lucide-react';
import { ShieldCheck, Database, Lock, Layout, Activity, Printer, FileText } from 'lucide-react';
import PrinterList from './printer-list';
import ExamplePage from './example/example.page';
import EventsDemoPage from './events-demo';
import PouchSample from './pouch-sample';
import FormShowcase from './example/features/form-showcase';
interface ShowcaseViewProps {
colorScheme: ColorSchemeType;
@@ -55,6 +56,8 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
return 'Authentication & Security Layers';
case 'ui-components':
return 'Theme, Typography, Forms & Data Grids';
case 'forms':
return 'Enterprise Form Engine & Zod Validation';
case 'events':
return 'Global Event Bus Synchronization';
case 'hardware':
@@ -100,6 +103,9 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Tabs.Tab value="ui-components" leftSection={<Layout size={18} />}>
UI Components
</Tabs.Tab>
<Tabs.Tab value="forms" leftSection={<FileText size={18} />}>
Form Engine
</Tabs.Tab>
<Tabs.Tab value="storage" leftSection={<Database size={18} />}>
Offline Storage
</Tabs.Tab>
@@ -277,6 +283,13 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
</Stack>
)}
{/* --- FORMS TAB --- */}
{activeTab === 'forms' && (
<Stack gap="xl">
<FormShowcase />
</Stack>
)}
{/* --- STORAGE TAB --- */}
{activeTab === 'storage' && (
<Stack gap="xl">