feat: enhance input showcase with new input components and status handling
This commit is contained in:
@@ -1,126 +1,193 @@
|
|||||||
import { Input, InputNumber, InputStatusType, TextArea } from '@repo/ui';
|
import { Input, InputPassword, InputNumber, InputCurrency, InputStatusType, TextArea } from '@repo/ui';
|
||||||
|
|
||||||
|
// Helper untuk label kolom
|
||||||
|
const Label = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<div className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-wider">{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper untuk prefix/suffix visual
|
||||||
|
const Fix = ({ children }: { children: React.ReactNode }) => <span className="text-gray-400 text-sm">{children}</span>;
|
||||||
|
|
||||||
export default function GroupComponent({ status }: { status?: InputStatusType }) {
|
export default function GroupComponent({ status }: { status?: InputStatusType }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-12 p-8 rounded-2xl">
|
||||||
<div className="space-y-4 bg-white p-5 rounded-xl border border-gray-200 shadow-xs">
|
{/* --- SECTION 1: OUTLINED VARIANT (DEFAULT) --- */}
|
||||||
<h3 className="text-xs font-bold text-gray-400 uppercase">Default</h3>
|
<section className="space-y-6">
|
||||||
<div className="grid grid-cols-4 gap-4 ">
|
<h2 className="text-lg font-bold text-gray-800 border-b pb-2">Outlined Variant (Standard)</h2>
|
||||||
<div className="space-y-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<div className="text-gray-500 ">Default</div>
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<Label>Basic</Label>
|
||||||
<Input status={status} placeholder="Outlined Placeholder" />
|
<Input status={status} placeholder="Input text..." />
|
||||||
<InputNumber status={status} className="w-full" placeholder="OutlinedNumber Placeholder" />
|
<InputPassword status={status} placeholder="Password..." />
|
||||||
<TextArea status={status} placeholder="Outlined TextArea Placeholder" rows={2} />
|
<InputNumber status={status} className="w-full" placeholder="Number..." />
|
||||||
<Input status={status} variant="filled" placeholder="Filled Placeholder" />
|
<InputCurrency status={status} className="w-full" placeholder="Currency..." />
|
||||||
|
<TextArea status={status} placeholder="TextArea..." rows={2} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>With Prefix</Label>
|
||||||
|
<Input status={status} prefix={<Fix>@</Fix>} placeholder="username" />
|
||||||
|
<InputPassword status={status} prefix={<Fix>🔒</Fix>} placeholder="password" />
|
||||||
|
<InputNumber status={status} prefix={<Fix>#</Fix>} className="w-full" placeholder="id" />
|
||||||
|
<InputCurrency status={status} prefix="Rp " className="w-full" placeholder="price" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>With Suffix</Label>
|
||||||
|
<Input status={status} suffix={<Fix>.com</Fix>} placeholder="website" />
|
||||||
|
<InputPassword status={status} suffix={<Fix>Required</Fix>} placeholder="password" />
|
||||||
|
<InputNumber status={status} suffix={<Fix>kg</Fix>} className="w-full" placeholder="weight" />
|
||||||
|
<InputCurrency status={status} suffix={<Fix>nett</Fix>} className="w-full" placeholder="total" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Both & Special</Label>
|
||||||
|
<Input status={status} prefix={<Fix>https://</Fix>} suffix={<Fix>.io</Fix>} placeholder="domain" />
|
||||||
|
{/* Password Tanpa Toggle */}
|
||||||
|
<InputPassword
|
||||||
|
status={status}
|
||||||
|
visibilityToggle={false}
|
||||||
|
prefix={<Fix>🔑</Fix>}
|
||||||
|
placeholder="Hidden Password"
|
||||||
|
/>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
status={status}
|
status={status}
|
||||||
variant="filled"
|
prefix={<Fix>+</Fix>}
|
||||||
|
suffix={<Fix>%</Fix>}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
placeholder="Filled Number Placeholder"
|
placeholder="tax"
|
||||||
|
/>
|
||||||
|
<InputCurrency
|
||||||
|
status={status}
|
||||||
|
prefix="USD "
|
||||||
|
suffix={<Fix>cents</Fix>}
|
||||||
|
className="w-full"
|
||||||
|
placeholder="amount"
|
||||||
/>
|
/>
|
||||||
<TextArea status={status} variant="filled" placeholder="Filled TextArea Placeholder" rows={2} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
</section>
|
||||||
<div className="text-gray-500 ">With Prefix</div>
|
|
||||||
<div className="space-y-2">
|
{/* --- SECTION 2: FILLED VARIANT --- */}
|
||||||
|
<section className="space-y-6">
|
||||||
|
<h2 className="text-lg font-bold text-gray-800 border-b pb-2">Filled Variant</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Basic Filled</Label>
|
||||||
|
<Input variant="filled" status={status} placeholder="Filled Input" />
|
||||||
|
<InputPassword variant="filled" status={status} placeholder="Filled Password" />
|
||||||
|
<InputNumber variant="filled" status={status} className="w-full" placeholder="Filled Number" />
|
||||||
|
<InputCurrency variant="filled" status={status} className="w-full" placeholder="Filled Currency" />
|
||||||
|
<TextArea variant="filled" status={status} placeholder="Filled TextArea" rows={2} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Filled + Prefix</Label>
|
||||||
|
<Input variant="filled" status={status} prefix={<Fix>ID</Fix>} placeholder="12345" />
|
||||||
|
<InputPassword variant="filled" status={status} prefix={<Fix>🔑</Fix>} placeholder="password" />
|
||||||
|
<InputNumber variant="filled" status={status} prefix={<Fix>Qty:</Fix>} className="w-full" />
|
||||||
|
<InputCurrency variant="filled" status={status} prefix="Rp " className="w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Filled + Suffix</Label>
|
||||||
|
<Input variant="filled" status={status} suffix={<Fix>Optional</Fix>} placeholder="Full Name" />
|
||||||
|
<InputPassword variant="filled" status={status} suffix={<Fix>?</Fix>} placeholder="password" />
|
||||||
|
<InputNumber variant="filled" status={status} suffix={<Fix>pts</Fix>} className="w-full" />
|
||||||
|
<InputCurrency variant="filled" status={status} suffix={<Fix>tax-incl</Fix>} className="w-full" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Both & Special</Label>
|
||||||
<Input
|
<Input
|
||||||
|
variant="filled"
|
||||||
status={status}
|
status={status}
|
||||||
placeholder="Outlined Placeholder"
|
prefix={<Fix>https://</Fix>}
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
suffix={<Fix>.io</Fix>}
|
||||||
|
placeholder="domain"
|
||||||
|
/>
|
||||||
|
{/* Password Tanpa Toggle */}
|
||||||
|
<InputPassword
|
||||||
|
variant="filled"
|
||||||
|
status={status}
|
||||||
|
visibilityToggle={false}
|
||||||
|
prefix={<Fix>🔑</Fix>}
|
||||||
|
placeholder="Hidden Password"
|
||||||
/>
|
/>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
status={status}
|
|
||||||
className="w-full"
|
|
||||||
placeholder="OutlinedNumber Placeholder"
|
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<TextArea status={status} placeholder="Outlined TextArea Placeholder" rows={2} />
|
|
||||||
<Input
|
|
||||||
status={status}
|
|
||||||
variant="filled"
|
variant="filled"
|
||||||
placeholder="Filled Placeholder"
|
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
status={status}
|
status={status}
|
||||||
variant="filled"
|
prefix={<Fix>+</Fix>}
|
||||||
|
suffix={<Fix>%</Fix>}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
placeholder="Filled Number Placeholder"
|
placeholder="tax"
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
/>
|
||||||
<TextArea status={status} variant="filled" placeholder="Filled TextArea Placeholder" rows={2} />
|
<InputCurrency
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="text-gray-500 ">With Suffix</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Input
|
|
||||||
status={status}
|
|
||||||
placeholder="Outlined Placeholder"
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
status={status}
|
|
||||||
className="w-full"
|
|
||||||
placeholder="OutlinedNumber Placeholder"
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<TextArea status={status} placeholder="Outlined TextArea Placeholder" rows={2} />
|
|
||||||
<Input
|
|
||||||
status={status}
|
|
||||||
variant="filled"
|
variant="filled"
|
||||||
placeholder="Filled Placeholder"
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
status={status}
|
status={status}
|
||||||
variant="filled"
|
prefix="USD "
|
||||||
|
suffix={<Fix>cents</Fix>}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
placeholder="Filled Number Placeholder"
|
placeholder="amount"
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
/>
|
||||||
<TextArea status={status} variant="filled" placeholder="Filled TextArea Placeholder" rows={2} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
</section>
|
||||||
<div className="text-gray-500 ">With Prefix and Suffix</div>
|
|
||||||
<div className="space-y-2">
|
{/* --- SECTION 3: Component States & Validation --- */}
|
||||||
<Input
|
|
||||||
status={status}
|
<section className="space-y-6">
|
||||||
placeholder="Outlined Placeholder"
|
<h2 className="text-lg font-bold text-gray-800 border-b pb-2">Component States & Validation</h2>
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
/>
|
{/* COLUMN 1: DISABLED (Semua Component) */}
|
||||||
<InputNumber
|
<div className="space-y-3">
|
||||||
status={status}
|
<Label>Disabled State</Label>
|
||||||
className="w-full"
|
<div className="p-4 bg-gray-50 rounded-lg space-y-3 border border-dashed border-gray-200">
|
||||||
placeholder="OutlinedNumber Placeholder"
|
<Input disabled placeholder="Disabled Input" prefix={<Fix>🚫</Fix>} />
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
<InputPassword disabled placeholder="Disabled Password" />
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
<InputNumber disabled className="w-full" placeholder="0" />
|
||||||
/>
|
<InputCurrency disabled className="w-full" placeholder="Rp 0" />
|
||||||
<TextArea status={status} placeholder="Outlined TextArea Placeholder" rows={2} />
|
<TextArea disabled placeholder="Disabled TextArea" rows={2} />
|
||||||
<Input
|
|
||||||
status={status}
|
|
||||||
variant="filled"
|
|
||||||
placeholder="Filled Placeholder"
|
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
status={status}
|
|
||||||
variant="filled"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="Filled Number Placeholder"
|
|
||||||
prefix={<span className="text-gray-500">$</span>}
|
|
||||||
suffix={<span className="text-gray-500">$</span>}
|
|
||||||
/>
|
|
||||||
<TextArea status={status} variant="filled" placeholder="Filled TextArea Placeholder" rows={2} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* COLUMN 2: READ ONLY (Semua Component) */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Read Only State</Label>
|
||||||
|
<div className="p-4 bg-gray-50 rounded-lg space-y-3 border border-dashed border-gray-200">
|
||||||
|
<Input readOnly value="Read only text" prefix={<Fix>📖</Fix>} />
|
||||||
|
<InputPassword readOnly value="secretpassword" />
|
||||||
|
<InputNumber readOnly className="w-full" value={12345} />
|
||||||
|
<InputCurrency readOnly className="w-full" value={50000} />
|
||||||
|
<TextArea readOnly value="This content is read only and cannot be edited." rows={2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* COLUMN 3: ERROR OUTLINED */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Error (Outlined)</Label>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Input status="error" placeholder="Invalid input" />
|
||||||
|
<InputPassword status="error" placeholder="Wrong password" />
|
||||||
|
<InputNumber status="error" className="w-full" placeholder="Out of range" />
|
||||||
|
<InputCurrency status="error" className="w-full" placeholder="Invalid amount" />
|
||||||
|
<TextArea status="error" placeholder="Error in message" rows={2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* COLUMN 4: ERROR FILLED */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Error (Filled)</Label>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Input variant="filled" status="error" placeholder="Invalid input" />
|
||||||
|
<InputPassword variant="filled" status="error" placeholder="Wrong password" />
|
||||||
|
<InputNumber variant="filled" status="error" className="w-full" placeholder="Out of range" />
|
||||||
|
<InputCurrency variant="filled" status="error" className="w-full" placeholder="Invalid amount" />
|
||||||
|
<TextArea variant="filled" status="error" placeholder="Error in message" rows={2} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,69 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import GroupComponent from './group-component';
|
import GroupComponent from './group-component';
|
||||||
|
import { InputStatusType } from '@repo/ui';
|
||||||
|
|
||||||
export default function InputShowCase() {
|
export default function InputShowCase() {
|
||||||
|
// State untuk menyimpan status yang dipilih
|
||||||
|
const [selectedStatus, setSelectedStatus] = useState<InputStatusType>('normal');
|
||||||
|
|
||||||
|
const statuses: { label: string; value: InputStatusType; color: string }[] = [
|
||||||
|
{ label: 'Normal', value: 'normal', color: 'bg-gray-500' },
|
||||||
|
{ label: 'Error', value: 'error', color: 'bg-red-500' },
|
||||||
|
{ label: 'Warning', value: 'warning', color: 'bg-amber-500' },
|
||||||
|
{ label: 'Success', value: 'success', color: 'bg-emerald-500' },
|
||||||
|
{ label: 'Info', value: 'info', color: 'bg-blue-500' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen ">
|
<div className="min-h-screen bg-gray-50/50">
|
||||||
<main className="mx-auto p-8 space-y-12">
|
<main className="mx-auto p-8 space-y-8">
|
||||||
<section className="space-y-6">
|
{/* HEADER & SELECTOR */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-2xl border border-gray-200 shadow-sm">
|
||||||
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="text-lg font-bold">1. All Conditions Matrix</h2>
|
<h2 className="text-xl font-bold text-gray-800">Input Components</h2>
|
||||||
<span className="px-2 py-0.5 text-[10px] bg-gray-200 text-gray-600 rounded uppercase tracking-wider">
|
<span className="px-2 py-0.5 text-[10px] bg-blue-100 text-blue-600 rounded-full uppercase tracking-wider font-bold">
|
||||||
Visual Audit
|
Interactive Lab
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Pilih status untuk melihat perubahan validasi pada semua tipe input.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6">
|
{/* TAB SELECTOR */}
|
||||||
{/* COLUMN: ERROR */}
|
<div className="flex bg-gray-100 p-1 rounded-xl w-fit border border-gray-200">
|
||||||
<GroupComponent status="error" />
|
{statuses.map((item) => (
|
||||||
{/* COLUMN: NORMAL */}
|
<button
|
||||||
<GroupComponent />
|
key={item.value}
|
||||||
{/* COLUMN: INFO */}
|
onClick={() => setSelectedStatus(item.value)}
|
||||||
<GroupComponent status="info" />
|
className={`
|
||||||
{/* COLUMN: WARNING */}
|
px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200
|
||||||
<GroupComponent status="warning" />
|
${
|
||||||
{/* COLUMN: SUCCESS */}
|
selectedStatus === item.value
|
||||||
<GroupComponent status="success" />
|
? 'bg-white text-gray-900 shadow-sm'
|
||||||
|
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`w-2 h-2 rounded-full ${item.color}`} />
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RENDER COMPONENT */}
|
||||||
|
<section className="animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||||
|
<div className="relative">
|
||||||
|
{/* Label Indikator Status Aktif */}
|
||||||
|
<div className="absolute -top-3 left-8 px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest bg-white border border-gray-200 shadow-sm z-10">
|
||||||
|
Active Status: <span className="text-blue-600">{selectedStatus}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<GroupComponent status={selectedStatus} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export * from './form-provider';
|
|||||||
|
|
||||||
export * from './input/input.component';
|
export * from './input/input.component';
|
||||||
export * from './input/input-password.component';
|
export * from './input/input-password.component';
|
||||||
|
export * from './input/input-currency.component';
|
||||||
export * from './input/input-number.component';
|
export * from './input/input-number.component';
|
||||||
|
|
||||||
export * from './textarea/textarea.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 { InputPasswordProps } from '../types';
|
||||||
import { Input } from './input.component';
|
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) => {
|
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;
|
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
|
VARIANT: FILLED
|
||||||
==================== */
|
==================== */
|
||||||
|
|||||||
@@ -43,12 +43,13 @@ interface BaseComponentProps {
|
|||||||
* Define specific component props
|
* Define specific component props
|
||||||
*/
|
*/
|
||||||
export interface InputProps extends RcInputProps, BaseComponentProps {}
|
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 InputNumberProps extends RcInputNumberProps, BaseComponentProps {}
|
||||||
|
export interface InputCurrencyProps extends InputNumberProps, BaseComponentProps {
|
||||||
|
prefix?: string; // Optional: Custom prefix, default 'Rp '
|
||||||
|
}
|
||||||
|
|
||||||
export interface TextAreaProps extends RcTextAreaProps, BaseComponentProps {}
|
export interface TextAreaProps extends RcTextAreaProps, BaseComponentProps {}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
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);
|
color: var(--color-gray-800);
|
||||||
@apply text-base; /* Set global base font to 13px */
|
@apply text-base; /* Set global base font to 13px */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|||||||
Reference in New Issue
Block a user