feat: enhance input showcase with new input components and status handling

This commit is contained in:
Firman Ramdhani
2026-01-26 12:23:26 +07:00
parent d4114702a0
commit 3c11b98153
8 changed files with 396 additions and 138 deletions
@@ -3,6 +3,7 @@ export * from './form-provider';
export * from './input/input.component';
export * from './input/input-password.component';
export * from './input/input-currency.component';
export * from './input/input-number.component';
export * from './textarea/textarea.component';
@@ -0,0 +1,41 @@
import { InputCurrencyProps } from '../types';
import { InputNumber } from './input-number.component';
export const InputCurrency = (props: InputCurrencyProps) => {
const { prefix = 'Rp ', ...restProps } = props;
// Change format from 10000 to "Rp 10.000"
const formatCurrency = (value: number | string | undefined) => {
if (value === undefined || value === null || value === '') {
return '';
}
// Convert value to string for processing
const valStr = `${value}`;
const formatted = valStr.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
return `${prefix}${formatted}`;
};
// Change parse from "Rp 10.000" to 10000
const parseCurrency = (displayValue: string | undefined) => {
if (!displayValue) return '';
// Remove prefix and all non-digit characters (except comma if decimal support is needed)
// Here we assume pure integer input, so dots (.) are removed.
const cleanValue = displayValue.replace(prefix, '').replace(/\./g, '');
// If you need decimal support (comma), use this regex instead:
// return displayValue.replace(prefix, '').replace(/\./g, '').replace(/,/g, '.');
return cleanValue;
};
return (
<InputNumber
{...restProps}
formatter={formatCurrency}
parser={parseCurrency}
// Ensure minimum value is 0 for currency input
min={props.min !== undefined ? props.min : 0}
/>
);
};
@@ -1,6 +1,101 @@
import React, { useState } from 'react';
import { InputPasswordProps } from '../types';
import { Input } from './input.component';
/**
* Eye Icon for visibility toggle
* @returns JSX Element
*/
const EyeIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
width="1em"
height="1em"
className="w-4 h-4"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
);
/**
* Eye Off Icon for visibility toggle
* @returns JSX Element
*/
const EyeOffIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
width="1em"
height="1em"
className="w-4 h-4"
>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
);
/**
* InputPassword Component
* @param props InputPasswordProps
* @returns JSX Element
*/
export const InputPassword = (props: InputPasswordProps) => {
return <Input {...props} type="password" />;
// Set default props and state
const { suffix, disabled, visibilityToggle = true, ...restProps } = props;
const [visible, setVisible] = useState(false);
const toggleVisibility = () => {
if (disabled) return;
setVisible(!visible);
};
// If no suffix and no visibility toggle, render simple password input
if (!suffix && !visibilityToggle) {
return <Input {...restProps} disabled={disabled} type="password" />;
}
const combinedSuffix = (
<div className="flex items-center gap-2">
{/* Suffix User */}
{suffix && <span className="text-gray-400 select-none">{suffix}</span>}
{/* Visibility Toggle Button */}
{visibilityToggle && (
<button
type="button"
onClick={toggleVisibility}
disabled={disabled}
className={`
flex items-center justify-center
focus:outline-none transition-colors
${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer text-gray-400 hover:text-gray-600'}
`}
>
{visible ? <EyeIcon /> : <EyeOffIcon />}
</button>
)}
</div>
);
return (
<Input
{...restProps}
disabled={disabled}
// If toggle is disabled, force type to 'password', ignore visible state
type={visibilityToggle && visible ? 'text' : 'password'}
suffix={combinedSuffix}
/>
);
};
@@ -163,6 +163,17 @@
border-color: var(--color-success-500) !important;
}
/* Info (Outlined) */
.input-number-variant-outlined.input-number-status-info {
border-color: var(--color-info-500) !important;
}
.input-number-variant-outlined.input-number-status-info:focus-within,
.input-number-variant-outlined.input-number-status-info.rc-input-number-focused,
.input-number-variant-outlined.input-number-status-info.rc-input-number-affix-wrapper-focused {
box-shadow: 0 0 0 3px var(--color-info-100) !important;
border-color: var(--color-info-500) !important;
}
/* ====================
VARIANT: FILLED
==================== */
@@ -43,12 +43,13 @@ interface BaseComponentProps {
* Define specific component props
*/
export interface InputProps extends RcInputProps, BaseComponentProps {}
export interface InputPasswordProps extends InputProps {
visibilityToggle?: boolean; // Default: true
}
/**
* Extend specific component props from base props
* This ensures consistency across different input components
* and allows for shared properties like 'status'
*/
export interface InputPasswordProps extends InputProps {}
export interface InputNumberProps extends RcInputNumberProps, BaseComponentProps {}
export interface InputCurrencyProps extends InputNumberProps, BaseComponentProps {
prefix?: string; // Optional: Custom prefix, default 'Rp '
}
export interface TextAreaProps extends RcTextAreaProps, BaseComponentProps {}
+2 -1
View File
@@ -11,7 +11,8 @@
@layer base {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: var(--color-gray-50);
/* background-color: var(--color-gray-50); */
background-color: #ffffff;
color: var(--color-gray-800);
@apply text-base; /* Set global base font to 13px */
-webkit-font-smoothing: antialiased;