From c39a8eb175b5a5222b5bd3d5eb75baa6602497e5 Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:26:03 +0700 Subject: [PATCH] feat: enhance form item component with status handling and styling for error/warning states --- .../input-showcase/group-component.tsx | 2 + .../form-provider/form-item.component.tsx | 27 ++++---- .../forms/styles/form-item.style.css | 61 +++++++++++++++++++ .../components/forms/styles/form.style.css | 2 +- packages/ui/src/globals.css | 46 ++++++++------ 5 files changed, 108 insertions(+), 30 deletions(-) create mode 100644 packages/ui/src/components/forms/styles/form-item.style.css diff --git a/apps/web/src/apps/modules/example/input-showcase/group-component.tsx b/apps/web/src/apps/modules/example/input-showcase/group-component.tsx index bd00ce5..92fd1cf 100644 --- a/apps/web/src/apps/modules/example/input-showcase/group-component.tsx +++ b/apps/web/src/apps/modules/example/input-showcase/group-component.tsx @@ -48,6 +48,7 @@ export default function GroupComponent({ status }: { status?: InputStatusType }) status={status} visibilityToggle={false} prefix={🔑} + suffix={?} placeholder="Hidden Password" /> 🔑} + suffix={?} placeholder="Hidden Password" /> {(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 ( -
+ // Wrapper Div with status class +
+ {/* LABEL SECTION */} {label && ( -
); }} diff --git a/packages/ui/src/components/forms/styles/form-item.style.css b/packages/ui/src/components/forms/styles/form-item.style.css new file mode 100644 index 0000000..ed38617 --- /dev/null +++ b/packages/ui/src/components/forms/styles/form-item.style.css @@ -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); + } +} diff --git a/packages/ui/src/components/forms/styles/form.style.css b/packages/ui/src/components/forms/styles/form.style.css index 3caaaf7..5de21e2 100644 --- a/packages/ui/src/components/forms/styles/form.style.css +++ b/packages/ui/src/components/forms/styles/form.style.css @@ -1,4 +1,4 @@ +@import './form-item.style.css'; @import './input.style.css'; @import './input-number.style.css'; - @import './textarea.style.css'; diff --git a/packages/ui/src/globals.css b/packages/ui/src/globals.css index e6296af..9b86462 100644 --- a/packages/ui/src/globals.css +++ b/packages/ui/src/globals.css @@ -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);