feat: implement comprehensive Form UI library with React Hook Form integration, Zod validation, and i18n support.
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user