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';
+28 -18
View File
@@ -35,23 +35,6 @@
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
/* -------------------------------
FORM DIMENSIONS (INPUT / TEXTAREA)
------------------------------- */
--input-height: 2.6rem; /* ~34px */
--input-font-size: var(--text-base);
--input-padding-x: 0.92rem; /* ~12px */
--input-padding-y: 0.62rem; /* ~8px */
--input-radius: 0.375rem; /* 6px */
/* Input Number Specific */
--input-handler-width: 1.5rem; /* 24px (Agak diperlebar dikit agar clickable) */
/* Textarea Specific */
--textarea-min-height: 6.15rem; /* 80px */
--textarea-padding-x: 0.92rem; /* 12px */
--textarea-padding-y: 0.62rem; /* 8px */
/* -------------------------------
COLORS: PALETTE
------------------------------- */
@@ -101,8 +84,35 @@
--color-info-600: #0284c7;
/* -------------------------------
COLORS: COMPONENT MAPPING
FORM ITEM SPECIFICS
------------------------------- */
/* Form Item Spacing */
--form-item-margin-bottom: 1.25rem; /* 20px */
/* Label Configuration */
--form-label-font-size: var(--text-sm); /* Uses scale text-sm (~12px) */
--form-label-color: var(--color-gray-700);
--form-label-font-weight: var(--font-weight-medium);
--form-label-margin-bottom: 0.375rem; /* ~6px */
/* Error/Warning Message Configuration */
--form-msg-font-size: var(--text-xs); /* Uses scale text-xs (~11px) */
--form-msg-margin-top: 0.375rem; /* ~6px */
--input-height: 2.6rem; /* ~34px */
--input-font-size: var(--text-base);
--input-padding-x: 0.92rem; /* ~12px */
--input-padding-y: 0.62rem; /* ~8px */
--input-radius: 0.375rem; /* 6px */
/* Input Number Specific */
--input-handler-width: 1.5rem; /* 24px (Slightly wider for better clickable) */
/* Textarea Specific */
--textarea-min-height: 6.15rem; /* 80px */
--textarea-padding-x: 0.92rem; /* 12px */
--textarea-padding-y: 0.62rem; /* 8px */
/* Input Colors */
--input-bg: #ffffff;
--input-border: var(--color-gray-300);
--input-border-hover: var(--color-brand-400);