feat: enhance input showcase with new input components and status handling
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user