feat: enhance form item component with status handling and styling for error/warning states
This commit is contained in:
@@ -48,6 +48,7 @@ export default function GroupComponent({ status }: { status?: InputStatusType })
|
||||
status={status}
|
||||
visibilityToggle={false}
|
||||
prefix={<Fix>🔑</Fix>}
|
||||
suffix={<Fix>?</Fix>}
|
||||
placeholder="Hidden Password"
|
||||
/>
|
||||
<InputNumber
|
||||
@@ -111,6 +112,7 @@ export default function GroupComponent({ status }: { status?: InputStatusType })
|
||||
status={status}
|
||||
visibilityToggle={false}
|
||||
prefix={<Fix>🔑</Fix>}
|
||||
suffix={<Fix>?</Fix>}
|
||||
placeholder="Hidden Password"
|
||||
/>
|
||||
<InputNumber
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user