feat: enhance form item component with status handling and styling for error/warning states

This commit is contained in:
Firman Ramdhani
2026-01-28 10:26:03 +07:00
parent ffe78cf47d
commit c39a8eb175
5 changed files with 108 additions and 30 deletions
@@ -1,4 +1,3 @@
// form-item.component.tsx
import React, { ReactElement } from 'react';
import { Field } from '@rc-component/form';
import { FormItemProps } from '../types';
@@ -9,35 +8,41 @@ export default function FormItem(props: FormItemProps) {
return (
<Field name={name} rules={rules} valuePropName={valuePropName} initialValue={initialValue}>
{(control, meta) => {
// Defensive check: tambahkan ?. untuk keamanan di versi baru
// Defensive check
const hasError = meta.errors && meta.errors.length > 0;
const hasWarning = meta.warnings && meta.warnings.length > 0;
// Determine status
const status = OverrideStatus ?? (hasError ? 'error' : hasWarning ? 'warning' : 'normal');
// Logic Clone Element
const isValid = React.isValidElement(children);
const childElement = isValid ? (children as ReactElement<{ status?: any }>) : null;
// Clone element logic tetap dipertahankan
// Pass status to child (Input/Textarea) so their borders change color
const childNode = childElement ? React.cloneElement(childElement, { ...control, status }) : children;
// Logic pengecekan 'required' pada rules
// Note: Pastikan rule object memiliki properti 'required', karena Rule juga bisa berupa function.
// Logic Required Check
const isRequired = rules?.some((r: any) => r && typeof r === 'object' && r.required);
return (
<div className="flex flex-col mb-4 w-full">
// Wrapper Div with status class
<div className={`rc-form-item status-${status}`}>
{/* LABEL SECTION */}
{label && (
<label className={`mb-1.5 text-sm font-medium ${hasError ? 'text-red-500' : 'text-gray-700'}`}>
<label className="rc-form-item-label">
{label}
{isRequired && <span className="ml-1 text-red-500">*</span>}
{isRequired && <span className="rc-form-item-required-mark">*</span>}
</label>
)}
<div className="relative">{childNode}</div>
{/* INPUT CONTROL */}
<div className="rc-form-item-control relative">{childNode}</div>
{hasError && <p className="mt-1.5 text-xs text-red-500">{meta.errors[0]}</p>}
{hasWarning && <p className="mt-1.5 text-xs text-yellow-500">{meta.warnings[0]}</p>}
{/* ERROR / WARNING MESSAGE */}
{(hasError || hasWarning) && (
<div className="rc-form-item-explain">{hasError ? meta.errors[0] : meta.warnings[0]}</div>
)}
</div>
);
}}
@@ -0,0 +1,61 @@
@layer components {
/* Container Form Item */
.rc-form-item {
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: var(--form-item-margin-bottom);
}
/* =========================
LABEL
========================= */
.rc-form-item-label {
display: flex;
align-items: center;
font-size: var(--form-label-font-size);
font-weight: var(--form-label-font-weight);
color: var(--form-label-color);
margin-bottom: var(--form-label-margin-bottom);
line-height: 1.4;
transition: color 0.2s;
}
/* Required Asterisk (*) */
.rc-form-item-required-mark {
margin-left: 0.25rem;
color: var(--color-error-500);
font-family: SimSun, sans-serif; /* Standard font for asterisks agar vertical align bagus */
}
/* =========================
MESSAGE (ERROR / WARNING)
========================= */
.rc-form-item-explain {
margin-top: var(--form-msg-margin-top);
font-size: var(--form-msg-font-size);
line-height: 1.4;
min-height: 1.4em; /* Mencegah layout jumping jika error muncul/hilang */
transition: color 0.2s;
}
/* =========================
STATUS MODIFIERS
========================= */
/* --- ERROR STATE --- */
.rc-form-item.status-error .rc-form-item-label {
color: var(--color-error-600); /* Label jadi merah saat error (opsional UI pattern) */
}
.rc-form-item.status-error .rc-form-item-explain {
color: var(--color-error-500);
}
/* --- WARNING STATE --- */
.rc-form-item.status-warning .rc-form-item-label {
color: var(--color-warning-600);
}
.rc-form-item.status-warning .rc-form-item-explain {
color: var(--color-warning-500);
}
}
@@ -1,4 +1,4 @@
@import './form-item.style.css';
@import './input.style.css';
@import './input-number.style.css';
@import './textarea.style.css';