chore: Delete various UI components, form elements, and associated example/showcase pages from the UI package and web app.

This commit is contained in:
Firman Ramdhani
2026-02-05 10:08:12 +07:00
parent 660a9ace20
commit 24e0d2ca61
45 changed files with 3 additions and 6612 deletions
@@ -1,15 +0,0 @@
import { ReactNode } from 'react';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
}
export function Button({ children, ...other }: ButtonProps): ReactNode {
return (
<button type="button" {...other}>
{children}
</button>
);
}
Button.displayName = 'Button';
@@ -1,17 +0,0 @@
import React, { useState } from 'react';
export const CounterExample: React.FC = () => {
const [count, setCount] = useState(0);
return (
<div className="py-4">
<div className="text-2xl font-semibold">tailwind on package UI</div>
<div className="bg-red-400 text-3xl font-bold underline">100</div>
<div className="mt-3 bg-brand-500 text-brand-800">sdsa</div>
<div className="text-5xl text-green-500">sada</div>
<button id="counter" type="button" onClick={() => setCount(count + 1)}>
{count}
</button>
</div>
);
};
@@ -1,337 +0,0 @@
import { DateUtils } from '@repo/utils';
import React, { useState, useEffect, useMemo } from 'react';
export const DateServiceExample: React.FC = () => {
// --- 0. TIMEZONE CONFIGURATION (ROOT CONTROL) ---
const [currentZone, setCurrentZone] = useState<string>(DateUtils.getGlobalTimezone());
// Ambil list timezone dari helper static yang kita buat sebelumnya
const timezoneList = useMemo(() => DateUtils.getSupportedTimezones(), []);
// --- 1. STATE & DATA ---
// MOCK DATA dipindahkan ke sini & pakai useMemo
// Agar saat timezone ganti, tasks ini di-regenerate dengan waktu "Now" versi timezone baru.
const tasks = useMemo(() => {
return [
{
id: 1,
title: 'Bayar Server (Lusa)',
dueDate: DateUtils.now().add(2, 'day').toISOString(),
},
{
id: 2,
title: 'Laporan Bulanan (Lewat 5 Hari)',
dueDate: DateUtils.now().subtract(5, 'day').toISOString(),
},
{
id: 3,
title: 'Meeting Tahunan (Bulan Depan)',
dueDate: DateUtils.now().add(1, 'month').toISOString(),
},
{
id: 4,
title: 'Deadline Besok Pagi',
dueDate: DateUtils.now().add(1, 'day').startOf('day').toISOString(),
},
];
}, [currentZone]); // Dependency: currentZone
const [currentTime, setCurrentTime] = useState<DateUtils>(DateUtils.now());
const [selectedDate, setSelectedDate] = useState<DateUtils>(DateUtils.now());
// State demo logic calendar
const [calendarTarget, setCalendarTarget] = useState(() => DateUtils.now().add(1, 'day').startOf('day'));
// Static date (Memo kosong) -> Akan tetap di Timezone saat pertama kali load (kecuali di-refresh)
// Ini bagus untuk demo bahwa object lama tidak berubah (immutable) kecuali dibuat ulang.
const staticDate = useMemo(() => new DateUtils(), []);
// Effect update jam tiap detik
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(DateUtils.now());
}, 1000);
return () => clearInterval(timer);
}, []);
// --- HANDLER GANTI TIMEZONE ---
const handleZoneChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newZone = e.target.value;
setCurrentZone(newZone);
// 1. Set Global Config (Inti Perubahan)
DateUtils.setGlobalConfig(newZone);
// 2. Force Refresh State agar UI langsung berubah
// Kita buat instance baru agar mengambil config timezone terbaru
setCurrentTime(DateUtils.now());
// Kita recreate selectedDate dengan nilai raw yang sama, tapi context timezone baru
setSelectedDate((prev) => new DateUtils(prev.getRaw()));
// Recreate target demo calendar
setCalendarTarget(DateUtils.now().add(1, 'day').startOf('day'));
};
const handleAddDay = (days: number) => {
setSelectedDate((prev) => prev.add(days, 'day'));
};
return (
<div className="p-8 max-w-5xl mx-auto bg-gray-50 min-h-screen font-sans text-gray-800">
{/* HEADER & TIMEZONE CONTROLLER */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4 border-b border-gray-200 pb-6">
<h1 className="text-3xl font-bold text-blue-900">DateUtils Feature Showcase</h1>
<div className="bg-white p-3 rounded-lg shadow-sm border border-blue-100 flex items-center gap-3">
<label htmlFor="tz-switcher" className="text-xs font-bold text-gray-500 uppercase tracking-wide">
App Timezone:
</label>
<select
id="tz-switcher"
value={currentZone}
onChange={handleZoneChange}
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded focus:ring-blue-500 focus:border-blue-500 block p-1.5 outline-none font-mono"
>
{timezoneList.map((tz) => (
<option key={tz} value={tz}>
{tz}
</option>
))}
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* --- CARD 1: REALTIME CLOCK --- */}
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200 flex flex-col justify-between">
<div>
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Realtime Clock</h2>
<div className="flex flex-col">
<div className="text-4xl font-mono font-bold text-gray-900">{currentTime.format('HH:mm:ss')}</div>
<div className="flex items-center gap-2 mt-2">
{/* UPDATE DI SINI: Gunakan .timezoneAbbr */}
<div className="text-sm font-bold text-blue-700 bg-blue-100 px-2 py-1 rounded border border-blue-200">
{/* Ini akan output "WIB", "WITA", atau "EST" */}
{currentTime.timezoneAbbr}
{/* Ini offset angka (+07:00) */}
<span className="opacity-60 text-xs ml-1">({currentTime.format('Z')})</span>
</div>
</div>
<div className="text-sm font-bold text-blue-700 bg-blue-100 px-2 py-1 rounded border border-blue-200">
{currentTime.timezoneWithOffset}
</div>
</div>
<div className="text-lg text-gray-500 mt-2">{currentTime.format('dddd, DD MMMM YYYY')}</div>
</div>
<div className="mt-4 text-xs bg-gray-100 p-2 rounded text-gray-500 font-mono break-all">
ISO (UTC): {currentTime.toISOString()}
</div>
</div>
{/* --- CARD 2: EPOCH & TIMESTAMPS --- */}
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">Epoch / Timestamp Data</h2>
<div className="space-y-4">
{/* STATIC SECTION */}
<div className="bg-blue-50 p-3 rounded-lg border border-blue-100">
<div className="text-xs font-bold text-blue-800 mb-1">STATIC (Load Once)</div>
<div className="text-xs text-blue-600 mb-2">Captured on mount (keeps original timezone).</div>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Millis</div>
<div className="font-mono font-bold text-gray-700">{staticDate.epochMillis}</div>
</div>
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Seconds</div>
<div className="font-mono font-bold text-gray-700">{staticDate.epochSeconds}</div>
</div>
</div>
</div>
{/* LIVE SECTION */}
<div className="bg-green-50 p-3 rounded-lg border border-green-100">
<div className="text-xs font-bold text-green-800 mb-1">LIVE (Reactive)</div>
<div className="text-xs text-green-600 mb-2">Updated every second (follows dropdown).</div>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Millis</div>
<div className="font-mono font-bold text-gray-700">{new DateUtils().epochMillis}</div>
</div>
<div>
<div className="text-[10px] uppercase text-gray-500">Epoch Seconds</div>
<div className="font-mono font-bold text-gray-700">{new DateUtils().epochSeconds}</div>
</div>
</div>
</div>
</div>
</div>
{/* --- CARD 3: CALENDAR LOGIC DEMO --- */}
<div className="bg-indigo-50 p-6 rounded-xl shadow-sm border border-indigo-200 md:col-span-2">
<h2 className="text-lg font-bold text-indigo-900 mb-4 flex items-center gap-2">
🗓 Calendar Logic Demo (The "Zero Day" Issue)
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="text-sm text-indigo-800 space-y-2">
<p>
Compare <strong>Current Time</strong> vs <strong>Tomorrow 00:00</strong>.
</p>
<div className="bg-white/50 p-2 rounded">
<div className="flex justify-between text-xs">
<span>Now ({currentZone}):</span>
<span className="font-mono font-bold">{currentTime.format('HH:mm')}</span>
</div>
<div className="flex justify-between text-xs mt-1">
<span>Target (Start of Day):</span>
<span className="font-mono font-bold">{calendarTarget.format('DD MMM HH:mm')}</span>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg shadow-sm border-l-4 border-red-400">
<div className="text-xs text-gray-400 uppercase font-bold">Standard .diff('day')</div>
<div className="text-2xl font-bold text-red-600 mt-1">
{calendarTarget.diff(currentTime, 'day')}{' '}
<span className="text-sm font-normal text-gray-500">Days</span>
</div>
<p className="text-xs text-red-500 mt-1">Hasil 0 jika jarak &lt; 24 jam.</p>
</div>
<div className="bg-white p-4 rounded-lg shadow-sm border-l-4 border-green-500">
<div className="text-xs text-gray-400 uppercase font-bold">New .diffCalendarDay()</div>
<div className="text-2xl font-bold text-green-600 mt-1">
{calendarTarget.diffCalendarDay(currentTime)}{' '}
<span className="text-sm font-normal text-gray-500">Days</span>
</div>
<p className="text-xs text-green-600 mt-1">Hasil 1. Benar secara kalender.</p>
</div>
</div>
</div>
{/* --- CARD 4: MANIPULATION --- */}
<div className="bg-white p-6 rounded-xl shadow-sm border border-gray-200 md:col-span-2">
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
Interactive Manipulation
</h2>
<div className="flex flex-col md:flex-row items-center gap-4 bg-gray-50 p-4 rounded-lg mb-4">
<button
onClick={() => handleAddDay(-1)}
className="px-4 py-2 bg-white border border-gray-300 shadow-sm text-gray-700 rounded hover:bg-gray-100 transition font-medium"
>
- 1 Day
</button>
<div className="flex-1 text-center">
<span className="block text-3xl font-bold text-blue-900">{selectedDate.format('DD MMMM YYYY')}</span>
<span className="text-sm text-blue-600 font-medium">Selected Date State</span>
<span className="text-xs text-gray-400 block mt-1">Offset: {selectedDate.format('Z')}</span>
</div>
<button
onClick={() => handleAddDay(1)}
className="px-4 py-2 bg-white border border-gray-300 shadow-sm text-gray-700 rounded hover:bg-gray-100 transition font-medium"
>
+ 1 Day
</button>
</div>
<div className="grid grid-cols-2 gap-4 text-center">
<div className="bg-gray-100 p-2 rounded">
<span className="text-xs text-gray-500 block">Precise Diff (Float)</span>
<span className="font-mono font-bold text-gray-700">
{selectedDate.diff(currentTime, 'day', true).toFixed(2)} days
</span>
</div>
<div className="bg-gray-100 p-2 rounded">
<span className="text-xs text-gray-500 block">Calendar Diff (Int)</span>
<span className="font-mono font-bold text-gray-700">
{selectedDate.diffCalendarDay(currentTime)} days
</span>
</div>
</div>
</div>
</div>
{/* --- CARD 5: TASK LIST (DYNAMIC) --- */}
<div className="mt-8 bg-white p-6 rounded-xl shadow-sm border border-gray-200">
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
Task List Implementation (Timezone Aware)
</h2>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="text-xs text-gray-500 border-b border-gray-100 bg-gray-50/50">
<th className="py-3 px-4 rounded-tl-lg">Task Name</th>
<th className="py-3 px-4">Due Date</th>
<th className="py-3 px-4">Status Badge</th>
<th className="py-3 px-4 rounded-tr-lg">Countdown</th>
</tr>
</thead>
<tbody>
{tasks.map((task) => {
const dueDate = new DateUtils(task.dueDate);
const now = DateUtils.now();
const isOverdue = now.isAfter(dueDate);
const daysDiff = dueDate.diffCalendarDay(now);
const isDueSoon = !isOverdue && daysDiff <= 3;
let statusBadge;
const rowClass = 'border-b border-gray-50 hover:bg-gray-50 transition-colors';
if (isOverdue) {
statusBadge = (
<span className="px-2 py-1 text-[10px] font-bold text-red-700 bg-red-100 rounded-full border border-red-200">
OVERDUE
</span>
);
} else if (isDueSoon) {
statusBadge = (
<span className="px-2 py-1 text-[10px] font-bold text-amber-700 bg-amber-100 rounded-full border border-amber-200">
DUE SOON
</span>
);
} else {
statusBadge = (
<span className="px-2 py-1 text-[10px] font-bold text-emerald-700 bg-emerald-100 rounded-full border border-emerald-200">
ACTIVE
</span>
);
}
return (
<tr key={task.id} className={rowClass}>
<td className="py-3 px-4 font-medium text-gray-700">{task.title}</td>
<td className="py-3 px-4 text-gray-500 font-mono text-sm">
{dueDate.format('DD MMM YYYY HH:mm')}
<span className="text-[10px] text-gray-400 ml-1">{dueDate.format('Z')}</span>
</td>
<td className="py-3 px-4">{statusBadge}</td>
<td className="py-3 px-4 text-sm text-gray-600 font-medium">
{isOverdue ? (
`${Math.abs(daysDiff)} days late`
) : daysDiff === 0 ? (
<span className="text-amber-600">Today!</span>
) : (
`${daysDiff} days left`
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);
};
@@ -1,179 +0,0 @@
import { EncryptionUtils } from '@repo/utils';
import React, { useState } from 'react';
export const EncryptionExample: React.FC = () => {
// --- 1. SETUP INSTANCE ---
// Kita gunakan Singleton agar hemat memory
const cryptoService = EncryptionUtils.getInstance();
// --- 2. STATE ---
const [plainText, setPlainText] = useState<string>('');
const [encryptedResult, setEncryptedResult] = useState<string>('');
const [decryptedResult, setDecryptedResult] = useState<string>('');
// State untuk demo LocalStorage
const [storageKey] = useState<string>('my_secure_token');
const [storageValue, setStorageValue] = useState<string>('');
const [savedStatus, setSavedStatus] = useState<string>('');
// --- 3. HANDLERS ---
const handleEncrypt = () => {
// Panggil method encrypt dari class
const result = cryptoService.encrypt(plainText);
setEncryptedResult(result);
// Reset decrypt result biar user harus klik decrypt lagi untuk validasi
setDecryptedResult('');
};
const handleDecrypt = () => {
// Panggil method decrypt dari class
const original = cryptoService.decrypt(encryptedResult);
setDecryptedResult(original);
};
// --- DEMO LOCALSTORAGE ---
const saveToStorage = () => {
try {
// Encrypt sebelum simpan
const secureData = cryptoService.encrypt(storageValue);
localStorage.setItem(storageKey, secureData);
setSavedStatus(`Saved securely! Cipher: ${secureData.substring(0, 15)}...`);
} catch (error) {
setSavedStatus('Error saving data');
}
};
const loadFromStorage = () => {
try {
const storedData = localStorage.getItem(storageKey);
if (!storedData) {
setSavedStatus('No data found in storage');
return;
}
// Decrypt setelah ambil
const originalData = cryptoService.decrypt(storedData);
setSavedStatus(`Loaded & Decrypted: "${originalData}"`);
} catch (error) {
setSavedStatus('Failed to decrypt storage data');
}
};
return (
<div className="p-8 max-w-4xl mx-auto bg-slate-50 min-h-screen font-sans text-slate-800">
<h1 className="text-3xl font-bold mb-8 text-slate-900">Encryption Service Demo</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* === SECTION 1: INTERACTIVE PLAYGROUND === */}
<div className="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h2 className="text-lg font-bold text-indigo-600 mb-4 flex items-center gap-2">🔐 Live Playground</h2>
{/* INPUT */}
<div className="mb-4">
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Secret Message (Input)</label>
<input
type="text"
value={plainText}
onChange={(e) => setPlainText(e.target.value)}
placeholder="Type sensitive data here..."
className="w-full p-2 border border-slate-300 rounded focus:ring-2 focus:ring-indigo-500 outline-none"
/>
</div>
{/* ACTIONS */}
<div className="flex gap-2 mb-6">
<button
onClick={handleEncrypt}
className="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 transition text-sm font-semibold"
>
Encrypt
</button>
<button
onClick={handleDecrypt}
disabled={!encryptedResult}
className="bg-emerald-600 text-white px-4 py-2 rounded hover:bg-emerald-700 transition text-sm font-semibold disabled:opacity-50"
>
Decrypt
</button>
</div>
{/* OUTPUT: ENCRYPTED */}
<div className="mb-4">
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">
Encrypted Output (Ciphertext)
</label>
<textarea
readOnly
value={encryptedResult}
className="w-full p-2 bg-slate-100 border border-slate-200 rounded text-xs font-mono text-slate-600 h-24 break-all"
placeholder="Result will appear here..."
/>
</div>
{/* OUTPUT: DECRYPTED */}
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Verified Decryption</label>
<div
className={`p-2 border rounded min-h-[40px] flex items-center ${
decryptedResult ? 'bg-green-50 border-green-200 text-green-800' : 'bg-slate-50 border-slate-200'
}`}
>
{decryptedResult || <span className="text-slate-400 italic text-sm">Waiting for decryption...</span>}
</div>
</div>
</div>
{/* === SECTION 2: SECURE LOCALSTORAGE === */}
<div className="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<h2 className="text-lg font-bold text-amber-600 mb-4 flex items-center gap-2">💾 Secure LocalStorage</h2>
<p className="text-sm text-slate-600 mb-4">
Simulasi menyimpan token auth. Data di browser storage akan terlihat acak, tapi aplikasi bisa membacanya
kembali.
</p>
<div className="space-y-4">
<div>
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Data to Save</label>
<input
type="text"
value={storageValue}
onChange={(e) => setStorageValue(e.target.value)}
placeholder="e.g., Bearer eyJhbGci..."
className="w-full p-2 border border-slate-300 rounded focus:ring-2 focus:ring-amber-500 outline-none"
/>
</div>
<div className="flex gap-2">
<button
onClick={saveToStorage}
className="bg-slate-800 text-white px-4 py-2 rounded hover:bg-slate-900 transition text-sm font-semibold flex-1"
>
Save Encrypted
</button>
<button
onClick={loadFromStorage}
className="bg-white border border-slate-300 text-slate-700 px-4 py-2 rounded hover:bg-slate-50 transition text-sm font-semibold flex-1"
>
Load & Decrypt
</button>
</div>
{/* STATUS MONITOR */}
<div className="mt-4 bg-amber-50 border border-amber-100 p-3 rounded">
<span className="text-xs font-bold text-amber-800 uppercase block mb-1">Log / Status</span>
<code className="text-xs text-amber-900 block break-all">{savedStatus || 'Ready...'}</code>
</div>
<div className="mt-4 pt-4 border-t border-slate-100">
<p className="text-xs text-slate-400">
<strong>Tips:</strong> Buka <em>Developer Tools &gt; Application &gt; LocalStorage</em>. Cari key{' '}
<code>my_secure_token</code>. Kamu akan melihat string acak (encrypted), bukan teks asli.
</p>
</div>
</div>
</div>
</div>
</div>
);
};
@@ -1,13 +0,0 @@
import React from 'react';
interface HeaderExampleProps {
title: string;
}
export const HeaderExample: React.FC<HeaderExampleProps> = ({ title }) => {
return (
<header id="header">
<h1>{title}</h1>
</header>
);
};
@@ -1,208 +0,0 @@
import React from 'react';
// --- SHARED SUB-COMPONENTS ---
const Card = ({
title,
children,
className = '',
}: {
title?: string;
children: React.ReactNode;
className?: string;
}) => (
<div className={`bg-white border border-gray-200 rounded-lg shadow-sm ${className}`}>
{title && (
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50/50">
<h3 className="text-lg font-semibold text-gray-800">{title}</h3>
</div>
)}
<div className="p-5">{children}</div>
</div>
);
const Badge = ({
children,
variant = 'info',
}: {
children: React.ReactNode;
variant?: 'success' | 'error' | 'warning' | 'info';
}) => {
const styles = {
success: 'bg-green-50 text-green-600 border-green-100',
error: 'bg-red-50 text-red-600 border-red-100',
warning: 'bg-amber-50 text-amber-600 border-amber-100',
info: 'bg-brand-50 text-brand-600 border-brand-100',
};
return (
<span className={`px-2 py-0.5 rounded-sm text-xs font-bold border ${styles[variant]} uppercase tracking-wider`}>
{children}
</span>
);
};
// --- MAIN PAGE COMPONENT ---
export function StyleGuideOne() {
return (
<div className="min-h-screen bg-gray-50 text-gray-700 font-sans">
{/* 1. OBJECT HEADER (SAP Pattern) */}
<div className="bg-white border-b border-gray-200 px-8 py-6 sticky top-0 z-10 shadow-sm">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<nav className="text-xs font-bold text-brand-600 uppercase tracking-widest mb-1">
Procurement / Purchase Orders
</nav>
<h1 className="text-xl font-bold text-gray-900 flex items-center gap-3">
PO #4500019283
<Badge variant="success">Released</Badge>
</h1>
</div>
<div className="flex items-center gap-2">
<button className="px-4 py-1.5 border border-gray-300 rounded-md bg-white hover:bg-gray-50 font-medium transition-all shadow-sm">
Reject
</button>
<button className="px-4 py-1.5 bg-brand-500 text-white rounded-md hover:bg-brand-600 font-medium transition-all shadow-sm">
Approve Order
</button>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto p-6 space-y-8">
{/* 2. ANALYTICS / KPI SECTION */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[
{ label: 'Total Value', val: '$12,450.00', color: 'text-gray-900' },
{ label: 'Items Count', val: '24 SKUs', color: 'text-gray-900' },
{ label: 'Days Overdue', val: '5 Days', color: 'text-red-500' },
{ label: 'Vendor Rating', val: '4.8 / 5.0', color: 'text-green-600' },
].map((kpi, i) => (
<div key={i} className="bg-white p-4 rounded-lg border border-gray-200 shadow-sm">
<p className="text-xs font-bold text-gray-500 uppercase tracking-wider">{kpi.label}</p>
<p className={`text-xl font-bold mt-1 ${kpi.color}`}>{kpi.val}</p>
</div>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* LEFT: FORM & CONTROLS */}
<div className="lg:col-span-1 space-y-6">
<Card title="General Information">
<div className="space-y-4">
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-500">Vendor Name</label>
<input
type="text"
readOnly
className="bg-gray-50 border border-gray-200 rounded-md px-3 py-1.5 text-gray-600 outline-none cursor-not-allowed"
defaultValue="Steel Global Corp"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-500">Shipping Method</label>
<select className="border border-gray-300 rounded-md px-3 py-1.5 bg-white focus:ring-1 focus:ring-brand-500 outline-none">
<option>Sea Freight - Standard</option>
<option>Air Freight - Express</option>
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-medium text-gray-500">Order Priority</label>
<div className="flex gap-4 mt-1">
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="prio" className="accent-brand-500" />{' '}
<span className="text-base">Normal</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input type="radio" name="prio" className="accent-brand-500" defaultChecked />{' '}
<span className="text-base">High</span>
</label>
</div>
</div>
</div>
</Card>
<Card title="Order Status Logs">
<ul className="space-y-4">
{[
{ time: '10:00 AM', msg: 'Order Created', user: 'System' },
{ time: '11:30 AM', msg: 'Manager Approved', user: 'Budi Santoso' },
].map((log, i) => (
<li key={i} className="flex gap-3 border-l-2 border-brand-200 pl-4 relative">
<div className="absolute w-2.5 h-2.5 bg-brand-500 rounded-full -left-[6px] top-1"></div>
<div>
<p className="text-base font-medium text-gray-800">{log.msg}</p>
<p className="text-xs text-gray-500">
{log.time} By {log.user}
</p>
</div>
</li>
))}
</ul>
</Card>
</div>
{/* RIGHT: DATA TABLE (The Core of ERP) */}
<div className="lg:col-span-2">
<Card className="!p-0 overflow-hidden">
<div className="p-4 border-b border-gray-100 flex justify-between items-center">
<h3 className="text-lg font-semibold text-gray-800">Line Items</h3>
<div className="flex gap-2">
<button className="text-xs font-bold text-brand-600 hover:bg-brand-50 px-2 py-1 rounded">
EXPORT CSV
</button>
<button className="text-xs font-bold text-brand-600 hover:bg-brand-50 px-2 py-1 rounded">
PRINT PO
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-gray-100/80">
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase">Material</th>
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase">Description</th>
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Qty</th>
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Price</th>
<th className="px-4 py-2 text-xs font-bold text-gray-500 uppercase text-right">Total</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 text-base">
{[1, 2, 3, 4, 5].map((item) => (
<tr key={item} className="hover:bg-brand-50/20 transition-all cursor-pointer group">
<td className="px-4 py-2 font-mono text-brand-600 group-hover:underline">M-1000{item}</td>
<td className="px-4 py-2 text-gray-800">
Industrial Steel Pipe 2.5" - Grade {item === 2 ? 'B' : 'A'}
</td>
<td className="px-4 py-2 text-right">100.00</td>
<td className="px-4 py-2 text-right">150.00</td>
<td className="px-4 py-2 text-right font-semibold text-gray-900">15,000.00</td>
</tr>
))}
</tbody>
<tfoot className="bg-gray-50/80 font-bold border-t-2 border-gray-200">
<tr>
<td colSpan={4} className="px-4 py-3 text-right text-gray-500 uppercase text-xs tracking-widest">
Grand Total (Net)
</td>
<td className="px-4 py-3 text-right text-brand-600 text-lg">$ 75,000.00</td>
</tr>
</tfoot>
</table>
</div>
</Card>
<div className="mt-4 bg-amber-50 border border-amber-100 rounded-lg p-4 flex gap-3">
<span className="text-xl">⚠️</span>
<div>
<p className="text-base font-bold text-amber-800">Budget Warning</p>
<p className="text-sm text-amber-700">
This purchase order exceeds the quarterly budget for Department "Steel Fabrication" by 12%.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
@@ -1,257 +0,0 @@
import { useState } from 'react';
// ==========================================================
// 1. ATOMIC COMPONENTS (Berdasar Config 13px & Burgundy)
// ==========================================================
const Card = ({ title, children, className = '', footer }: any) => (
<div className={`bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden ${className}`}>
{title && (
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
<h3 className="text-lg font-semibold text-gray-800 tracking-tight">{title}</h3>
</div>
)}
<div className="p-5 text-base">{children}</div>
{footer && <div className="px-4 py-3 bg-gray-50 border-t border-gray-100">{footer}</div>}
</div>
);
const Button = ({ children, variant = 'primary', className = '', ...props }: any) => {
const styles: any = {
primary: 'bg-brand-500 text-white hover:bg-brand-600 shadow-sm border border-brand-600',
secondary: 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 shadow-xs',
ghost: 'text-gray-600 hover:bg-gray-100',
danger: 'bg-red-500 text-white hover:bg-red-600',
};
return (
<button
className={`px-3 py-1.5 rounded-md font-medium text-base transition-all active:scale-95 disabled:opacity-50 ${styles[variant]} ${className}`}
{...props}
>
{children}
</button>
);
};
const Badge = ({ children, variant = 'info' }: any) => {
const styles: any = {
success: 'bg-green-50 text-green-600 border-green-100',
error: 'bg-red-50 text-red-600 border-red-100',
warning: 'bg-amber-50 text-amber-600 border-amber-100',
info: 'bg-brand-50 text-brand-700 border-brand-100',
};
return (
<span className={`px-2 py-0.5 rounded-sm text-xs font-bold border ${styles[variant]} uppercase tracking-wider`}>
{children}
</span>
);
};
// ==========================================================
// 2. MAIN PAGE: ADVANCED STYLE GUIDE
// ==========================================================
export function StyleGuideTwo() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="flex min-h-screen bg-gray-50 font-sans text-gray-800">
{/* SIDE NAVIGATION */}
<aside className="w-64 bg-brand-950 text-white flex flex-col sticky top-0 h-screen shadow-xl shrink-0">
<div className="p-6 border-b border-brand-900 flex items-center gap-3">
<div className="w-8 h-8 bg-brand-500 rounded-lg flex items-center justify-center font-bold text-xl shadow-inner">
B
</div>
<span className="font-bold text-lg tracking-tight">BURGUNDY_ERP</span>
</div>
<nav className="flex-1 p-4 space-y-1">
<p className="text-brand-300 text-[10px] font-bold uppercase tracking-widest px-2 mb-2 opacity-60">
Finance Modules
</p>
<div className="flex items-center gap-3 px-3 py-2 rounded-md bg-brand-500 text-white shadow-lg cursor-pointer">
<span>📓</span> <span className="text-base font-medium">General Ledger</span>
</div>
<div className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-brand-900 text-brand-100 cursor-pointer transition-colors">
<span>📊</span> <span className="text-base font-medium">Cost Analysis</span>
</div>
<div className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-brand-900 text-brand-100 cursor-pointer transition-colors">
<span>🏦</span> <span className="text-base font-medium">Bank Reconciliation</span>
</div>
</nav>
</aside>
{/* MAIN CONTENT AREA */}
<main className="flex-1 flex flex-col min-w-0">
{/* TOP BAR */}
<header className="h-14 bg-white border-b border-gray-200 flex items-center justify-between px-8 shrink-0 shadow-sm">
<div className="text-sm text-gray-500 font-medium">
Master Data <span className="mx-2">/</span>{' '}
<span className="text-gray-900 font-bold">Chart of Accounts</span>
</div>
<div className="flex items-center gap-4">
<div className="w-8 h-8 rounded-full bg-brand-100 border border-brand-200 flex items-center justify-center text-brand-700 font-bold text-xs">
JD
</div>
</div>
</header>
{/* CONTENT */}
<div className="p-8 space-y-8 overflow-y-auto">
{/* HEADER SECTION */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-gray-900">Account Overview: 110200</h2>
<p className="text-base text-gray-500 mt-1">
Domestic Trade Accounts Receivable {' '}
<span className="text-brand-600 font-semibold italic">Local Currency (IDR)</span>
</p>
</div>
<div className="flex gap-2">
<Button variant="secondary" onClick={() => window.print()}>
Print Statement
</Button>
<Button onClick={() => setIsModalOpen(true)}>Create Entry</Button>
</div>
</div>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* LEFT: MAIN DATA TABLE */}
<div className="xl:col-span-2 space-y-6">
<Card title="Recent Transactions">
<div className="overflow-x-auto -mx-5 -my-5">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-gray-50 border-b border-gray-100">
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase">Document</th>
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase">Partner</th>
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-right">Debit</th>
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-right">Credit</th>
<th className="px-5 py-3 text-xs font-bold text-gray-500 uppercase text-center">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{[1, 2, 3, 4].map((i) => (
<tr key={i} className="hover:bg-brand-50/20 text-base transition-colors group">
<td className="px-5 py-3 font-mono text-brand-700 font-semibold">INV-2026-00{i}</td>
<td className="px-5 py-3">
<p className="font-medium text-gray-800">Global Tech Solutions</p>
<p className="text-xs text-gray-400">Vendor ID: V-9920</p>
</td>
<td className="px-5 py-3 text-right font-semibold">$ {i * 125}.00</td>
<td className="px-5 py-3 text-right text-gray-300">0.00</td>
<td className="px-5 py-3 text-center">
<Badge variant={i % 2 === 0 ? 'success' : 'warning'}>
{i % 2 === 0 ? 'Cleared' : 'Pending'}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card title="Budget Utilization">
<div className="space-y-4">
<div className="w-full bg-gray-100 rounded-full h-3">
<div className="bg-brand-500 h-3 rounded-full" style={{ width: '75%' }}></div>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500 font-medium italic underline decoration-brand-200 underline-offset-4">
Spent: $75,000.00
</span>
<span className="text-gray-900 font-bold">Total: $100,000.00</span>
</div>
</div>
</Card>
<Card title="Quick Insight">
<p className="text-gray-600 leading-relaxed italic">
"Anomali terdeteksi pada baris INV-2026-003. Nilai transaksi melebihi rata-rata bulanan sebesar
40%."
</p>
</Card>
</div>
</div>
{/* RIGHT: CONFIGURATION & ACTIONS */}
<div className="xl:col-span-1 space-y-6">
<Card title="Account Settings" footer={<Button className="w-full">Update Control</Button>}>
<div className="space-y-4">
<div className="flex flex-col gap-1">
<label className="text-sm font-bold text-gray-500 uppercase tracking-tighter italic">
Reconciliation Type
</label>
<select className="w-full border border-gray-300 rounded-md px-3 py-1.5 bg-white focus:ring-1 focus:ring-brand-500 outline-none">
<option>Automatic Match</option>
<option>Manual Review</option>
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-bold text-gray-500 uppercase tracking-tighter italic">
Tax Code
</label>
<input
className="w-full border border-gray-300 rounded-md px-3 py-1.5 outline-none focus:border-brand-500 transition-colors"
placeholder="e.g. VAT_11"
/>
</div>
<div className="flex items-center gap-3 py-2 border-t border-gray-50">
<input type="checkbox" className="w-4 h-4 accent-brand-500" id="lock" />
<label htmlFor="lock" className="text-sm font-semibold text-gray-700">
Lock account for posting
</label>
</div>
</div>
</Card>
<div className="bg-brand-900 text-white p-5 rounded-lg shadow-lg relative overflow-hidden">
<div className="relative z-10">
<h4 className="text-lg font-bold mb-1 italic">Premium Support</h4>
<p className="text-sm text-brand-100 opacity-80">
Butuh bantuan rekonsiliasi? Hubungi konsultan finansial kami.
</p>
<button className="mt-4 text-xs font-bold bg-white text-brand-900 px-3 py-1.5 rounded uppercase tracking-widest shadow-lg">
Chat Konsultan
</button>
</div>
<div className="absolute -right-4 -bottom-4 text-6xl opacity-10 font-bold">ERP</div>
</div>
</div>
</div>
</div>
</main>
{/* 3. SAMPLE MODAL (Overlay) */}
{isModalOpen && (
<div className="fixed inset-0 bg-brand-950/40 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden border border-gray-200">
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<h3 className="text-xl font-bold text-gray-900">New Journal Entry</h3>
<button onClick={() => setIsModalOpen(false)} className="text-gray-400 hover:text-gray-600 text-2xl">
×
</button>
</div>
<div className="p-6 space-y-4">
<p className="text-base text-gray-600">
Silakan masukkan detail entri jurnal baru untuk tahun fiskal 2026.
</p>
<div className="grid grid-cols-2 gap-4">
<div className="col-span-2 border border-brand-100 bg-brand-50/50 p-3 rounded-md italic text-xs text-brand-800">
Info: Pastikan periode pembukuan Januari sudah dibuka sebelum menekan "Post".
</div>
</div>
</div>
<div className="px-6 py-4 bg-gray-50 border-t border-gray-100 flex justify-end gap-2">
<Button variant="secondary" onClick={() => setIsModalOpen(false)}>
Discard
</Button>
<Button onClick={() => setIsModalOpen(false)}>Post Entry</Button>
</div>
</div>
</div>
)}
</div>
);
}
@@ -1,232 +0,0 @@
import React from 'react';
// ==========================================================
// COMPACT UI COMPONENTS
// ==========================================================
const SectionTitle = ({ children }: { children: React.ReactNode }) => (
<h3 className="text-[11px] font-bold text-gray-500 uppercase tracking-widest mb-2 border-b border-gray-200 pb-1 italic">
{children}
</h3>
);
const CompactCard = ({ title, children, footer }: any) => (
<div className="bg-white border border-gray-200 rounded shadow-sm flex flex-col">
{title && (
<div className="px-3 py-1.5 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
<span className="text-sm font-bold text-gray-700 tracking-tight">{title}</span>
</div>
)}
<div className="p-3 flex-1">{children}</div>
{footer && <div className="px-3 py-2 bg-gray-50/80 border-t border-gray-100">{footer}</div>}
</div>
);
const Badge = ({ children, variant = 'info' }: any) => {
const styles: any = {
success: 'bg-green-50 text-green-700 border-green-200',
error: 'bg-red-50 text-red-700 border-red-200',
warning: 'bg-amber-50 text-amber-700 border-amber-200',
info: 'bg-brand-50 text-brand-700 border-brand-200',
};
return (
<span
className={`px-1.5 py-0.5 rounded-sm text-[10px] font-bold border ${styles[variant]} uppercase tracking-tighter`}
>
{children}
</span>
);
};
// ==========================================================
// MAIN STYLE SHOWCASE
// ==========================================================
export function StyleGuideTree() {
return (
<div className="min-h-screen bg-gray-100 p-3 space-y-3 font-sans text-gray-800">
{/* GLOBAL ACTION BAR */}
<div className="bg-brand-900 text-white p-3 rounded flex justify-between items-center shadow-md">
<div className="flex items-center gap-3">
<div className="bg-white p-1 rounded text-brand-900 font-bold text-xs uppercase">ERP-SYSTEM</div>
<h1 className="text-base font-bold tracking-tight">Financial Postings & Asset Management</h1>
</div>
<div className="flex gap-2">
<button className="bg-brand-700 hover:bg-brand-600 px-3 py-1 rounded text-base font-medium border border-brand-600 transition-all">
Simulate
</button>
<button className="bg-white text-brand-900 hover:bg-gray-100 px-3 py-1 rounded text-base font-bold shadow-sm transition-all">
Post Document
</button>
</div>
</div>
<div className="grid grid-cols-12 gap-3">
{/* ROW 1: ANALYTICS & STATS (Top Row) */}
{[
{ label: 'Total Debit', val: 'IDR 4.500.000.000', sub: '24 Entries Today', color: 'text-gray-900' },
{ label: 'Total Credit', val: 'IDR 4.500.000.000', sub: 'Balanced', color: 'text-green-600' },
{ label: 'Unposted Items', val: '12 Documents', sub: 'High Priority', color: 'text-amber-600' },
{ label: 'System Health', val: '99.9%', sub: 'No Latency', color: 'text-brand-600' },
].map((stat, i) => (
<div key={i} className="col-span-12 md:col-span-3 bg-white p-3 rounded border border-gray-200 shadow-sm">
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">{stat.label}</p>
<p className={`text-lg font-bold ${stat.color} tracking-tight`}>{stat.val}</p>
<p className="text-[11px] text-gray-400 font-medium italic">{stat.sub}</p>
</div>
))}
{/* ROW 2: FORMS & CONTROLS (Left Panel) */}
<div className="col-span-12 lg:col-span-4 space-y-3">
<CompactCard title="Document Header">
<div className="grid grid-cols-2 gap-3">
<div className="col-span-1 flex flex-col">
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
Posting Date
</label>
<input
type="date"
className="border border-gray-300 rounded px-2 py-1 text-base outline-none focus:border-brand-500 bg-gray-50"
defaultValue="2026-01-19"
/>
</div>
<div className="col-span-1 flex flex-col">
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">Period</label>
<input
type="text"
readOnly
className="border border-gray-200 rounded px-2 py-1 text-base bg-gray-100 text-gray-500"
defaultValue="01 / 2026"
/>
</div>
<div className="col-span-2 flex flex-col">
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
Document Type
</label>
<select className="border border-gray-300 rounded px-2 py-1 text-base outline-none bg-white">
<option>SA - GL Account Document</option>
<option>DZ - Customer Payment</option>
</select>
</div>
<div className="col-span-2 flex flex-col">
<label className="text-xs font-bold text-gray-400 uppercase italic mb-1 tracking-tight">
Header Text
</label>
<textarea
className="border border-gray-300 rounded px-2 py-1 text-base h-16 outline-none focus:border-brand-500"
placeholder="Monthly reconciliation..."
></textarea>
</div>
</div>
</CompactCard>
<CompactCard title="Operational Controls">
<div className="space-y-2">
<label className="flex items-center gap-3 p-2 border border-gray-100 rounded hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" className="accent-brand-600 w-4 h-4" defaultChecked />
<span className="text-base font-medium">Automatic Tax Calculation</span>
</label>
<label className="flex items-center gap-3 p-2 border border-gray-100 rounded hover:bg-gray-50 cursor-pointer transition-colors">
<input type="checkbox" className="accent-brand-600 w-4 h-4" />
<span className="text-base font-medium">Post to All Ledgers</span>
</label>
</div>
</CompactCard>
</div>
{/* ROW 3: COMPLEX DATA TABLE (Center Panel) */}
<div className="col-span-12 lg:col-span-8 space-y-3">
<CompactCard className="!p-0 overflow-hidden">
<div className="bg-gray-50/50 px-4 py-2 border-b border-gray-200 flex justify-between items-center">
<SectionTitle>Line Item Entries (001 - 005)</SectionTitle>
<div className="flex gap-2 mb-2">
<button className="text-[10px] font-bold text-brand-700 bg-brand-50 px-2 py-0.5 rounded border border-brand-100">
ADD ROW
</button>
<button className="text-[10px] font-bold text-gray-500 bg-white px-2 py-0.5 rounded border border-gray-200">
CLEAR ALL
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-gray-100 border-b border-gray-200">
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">Item</th>
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">GL Account</th>
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase">Cost Center</th>
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-right">
Amount (LC)
</th>
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-center">Ind.</th>
<th className="px-3 py-1.5 text-[10px] font-bold text-gray-500 uppercase text-center">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{[10, 20, 30, 40, 50].map((item, idx) => (
<tr key={item} className="hover:bg-brand-50/30 transition-colors group">
<td className="px-3 py-1.5 text-center font-mono text-gray-400">{item}</td>
<td className="px-3 py-1.5">
<div className="font-bold text-brand-700 text-base leading-none">11000{idx}</div>
<div className="text-[10px] text-gray-400 italic">Petty Cash - Main Office</div>
</td>
<td className="px-3 py-1.5">
<div className="font-medium text-gray-700">CC-HEAD-01</div>
</td>
<td className="px-3 py-1.5 text-right font-mono font-bold text-gray-900">
{(item * 1500000).toLocaleString('id-ID')}
</td>
<td className="px-3 py-1.5 text-center font-bold text-brand-600">{idx % 2 === 0 ? 'S' : 'H'}</td>
<td className="px-3 py-1.5 text-center">
<Badge variant={idx === 2 ? 'warning' : 'success'}>{idx === 2 ? 'Review' : 'Valid'}</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="bg-gray-50 p-3 flex justify-end gap-10 border-t border-gray-200">
<div className="text-right">
<p className="text-[10px] font-bold text-gray-400 uppercase">Total Debit</p>
<p className="text-lg font-bold text-gray-900 tracking-tight">IDR 450.000.000</p>
</div>
<div className="text-right">
<p className="text-[10px] font-bold text-gray-400 uppercase">Total Credit</p>
<p className="text-lg font-bold text-brand-600 tracking-tight">IDR 450.000.000</p>
</div>
</div>
</CompactCard>
{/* MESSAGE LOGS / ACTIVITY */}
<div className="grid grid-cols-2 gap-3">
<CompactCard title="System Activity">
<div className="space-y-2">
{[
{ t: '09:41', u: 'System', m: 'Auto-balancing completed successfully.' },
{ t: '10:05', u: 'Admin', m: 'Modified Cost Center on Item 20.' },
].map((log, i) => (
<div key={i} className="text-base flex gap-2 border-b border-gray-50 pb-1">
<span className="font-mono text-brand-600 text-xs">{log.t}</span>
<span className="text-gray-600 leading-tight">
<strong>{log.u}:</strong> {log.m}
</span>
</div>
))}
</div>
</CompactCard>
<CompactCard title="Quick Attachments">
<div className="flex flex-wrap gap-2">
<div className="border border-dashed border-gray-300 rounded p-2 text-center w-full hover:bg-gray-50 cursor-pointer">
<span className="text-xs font-bold text-gray-400">+ DROP FILES HERE</span>
</div>
<div className="bg-gray-100 px-2 py-1 rounded text-[11px] font-medium flex items-center gap-2">
📄 invoice_001.pdf <span className="text-red-500 font-bold cursor-pointer">×</span>
</div>
</div>
</CompactCard>
</div>
</div>
</div>
</div>
);
}
@@ -1,51 +0,0 @@
import RcCheckbox from '@rc-component/checkbox';
import { CheckboxProps } from '../types';
import { forwardRef } from 'react';
import { InputRef } from '@rc-component/input/lib/interface';
/**
* Checkbox component built on top of RcCheckbox.
*
* Renders a checkbox with an optional label (uses `label` prop or `children`).
* Default prop values: `status = 'default'`, `variant = 'outlined'`.
*
* The wrapper element will receive these classes:
* - 'rc-checkbox-wrapper'
* - `checkbox-status-{status}`
* - `checkbox-variant-{variant}`
* - 'rc-checkbox-wrapper-disabled' when `disabled` is true
* and any additional classes passed via `className`.
*
* All remaining props are forwarded to the underlying RcCheckbox, which is rendered with `prefixCls="rc-checkbox"`.
*
* Note: a `ref` parameter is accepted by the forwardRef call but is not currently attached to the rendered input element.
*
* @param props - Checkbox props
* @param ref - Forwarded ref (InputRef). Accepted but not applied to the DOM node in the current implementation.
* @returns JSX.Element
*/
export const Checkbox = forwardRef<InputRef, CheckboxProps>((props, ref) => {
const { label, status = 'default', className, children, ...rest } = props;
const displayLabel = label || children;
const wrapperClass = [
'rc-checkbox-wrapper',
`checkbox-status-${status}`,
rest.disabled ? 'rc-checkbox-wrapper-disabled' : '',
className,
]
.filter(Boolean)
.join(' ');
return (
<label className={wrapperClass}>
<span className="rc-checkbox-container">
<RcCheckbox ref={ref} {...rest} prefixCls="rc-checkbox" />
</span>
{displayLabel && <span className="rc-checkbox-label-text">{displayLabel}</span>}
</label>
);
});
Checkbox.displayName = 'Checkbox';
@@ -1,14 +0,0 @@
import RcForm from '@rc-component/form';
import { FormProps } from '../types';
/**
* A form control component that wraps the underlying form library.
*
* @template Values - The type of values managed by the form. Defaults to `any`.
* @param props - The form configuration properties.
* @param props.children - The child elements to render within the form.
* @returns A rendered form component with the provided children.
*/
export default function FormControl<Values = any>({ children, ...props }: FormProps<Values>) {
return <RcForm {...props}>{children}</RcForm>;
}
@@ -1,81 +0,0 @@
import React, { ReactElement } from 'react';
import { Field } from '@rc-component/form';
import { FormItemProps } from '../types';
/**
* FormItem component that wraps form fields with validation, error/warning messaging, and styling.
*
* This component manages the rendering of a form field with associated label, validation rules,
* and status-based styling. It integrates with a Field control system to handle form state,
* errors, and warnings.
*
* @component
* @example
* ```tsx
* <FormItem
* name="username"
* label="Username"
* rules={[{ required: true, message: 'Username is required' }]}
* >
* <Input />
* </FormItem>
* ```
*
* @param {FormItemProps} props - The component props
* @param {string} props.name - The field name for form control binding
* @param {string} [props.label] - The label text to display above the input
* @param {ReactNode} props.children - The form control element (e.g., Input, Textarea)
* @param {Array<ValidationRule>} [props.rules] - Validation rules for the field
* @param {string} [props.valuePropName='value'] - The prop name used to bind the field value
* @param {any} [props.initialValue] - The initial value for the field
* @param {'error' | 'warning' | 'normal'} [props.status] - Override the status appearance
*
* @returns {ReactElement} A form item container with label, control, and validation messages
*/
export default function FormItem(props: FormItemProps) {
const { name, label, children, rules, valuePropName = 'value', initialValue, status: OverrideStatus } = props;
return (
<Field name={name} rules={rules} valuePropName={valuePropName} initialValue={initialValue}>
{(control, meta) => {
// 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;
// Pass status to child (Input/Textarea) so their borders change color
const childNode = childElement ? React.cloneElement(childElement, { ...control, status }) : children;
// Logic Required Check
const isRequired = rules?.some((r: any) => r && typeof r === 'object' && r.required);
return (
// Wrapper Div with status class
<div className={`rc-form-item status-${status}`}>
{/* LABEL SECTION */}
{label && (
<label className="rc-form-item-label">
{label}
{isRequired && <span className="rc-form-item-required-mark">*</span>}
</label>
)}
{/* INPUT CONTROL */}
<div className="rc-form-item-control relative">{childNode}</div>
{/* ERROR / WARNING MESSAGE */}
{(hasError || hasWarning) && (
<div className="rc-form-item-explain">{hasError ? meta.errors[0] : meta.warnings[0]}</div>
)}
</div>
);
}}
</Field>
);
}
@@ -1,42 +0,0 @@
// index.ts
import React from 'react';
import { useForm, List, FieldContext } from '@rc-component/form';
import { FormProps } from '../types';
import FormControl from './form-control.component';
import FormItem from './form-item.component';
/**
* Internal Form component using forwardRef to handle RcForm instance
*/
/**
* React component that provides form context and forwards a ref to the underlying FormControl.
*
* @template Values - Shape of the form values (extends object). Defaults to any.
* @param props - FormProps for the form, augmented with an optional forwarded ref.
* @param props.ref - Optional forwarded ref passed to the underlying FormControl.
* @returns A React element that sets up form context and delegates rendering/behavior to FormControl.
*
* @remarks
* - Built using React.forwardRef and typed as a generic component so callers can infer form value types.
* - Use to wrap form fields and access form state/actions via context.
*/
const FormProvider = React.forwardRef(FormControl) as <Values extends object = any>(
props: FormProps<Values> & { ref?: React.ForwardedRef<any> },
) => React.ReactElement;
type CompoundedComponent = typeof FormProvider & {
Item: typeof FormItem;
useForm: typeof useForm;
List: typeof List;
Provider: typeof FieldContext.Provider;
};
// Assign sub-components and utilities
const Form = FormProvider as CompoundedComponent;
Form.Item = FormItem;
Form.useForm = useForm;
Form.List = List;
Form.Provider = FieldContext.Provider;
export { Form };
-12
View File
@@ -1,12 +0,0 @@
export * from './types';
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';
export * from './checkbox/checkbox.component';
export * from './switch/switch.component';
export * from "./picker";
@@ -1,56 +0,0 @@
import { CurrencyUtils } from '@repo/utils';
import { InputCurrencyProps } from '../types';
import { InputNumber } from './input-number.component';
/**
* InputCurrency
*
* A controlled InputNumber component with currency formatting.
* Delegates all formatting/parsing to CurrencyUtils.
*
* Features:
* - Display currency prefix (e.g., Rp, $)
* - Thousand separator formatting
* - Optional decimal rounding
* - Global prefix support via CurrencyUtils
*/
export const InputCurrency = (props: InputCurrencyProps) => {
const {
prefix, // Optional override for instance prefix
decimalSeparator, // Optional override for separator
decimalScale, // Optional rounding
...restProps
} = props;
// Create a CurrencyUtils instance for this input
const currencyService = new CurrencyUtils({
prefix,
decimalSeparator,
decimalScale,
});
/**
* Formatter: converts numeric value to formatted string for display
*/
const formatter = (value: number | string | undefined) => {
return currencyService.format(value);
};
/**
* Parser: converts formatted string back to raw numeric string
* Note: never rounds or modifies the underlying value
*/
const parser = (displayValue: string | undefined) => {
return currencyService.parseToRaw(displayValue);
};
return (
<InputNumber
{...restProps}
formatter={formatter}
parser={parser}
// Default min value to 0 for currency
min={props.min !== undefined ? props.min : 0}
/>
);
};
@@ -1,65 +0,0 @@
/**
* A controlled input component for numeric values with increment/decrement handlers.
*
* Wraps the rc-component InputNumber with custom styling support and status indicators.
* Features custom chevron-style up/down icons and support for multiple visual variants.
*
* @component
* @example
* ```tsx
* const ref = useRef<HTMLInputElement>(null);
* <InputNumber
* ref={ref}
* value={10}
* onChange={(val) => console.log(val)}
* status="success"
* />
* ```
*
* @param {InputNumberProps} props - The input number component props
* @param {InputStatusType} [props.status] - The validation status ('default', 'success', 'error', etc.)
* @param {string} [props.className] - Additional CSS classes to apply
* @param {React.Ref<HTMLInputElement>} ref - Ref to the underlying input element
*
* @returns {React.ReactElement} The rendered input number component
*/
import { forwardRef } from 'react';
import RcInputNumber from '@rc-component/input-number';
import { InputNumberProps, InputStatusType, VALID_INPUT_STATUSES } from '../types';
// Handler Icons (Chevron Modern)
const UpIcon = () => (
<span className="input-number-action-up-inner flex items-center justify-center w-full h-full">
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor" className="w-3 h-3">
<path d="M8.5 6.5L12 10 5 10z" />
</svg>
</span>
);
const DownIcon = () => (
<span className="input-number-action-down-inner flex items-center justify-center w-full h-full">
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor" className="w-3 h-3">
<path d="M8.5 9.5L5 6 12 6z" />
</svg>
</span>
);
export const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
const { status, className, ...restProps } = props;
const currentStatus: InputStatusType = status && VALID_INPUT_STATUSES.includes(status) ? status : 'default';
const computedClassName = [className, `input-number-status-${currentStatus}`].filter(Boolean).join(' ');
return (
<RcInputNumber
ref={ref}
{...restProps}
className={computedClassName}
upHandler={<UpIcon />}
downHandler={<DownIcon />}
/>
);
});
InputNumber.displayName = 'InputNumber';
@@ -1,101 +0,0 @@
import { 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) => {
// 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}
/>
);
};
@@ -1,41 +0,0 @@
import { forwardRef } from 'react';
import RcInput, { InputRef } from '@rc-component/input';
import { InputProps, InputStatusType, VALID_INPUT_STATUSES } from '../types';
/**
* Input component that wraps the RC Input library with custom status.
*
* @component
* @param {InputProps} props - The input component props
* @param {InputStatusType} [props.status] - The validation status of the input (validated against VALID_INPUT_STATUSES)
* @param {string} [props.className] - Additional CSS classes to apply to the input
* @param {boolean} [props.disabled] - Whether the input is disabled
* @param {InputRef} ref - Reference to the underlying RC Input element
* @returns {React.ReactElement} The rendered Input component with computed classes for status, and disabled state
*
* @remarks
* - Invalid status values default to 'default'
* - The 'rc-input-disabled' class is automatically applied by RC Input library when disabled={true}
* - The 'input-disabled' helper class is manually added for custom wrapper styling when needed
*/
export const Input = forwardRef<InputRef, InputProps>((props, ref) => {
const { status, className, disabled, ...restProps } = props;
// Determine current status, defaulting to 'default' if invalid
const currentStatus: InputStatusType = status && VALID_INPUT_STATUSES.includes(status) ? status : 'default';
// Construct classname
// Note: class 'rc-input-disabled' is added automatically by RC Input when disabled
const computedClassName = [
className,
`input-status-${currentStatus}`,
// Add custom disabled class if disabled
disabled ? 'input-disabled' : '',
]
.filter(Boolean)
.join(' ');
return <RcInput ref={ref} {...restProps} disabled={disabled} className={computedClassName} />;
});
Input.displayName = 'Input';
@@ -1,190 +0,0 @@
import { forwardRef } from 'react';
import RcPicker, { PickerPanel as RcPickerPanel, RangePicker as RcRangePicker } from '@rc-component/picker';
import type { GenerateConfig } from '@rc-component/picker/lib/generate';
import enUS from '@rc-component/picker/lib/locale/en_US';
import {
PickerProps,
RangePickerProps,
PickerPanelProps,
InputStatusType,
VALID_INPUT_STATUSES,
SpecificPickerProps,
SpecificRangePickerProps,
} from '../types';
const COMPONENT_PREFIX = 'rc-picker';
interface PickerGenerateReturns<DateType extends object> {
Picker: React.ForwardRefExoticComponent<PickerProps<DateType> & React.RefAttributes<any>>;
RangePicker: React.ForwardRefExoticComponent<RangePickerProps<DateType> & React.RefAttributes<any>>;
Calendar: React.ForwardRefExoticComponent<PickerPanelProps<DateType> & React.RefAttributes<any>>;
DatePicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
WeekPicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
MonthPicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
QuarterPicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
YearPicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
TimePicker: React.ForwardRefExoticComponent<SpecificPickerProps<DateType> & React.RefAttributes<any>>;
DateRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
WeekRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
MonthRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
QuarterRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
YearRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
TimeRangePicker: React.ForwardRefExoticComponent<SpecificRangePickerProps<DateType> & React.RefAttributes<any>>;
}
const getStatusClassName = (prefixCls: string, className?: string, status?: InputStatusType, disabled?: boolean) => {
const currentStatus = status && VALID_INPUT_STATUSES.includes(status) ? status : '';
return [
className,
prefixCls,
currentStatus ? `picker-status-${currentStatus}` : '',
disabled ? `${prefixCls}-disabled` : '',
]
.filter(Boolean)
.join(' ');
};
export function generateComponentPicker<DateType extends object>(
generateConfig: GenerateConfig<DateType>,
): PickerGenerateReturns<DateType> {
// 1. Generic Picker
const Picker = forwardRef<any, PickerProps<DateType>>((props, ref) => {
const { status, className, locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
return (
<RcPicker<DateType>
ref={ref}
prefixCls={prefixCls}
generateConfig={generateConfig}
locale={locale || enUS}
className={getStatusClassName(prefixCls, className, status, props.disabled)}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
transitionName={`rc-slide-up`}
{...restProps}
/>
);
});
Picker.displayName = 'Picker';
// 2. Generic Range Picker
const RangePicker = forwardRef<any, RangePickerProps<DateType>>((props, ref) => {
const { status, className, locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
return (
<RcRangePicker<DateType>
ref={ref}
prefixCls={prefixCls}
generateConfig={generateConfig}
locale={locale || enUS}
className={getStatusClassName(prefixCls, className, status, props.disabled as boolean)}
{...restProps}
/>
);
});
RangePicker.displayName = 'RangePicker';
// 3. Calendar
const Calendar = forwardRef<any, PickerPanelProps<DateType>>((props, ref) => {
const { locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
return (
<RcPickerPanel<DateType>
ref={ref}
prefixCls={prefixCls}
generateConfig={generateConfig}
locale={locale || enUS}
{...restProps}
/>
);
});
Calendar.displayName = 'Calendar';
// ================== Specific Pickers ==================
const DatePicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} placeholder="Select date" {...props} picker="date" />
));
DatePicker.displayName = 'DatePicker';
const TimePicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} mode={undefined} placeholder="Select time" {...props} picker="time" />
));
TimePicker.displayName = 'TimePicker';
const WeekPicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} mode={undefined} placeholder="Select week" {...props} picker="week" />
));
WeekPicker.displayName = 'WeekPicker';
const MonthPicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} mode={undefined} placeholder="Select moth" {...props} picker="month" />
));
MonthPicker.displayName = 'MonthPicker';
const QuarterPicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} mode={undefined} placeholder="Select quarter" {...props} picker="quarter" />
));
QuarterPicker.displayName = 'QuarterPicker';
const YearPicker = forwardRef<any, SpecificPickerProps<DateType>>((props, ref) => (
<Picker ref={ref} mode={undefined} placeholder="Select year" {...props} picker="year" />
));
YearPicker.displayName = 'YearPicker';
// ================== Specific Range Pickers ==================
const DateRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker ref={ref} placeholder={['Start date', 'End date']} {...props} picker="date" />
));
DateRangePicker.displayName = 'DateRangePicker';
const TimeRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker ref={ref} mode={undefined} placeholder={['Start time', 'End time']} {...props} picker="time" />
));
TimeRangePicker.displayName = 'TimeRangePicker';
const WeekRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker ref={ref} mode={undefined} placeholder={['Start week', 'End week']} {...props} picker="week" />
));
WeekRangePicker.displayName = 'WeekRangePicker';
const MonthRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker ref={ref} mode={undefined} placeholder={['Start month', 'End month']} {...props} picker="month" />
));
MonthRangePicker.displayName = 'MonthRangePicker';
const QuarterRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker
ref={ref}
mode={undefined}
placeholder={['Start quarter', 'End quarter']}
{...props}
picker="quarter"
/>
));
QuarterRangePicker.displayName = 'QuarterRangePicker';
const YearRangePicker = forwardRef<any, SpecificRangePickerProps<DateType>>((props, ref) => (
<RangePicker ref={ref} mode={undefined} placeholder={['Start year', 'End year']} {...props} picker="year" />
));
YearRangePicker.displayName = 'YearRangePicker';
return {
Picker,
RangePicker,
Calendar,
DatePicker,
WeekPicker,
MonthPicker,
QuarterPicker,
YearPicker,
TimePicker,
DateRangePicker,
WeekRangePicker,
MonthRangePicker,
QuarterRangePicker,
YearRangePicker,
TimeRangePicker,
};
}
@@ -1,24 +0,0 @@
import { Dayjs } from 'dayjs';
import dayjsGenerateConfig from '@rc-component/picker/es/generate/dayjs';
import { generateComponentPicker } from './generate-picker.component';
export const {
// Base
Picker,
RangePicker,
Calendar,
// Pickers
DatePicker,
WeekPicker,
MonthPicker,
QuarterPicker,
YearPicker,
TimePicker,
// Range Pickers
DateRangePicker,
WeekRangePicker,
MonthRangePicker,
QuarterRangePicker,
YearRangePicker,
TimeRangePicker,
} = generateComponentPicker<Dayjs>(dayjsGenerateConfig);
@@ -1,213 +0,0 @@
@layer components {
/* =========================================
BASE WRAPPER & LABEL
========================================= */
.rc-checkbox-wrapper {
display: inline-flex;
align-items: center;
cursor: pointer;
font-size: var(--form-label-font-size);
font-weight: var(--form-label-font-weight);
color: var(--form-label-color);
line-height: 1;
gap: 8px; /* Distance between checkbox and label */
transition: all 0.2s;
}
.rc-checkbox-wrapper-disabled {
cursor: not-allowed;
color: var(--disabled-text);
}
/* =========================================
CORE CHECKBOX STRUCTURE
========================================= */
.rc-checkbox {
white-space: nowrap;
cursor: pointer;
outline: none;
display: inline-block;
position: relative;
line-height: 1;
vertical-align: middle;
top: -1px; /* Optically aligns the checkbox */
}
.rc-checkbox-input {
position: absolute;
inset: 0;
z-index: 9999;
cursor: pointer;
opacity: 0;
margin: 0;
}
.rc-checkbox-inner {
position: relative;
top: 0;
left: 0;
display: inline-block;
width: var(--checkbox-size); /* Size of the checkbox */
height: var(--checkbox-size);
border-width: 1px;
border-style: solid;
border-radius: var(--checkbox-radius); /* Consistent with global shape */
border-color: var(--input-border);
background-color: var(--input-bg);
/* Use standard 0.2s transition */
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Check mark (The L-shape) */
.rc-checkbox-inner:after {
content: ' ';
position: absolute;
display: table;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(45deg) scale(0);
opacity: 0;
/* Position adjust for 16px box */
left: var(--checkbox-check-left);
top: var(--checkbox-check-top);
width: var(--checkbox-check-width);
height: var(--checkbox-check-height);
transition:
all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6),
opacity 0.1s;
}
/* =========================================
INTERACTION STATES
========================================= */
/* Hover */
.rc-checkbox:hover .rc-checkbox-inner,
.rc-checkbox-wrapper:hover .rc-checkbox-inner {
border-color: var(--input-border-hover);
}
/* Focus Ring */
.rc-checkbox-input:focus-visible + .rc-checkbox-inner {
border-color: var(--focus-border-color);
box-shadow: 0 0 0 3px var(--focus-ring-color);
}
/* Checked State */
.rc-checkbox-checked .rc-checkbox-inner {
border-color: var(--color-brand-500);
background-color: var(--color-brand-500);
}
.rc-checkbox-checked .rc-checkbox-inner:after {
transform: rotate(45deg) scale(1);
opacity: 1;
transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
}
/* =========================================
STATUS MATRIX (Error, Warning, Success, Info)
========================================= */
/* --- DEFAULT --- */
.checkbox-status-default .rc-checkbox-inner {
border-color: var(--input-border);
}
.checkbox-status-default:hover .rc-checkbox-inner {
border-color: var(--input-border-hover);
}
.checkbox-status-default .rc-checkbox-checked .rc-checkbox-inner {
background-color: var(--color-brand-500);
border-color: var(--color-brand-500);
}
.checkbox-status-default .rc-checkbox-input:focus-visible + .rc-checkbox-inner {
box-shadow: 0 0 0 3px var(--color-brand-100);
}
/* --- ERROR --- */
.checkbox-status-error .rc-checkbox-inner {
border-color: var(--color-error-300);
}
.checkbox-status-error:hover .rc-checkbox-inner {
border-color: var(--color-error-500);
}
.checkbox-status-error .rc-checkbox-checked .rc-checkbox-inner {
background-color: var(--color-error-500);
border-color: var(--color-error-500);
}
.checkbox-status-error .rc-checkbox-input:focus-visible + .rc-checkbox-inner {
box-shadow: 0 0 0 3px var(--color-error-100);
}
/* --- WARNING --- */
.checkbox-status-warning .rc-checkbox-inner {
border-color: var(--color-warning-300);
}
.checkbox-status-warning:hover .rc-checkbox-inner {
border-color: var(--color-warning-500);
}
.checkbox-status-warning .rc-checkbox-checked .rc-checkbox-inner {
background-color: var(--color-warning-500);
border-color: var(--color-warning-500);
}
.checkbox-status-warning .rc-checkbox-input:focus-visible + .rc-checkbox-inner {
box-shadow: 0 0 0 3px var(--color-warning-100);
}
/* --- SUCCESS --- */
.checkbox-status-success .rc-checkbox-inner {
border-color: var(--color-success-300);
}
.checkbox-status-success:hover .rc-checkbox-inner {
border-color: var(--color-success-500);
}
.checkbox-status-success .rc-checkbox-checked .rc-checkbox-inner {
background-color: var(--color-success-500);
border-color: var(--color-success-500);
}
.checkbox-status-success .rc-checkbox-input:focus-visible + .rc-checkbox-inner {
box-shadow: 0 0 0 3px var(--color-success-100);
}
/* --- INFO --- */
.checkbox-status-info .rc-checkbox-inner {
border-color: var(--color-info-300);
}
.checkbox-status-info:hover .rc-checkbox-inner {
border-color: var(--color-info-500);
}
.checkbox-status-info .rc-checkbox-checked .rc-checkbox-inner {
background-color: var(--color-info-500);
border-color: var(--color-info-500);
}
.checkbox-status-info .rc-checkbox-input:focus-visible + .rc-checkbox-inner {
box-shadow: 0 0 0 3px var(--color-info-100);
}
/* =========================================
DISABLED STATE
========================================= */
.rc-checkbox-disabled {
cursor: not-allowed;
}
.rc-checkbox-disabled .rc-checkbox-inner {
background-color: var(--disabled-bg) !important;
border-color: var(--disabled-border) !important;
}
.rc-checkbox-disabled.rc-checkbox-checked .rc-checkbox-inner:after {
border-color: var(--disabled-text);
}
.rc-checkbox-disabled .rc-checkbox-input {
cursor: not-allowed;
}
/* Print Support */
@media print {
.rc-checkbox-checked .rc-checkbox-inner {
box-shadow: inset 0 0 0 16px var(--color-brand-500);
}
}
}
@@ -1,61 +0,0 @@
@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 */
}
/* =========================
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; /* Reserve space even when empty to prevent layout shift */
transition: color 0.2s;
}
/* =========================
STATUS MODIFIERS
========================= */
/* --- ERROR STATE --- */
.rc-form-item.status-error .rc-form-item-label {
color: var(--color-error-600); /* Darker shade for label on error */
}
.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,7 +0,0 @@
@import './form-item.style.css';
@import './input.style.css';
@import './input-number.style.css';
@import './textarea.style.css';
@import './checkbox.style.css';
@import './switch.style.css';
@import './picker.style.css';
@@ -1,264 +0,0 @@
@layer components {
/* =========================================
1. BASE SETUP (Layout & Reset)
========================================= */
.rc-input-number {
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
width: 100%;
/* Global Variables */
height: var(--input-height);
font-size: var(--input-font-size);
border-radius: var(--input-radius);
line-height: 1.5;
color: var(--input-text);
background-color: var(--input-bg);
border: 1px solid transparent;
/* Standard Transition */
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
margin: 0;
padding: 0;
}
/* Input Element */
.rc-input-number-input {
width: 100%;
height: 100%;
padding: 0 var(--input-padding-x);
background-color: transparent;
border: 0;
border-radius: var(--input-radius);
outline: 0;
color: inherit; /* Inherit from .rc-input-number */
transition: all 0.2s linear;
/* Prevent text under action buttons */
padding-right: var(--input-handler-width);
}
/* Hide Native Spinners (Browser Default) */
.rc-input-number-input::-webkit-inner-spin-button,
.rc-input-number-input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
display: none;
}
.rc-input-number-input {
-moz-appearance: textfield;
}
/* Prefix Styling */
.rc-input-number-prefix {
margin-left: var(--input-padding-x);
margin-right: 0.5rem; /* ~8px */
display: flex;
align-items: center;
flex: none;
color: var(--color-gray-500);
}
/* Suffix Styling */
.rc-input-number-suffix {
margin-right: var(--input-padding-x);
display: flex;
align-items: center;
flex: none;
color: var(--color-gray-500);
z-index: 10;
transition: margin-right 0.2s ease-in-out;
}
/* Animation Trigger: On Hover/Focus, Suffix shifts left to accommodate action buttons */
.rc-input-number:hover .rc-input-number-suffix,
.rc-input-number-affix-wrapper:hover .rc-input-number-suffix,
.rc-input-number-focused .rc-input-number-suffix,
.rc-input-number-affix-wrapper-focused .rc-input-number-suffix,
.rc-input-number:focus-within .rc-input-number-suffix,
.rc-input-number-affix-wrapper:focus-within .rc-input-number-suffix {
margin-right: calc(var(--input-padding-x) + var(--input-handler-width));
}
/* =========================================
2. ACTIONS CONTAINER (Custom Spinners)
Target: .rc-input-number-actions
========================================= */
.rc-input-number-actions {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: var(--input-handler-width);
height: 100%;
/* Using var input-bg for consistency (white) */
background: var(--input-bg);
/* Default Border (will be overridden by status) */
border-left: 1px solid var(--input-border);
border-radius: 0 var(--input-radius) var(--input-radius) 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.24s linear 0.1s;
z-index: 10;
display: flex;
flex-direction: column;
}
/* Show Actions on Interaction */
.rc-input-number:hover .rc-input-number-actions,
.rc-input-number:focus-within .rc-input-number-actions {
opacity: 1;
pointer-events: auto;
}
/* Individual Buttons */
.rc-input-number-action {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
overflow: hidden;
color: var(--color-gray-400);
cursor: pointer;
transition: all 0.2s linear;
}
.rc-input-number-action:active {
background-color: var(--color-gray-100);
}
.rc-input-number-action:hover {
color: var(--color-brand-500);
height: 60%; /* Effect hover slightly reduces height */
}
/* Border between Up/Down Buttons */
.rc-input-number-action-up {
border-bottom: 1px solid var(--input-border);
}
/* =========================================
3. STATUS MATRIX
========================================= */
/* --- DEFAULT --- */
.input-number-status-default {
border-color: var(--input-border);
}
.input-number-status-default:hover:not(.rc-input-number-disabled) {
border-color: var(--input-border-hover);
}
.input-number-status-default:focus-within {
border-color: var(--focus-border-color);
box-shadow: 0 0 0 3px var(--focus-ring-color);
}
/* Action Borders Sync */
.input-number-status-default:focus-within .rc-input-number-actions {
border-left-color: var(--focus-border-color);
}
.input-number-status-default:focus-within .rc-input-number-action-up {
border-bottom-color: var(--focus-border-color);
}
/* --- ERROR --- */
.input-number-status-error {
border-color: var(--color-error-500);
}
.input-number-status-error:hover:not(.rc-input-number-disabled) {
border-color: var(--color-error-600);
}
.input-number-status-error:focus-within {
border-color: var(--color-error-600);
box-shadow: 0 0 0 3px var(--color-error-100);
}
/* Action Borders Sync */
.input-number-status-error .rc-input-number-actions {
border-left-color: var(--color-error-500);
}
.input-number-status-error .rc-input-number-action-up {
border-bottom-color: var(--color-error-500);
}
/* --- WARNING --- */
.input-number-status-warning {
border-color: var(--color-warning-500);
}
.input-number-status-warning:hover:not(.rc-input-number-disabled) {
border-color: var(--color-warning-600);
}
.input-number-status-warning:focus-within {
border-color: var(--color-warning-600);
box-shadow: 0 0 0 3px var(--color-warning-100);
}
/* Action Borders Sync */
.input-number-status-warning .rc-input-number-actions {
border-left-color: var(--color-warning-500);
}
.input-number-status-warning .rc-input-number-action-up {
border-bottom-color: var(--color-warning-500);
}
/* --- SUCCESS --- */
.input-number-status-success {
border-color: var(--color-success-500);
}
.input-number-status-success:hover:not(.rc-input-number-disabled) {
border-color: var(--color-success-600);
}
.input-number-status-success:focus-within {
border-color: var(--color-success-600);
box-shadow: 0 0 0 3px var(--color-success-100);
}
/* Action Borders Sync */
.input-number-status-success .rc-input-number-actions {
border-left-color: var(--color-success-500);
}
.input-number-status-success .rc-input-number-action-up {
border-bottom-color: var(--color-success-500);
}
/* --- INFO --- */
.input-number-status-info {
border-color: var(--color-info-500);
}
.input-number-status-info:hover:not(.rc-input-number-disabled) {
border-color: var(--color-info-600);
}
.input-number-status-info:focus-within {
border-color: var(--color-info-600);
box-shadow: 0 0 0 3px var(--color-info-100);
}
/* Action Borders Sync */
.input-number-status-info .rc-input-number-actions {
border-left-color: var(--color-info-500);
}
.input-number-status-info .rc-input-number-action-up {
border-bottom-color: var(--color-info-500);
}
/* =========================================
4. DISABLED STATE
========================================= */
.rc-input-number-disabled {
background-color: var(--disabled-bg) !important;
border-color: var(--disabled-border) !important;
color: var(--disabled-text);
cursor: not-allowed;
opacity: 1;
}
.rc-input-number-disabled .rc-input-number-input {
cursor: not-allowed;
}
.rc-input-number-disabled .rc-input-number-actions {
display: none !important;
}
}
@@ -1,236 +0,0 @@
@layer components {
/* =========================================
BASE RESET & SHARED
========================================= */
.rc-input {
/* Use box-sizing border-box for predictable sizing */
box-sizing: border-box;
display: inline-block;
width: 100%;
/* Using variable dimensions */
height: var(--input-height);
padding: 0 var(--input-padding-x); /* Consistent with 0.92rem */
font-size: var(--input-font-size);
line-height: 1.5;
color: var(--input-text);
background-color: var(--input-bg);
border: 1px solid transparent;
border-radius: var(--input-radius);
/* Standard Transition */
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
}
.rc-input::placeholder {
color: var(--input-placeholder);
opacity: 1;
}
/* Disabled State (Standalone Input) */
.rc-input:disabled {
background-color: var(--disabled-bg) !important;
color: var(--disabled-text);
border-color: var(--disabled-border) !important;
cursor: not-allowed;
opacity: 1;
pointer-events: none;
}
/* =========================================
CLEAR ICON
Button (x) to clear input content
========================================= */
.rc-input-clear-icon {
/* Reset Button Styles */
margin: 0;
padding: 0;
background: none;
border: none;
outline: none;
/* Layout & Sizing */
display: flex;
align-items: center;
justify-content: center;
font-size: var(--text-sm); /* ~12px icon size */
line-height: 1;
cursor: pointer;
/* Colors & Transition */
color: var(--color-gray-400); /* Default gray color */
transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
/* Visibility Control ( Smooth appearance/disappearance could use opacity + pointer-events )
/* visibility: hidden; */
visibility: visible;
}
/* Hover State: Darker Gray */
.rc-input-clear-icon:hover {
color: var(--color-gray-500);
}
/* Active/Click State */
.rc-input-clear-icon:active {
color: var(--color-gray-600);
}
/* Hidden State */
.rc-input-clear-icon-hidden {
display: none;
/* Alternative approach to keep layout but invisible:
visibility: hidden;
pointer-events: none;
*/
}
/* Placement Helper: Margin to separate from input text */
.rc-input-affix-wrapper .rc-input-clear-icon {
margin-left: 0.5rem; /* ~8px spacing from text */
z-index: 2; /* Above input text */
}
/* =========================================
AFFIX WRAPPER (PREFIX / SUFFIX) SETUP
========================================= */
/* Base Wrapper Style */
.rc-input-affix-wrapper {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
/* Using variable dimensions */
height: var(--input-height);
padding: 0 var(--input-padding-x);
border: 1px solid transparent;
border-radius: var(--input-radius);
background-color: var(--input-bg);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Reset Input inside Affix Wrapper */
.rc-input-affix-wrapper .rc-input {
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0;
margin: 0;
background-color: transparent;
box-shadow: none !important;
}
/* Prefix & Suffix Positioning */
.rc-input-prefix {
margin-right: 0.5rem; /* ~8px, hardcoded */
display: flex;
align-items: center;
flex: none;
}
.rc-input-suffix {
margin-left: 0.5rem; /* ~8px */
display: flex;
align-items: center;
flex: none;
}
/* Disabled Wrapper */
.rc-input-affix-wrapper-disabled {
background-color: var(--disabled-bg) !important;
color: var(--disabled-text);
border-color: var(--disabled-border) !important;
cursor: not-allowed;
pointer-events: none;
}
.rc-input-affix-wrapper-disabled .rc-input {
cursor: not-allowed;
}
/* =========================================
LOGIC MATRIX: [VARIANT] x [STATUS]
========================================= */
/* ------------------------------------------------
1. VARIANT: OUTLINED (Default)
------------------------------------------------ */
/* DEFAULT */
.input-status-default {
border-color: var(--input-border);
}
/* Hover */
.input-status-default:hover:not(.rc-input-affix-wrapper-disabled):not(:disabled) {
border-color: var(--input-border-hover);
}
/* Focus */
.input-status-default:focus,
.input-status-default.rc-input-affix-wrapper-focused {
border-color: var(--focus-border-color);
box-shadow: 0 0 0 3px var(--focus-ring-color);
z-index: 1;
}
/* ERROR */
.input-status-error {
border-color: var(--color-error-500);
z-index: 1;
}
.input-status-error:hover:not(.rc-input-affix-wrapper-disabled):not(:disabled) {
border-color: var(--color-error-600);
}
.input-status-error:focus,
.input-status-error.rc-input-affix-wrapper-focused {
border-color: var(--color-error-600);
box-shadow: 0 0 0 3px var(--color-error-100);
}
/* WARNING */
.input-status-warning {
border-color: var(--color-warning-500);
z-index: 1;
}
.input-status-warning:hover:not(.rc-input-affix-wrapper-disabled):not(:disabled) {
border-color: var(--color-warning-600);
}
.input-status-warning:focus,
.input-status-warning.rc-input-affix-wrapper-focused {
border-color: var(--color-warning-600);
box-shadow: 0 0 0 3px var(--color-warning-100);
}
/* SUCCESS */
.input-status-success {
border-color: var(--color-success-500);
z-index: 1;
}
.input-status-success:hover:not(.rc-input-affix-wrapper-disabled):not(:disabled) {
border-color: var(--color-success-600);
}
.input-status-success:focus,
.input-status-success.rc-input-affix-wrapper-focused {
border-color: var(--color-success-600);
box-shadow: 0 0 0 3px var(--color-success-100);
}
/* INFO */
.input-status-info {
border-color: var(--color-info-500);
z-index: 1;
}
.input-status-info:hover:not(.rc-input-affix-wrapper-disabled):not(:disabled) {
border-color: var(--color-info-600);
}
.input-status-info:focus,
.input-status-info.rc-input-affix-wrapper-focused {
border-color: var(--color-info-600);
box-shadow: 0 0 0 3px var(--color-info-100);
}
}
@@ -1,647 +0,0 @@
@layer components {
/* =========================================
PICKER BASE (INPUT WRAPPER)
========================================= */
.rc-picker {
box-sizing: border-box;
display: inline-flex;
align-items: center;
width: 100%;
position: relative;
/* Using variable dimensions from globals.css */
height: var(--input-height);
padding: 0 var(--input-padding-x);
font-size: var(--input-font-size);
line-height: 1.5;
color: var(--input-text);
background-color: var(--input-bg);
border: 1px solid transparent;
border-color: var(--input-border);
border-radius: var(--input-radius);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
}
/* Input inside Picker */
.rc-picker-input {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 100%;
flex: 1;
}
.rc-picker-input > input {
width: 100%;
height: 100%;
min-width: 0;
padding: 0;
background-color: transparent;
border: 0;
outline: none;
color: inherit;
font-size: inherit;
line-height: inherit;
transition: all 0.2s;
}
.rc-picker-input > input::placeholder {
color: var(--input-placeholder);
opacity: 1;
}
/* Suffix Icon (Calendar Icon) */
.rc-picker-suffix {
margin-left: 0.5rem;
color: var(--color-gray-400);
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
font-size: var(--text-lg);
transition: all 0.2s;
}
/* Clear Icon */
.rc-picker-clear {
position: absolute;
top: 50%;
right: var(--input-padding-x);
transform: translateY(-50%);
cursor: pointer;
opacity: 0;
transition:
opacity 0.2s,
color 0.2s;
background: var(--input-bg);
color: var(--color-gray-400);
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
width: 1.2em;
height: 1.2em;
border-radius: 50%;
}
.rc-picker:hover:not(.rc-picker-disabled) .rc-picker-clear {
opacity: 1;
}
.rc-picker-clear:hover {
color: var(--color-gray-500);
background-color: var(--color-gray-100);
}
.rc-picker-clear-btn::after {
content: '×';
font-size: 1.2em;
line-height: 1;
}
/* =========================================
STATES (HOVER, FOCUS, DISABLED, ERROR)
========================================= */
/* Hover State */
.rc-picker:hover:not(.rc-picker-disabled) {
border-color: var(--input-border-hover);
}
/* Focused State */
.rc-picker-focused {
border-color: var(--focus-border-color);
box-shadow: 0 0 0 3px var(--focus-ring-color);
z-index: 1;
}
/* Disabled State */
.rc-picker-disabled {
background-color: var(--disabled-bg);
color: var(--disabled-text);
border-color: var(--disabled-border);
cursor: not-allowed;
}
.rc-picker-disabled input {
cursor: not-allowed;
}
/* Range Picker Specific */
.rc-picker-range.rc-picker-focused .rc-picker-active-bar {
opacity: 1;
}
.rc-picker-range-separator {
align-items: center;
padding: 0 8px;
line-height: 1;
color: var(--color-gray-400);
display: inline-flex;
}
.rc-picker-active-bar {
bottom: -1px;
height: 2px;
margin-left: var(--input-padding-x); /* Align with input start */
background: var(--color-brand-500);
opacity: 0;
transition: all 0.3s ease-out;
pointer-events: none;
z-index: 2;
position: absolute;
left: 0;
width: auto;
}
/* =========================================
PICKER DROPDOWN PANEL (THE MEAT)
========================================= */
.rc-picker-dropdown {
position: absolute;
z-index: 1050;
box-sizing: border-box;
font-size: var(--text-sm);
font-family: inherit;
}
.rc-picker-dropdown-hidden {
display: none;
}
/* Arrow styling removed for modern clean look (AntD v5/v6 typically removes large arrows) */
.rc-picker-dropdown .rc-picker-range-arrow {
display: none;
}
/* Panel Container */
.rc-picker-panel-container {
overflow: hidden;
vertical-align: top;
background: #fff;
border-radius: var(--radius-lg);
/* Elevation 3/4 equivalent */
box-shadow:
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
transition: margin 0.3s;
border: 1px solid var(--color-gray-100);
}
.rc-picker-panel-layout {
display: flex;
flex-wrap: nowrap;
align-items: stretch;
}
.rc-picker-panel {
vertical-align: top;
background: transparent;
display: inline-flex;
flex-direction: column;
}
/* Double Panel (Range Picker) */
.rc-picker-panel + .rc-picker-panel {
border-left: 1px solid var(--color-gray-100);
}
/* =========================================
HEADER SECTION
========================================= */
.rc-picker-header {
display: flex;
padding: 0 8px;
border-bottom: 1px solid var(--color-gray-100);
min-height: 40px;
align-items: center;
justify-content: space-between;
}
.rc-picker-header > * {
flex: none;
}
.rc-picker-header-view {
flex: auto;
text-align: center;
font-weight: var(--font-weight-semibold);
display: flex;
justify-content: center;
gap: 4px;
}
.rc-picker-header-view button {
color: var(--color-gray-800);
font-weight: inheriting;
padding: 0 4px;
background: transparent;
border: 0;
cursor: pointer;
font-size: var(--text-sm);
line-height: 40px;
transition: color 0.2s;
}
.rc-picker-header-view button:hover {
color: var(--color-brand-500);
}
/* Navigation Buttons (< << >> >) */
.rc-picker-header-super-prev-btn,
.rc-picker-header-prev-btn,
.rc-picker-header-next-btn,
.rc-picker-header-super-next-btn {
min-width: 1.6em;
font-size: 14px;
color: var(--color-gray-400);
cursor: pointer;
background: transparent;
border: 0;
line-height: 40px;
padding: 0 5px;
display: inline-flex;
justify-content: center;
align-items: center;
transition: color 0.2s;
position: relative;
}
.rc-picker-header-super-prev-btn:hover,
.rc-picker-header-prev-btn:hover,
.rc-picker-header-next-btn:hover,
.rc-picker-header-super-next-btn:hover {
color: var(--color-gray-900);
}
/* Arrow Symbols using pseudo elements if needed, or rely on RC Picker default text symbols but styled */
/* RC Picker default uses unicode arrows usually. */
/* =========================================
BODY SECTION (CALENDAR GRID)
========================================= */
.rc-picker-body {
padding: 8px 12px;
}
.rc-picker-content {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.rc-picker-content th,
.rc-picker-content td {
position: relative;
min-width: 24px;
font-weight: 400;
}
.rc-picker-content th {
height: 32px;
color: var(--color-gray-500);
line-height: 32px;
text-align: center;
}
.rc-picker-cell {
padding: 4px 0;
color: var(--color-gray-400);
cursor: pointer;
}
.rc-picker-cell-in-view {
color: var(--color-gray-800);
}
.rc-picker-cell-disabled {
cursor: not-allowed;
color: var(--disabled-text);
}
.rc-picker-cell-disabled .rc-picker-cell-inner {
background: var(--disabled-bg);
}
/* Inner Cell (The Circle/Square) */
.rc-picker-cell-inner {
position: relative;
z-index: 2;
display: inline-block;
min-width: 24px;
height: 24px;
line-height: 24px;
border-radius: var(
--radius-md
); /* Ant Design v5 uses slightly rounded squares, not full circles usually, but we can do md radius */
transition:
background 0.2s,
border 0.2s,
color 0.2s;
text-align: center;
margin: 0 auto;
padding: 0 4px; /* For longer text like months */
}
/* Month/Year Panel cells are wider */
.rc-picker-month-panel .rc-picker-cell-inner,
.rc-picker-year-panel .rc-picker-cell-inner,
.rc-picker-quarter-panel .rc-picker-cell-inner,
.rc-picker-decade-panel .rc-picker-cell-inner {
width: auto;
padding: 0 12px;
min-width: 48px;
}
/* Hover State */
.rc-picker-cell:hover:not(.rc-picker-cell-selected):not(.rc-picker-cell-range-start):not(
.rc-picker-cell-range-end
):not(.rc-picker-cell-disabled)
.rc-picker-cell-inner {
background: var(--color-gray-100);
}
/* Selected Date */
.rc-picker-cell-selected .rc-picker-cell-inner,
.rc-picker-cell-range-start .rc-picker-cell-inner,
.rc-picker-cell-range-end .rc-picker-cell-inner {
color: #fff;
background: var(--color-brand-500);
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
}
.rc-picker-cell-selected:hover .rc-picker-cell-inner {
background: var(--color-brand-600);
}
/* Today */
.rc-picker-cell-today .rc-picker-cell-inner::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
border: 1px solid var(--color-brand-500);
border-radius: var(--radius-md);
content: '';
transition: all 0.2s;
}
.rc-picker-cell-selected.rc-picker-cell-today .rc-picker-cell-inner::before {
border-color: #fff; /* White border inside selected today */
}
/* =========================================
RANGE SELECTION STYLING
========================================= */
/* The strip between start and end */
.rc-picker-cell-in-range > .rc-picker-cell-inner {
background: var(--color-brand-50);
border-radius: 0;
min-width: 100%; /* Fill the whole cell width */
color: var(--color-gray-800);
}
.rc-picker-cell-in-range::before {
background: var(--color-brand-50);
}
/* Range Edge Adjustments */
.rc-picker-cell-range-start > .rc-picker-cell-inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: var(--radius-md);
border-bottom-left-radius: var(--radius-md);
}
.rc-picker-cell-range-end > .rc-picker-cell-inner {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
/* Ghost/Pseudo elements for continuous range background */
/* RC Picker handles this logic via class combinations.
We need to ensure the background flows nicely. */
.rc-picker-cell-range-start.rc-picker-cell-range-end .rc-picker-cell-inner {
border-radius: var(--radius-md);
}
/* Range Hover (when selecting the second date) */
.rc-picker-cell-range-hover-start::after,
.rc-picker-cell-range-hover-end::after,
.rc-picker-cell-range-hover::after {
content: ''; /* Should show dotted line or lighter highlight */
position: absolute;
top: 50%;
left: 0;
right: 0;
bottom: 0;
border-top: 2px dashed var(--color-brand-300);
transform: translateY(-50%);
z-index: 0;
display: none; /* AntD v6 doesn't really emphasize this as much, can keep simple */
}
/* =========================================
TIME PANEL
========================================= */
.rc-picker-time-panel {
border-left: 1px solid var(--color-gray-100);
width: auto;
}
.rc-picker-time-panel .rc-picker-content {
display: flex;
max-height: 224px;
height: 224px;
}
.rc-picker-time-panel-column {
flex: 1 0 auto;
width: 60px; /* Wider for easier clicking */
margin: 0;
padding: 0;
overflow-y: hidden;
text-align: left;
list-style: none;
transition: background 0.3s;
overflow-x: hidden;
border-left: 1px solid var(--color-gray-100); /* Separator between hrs/min/sec */
}
.rc-picker-time-panel-column:first-child {
border-left: 0;
}
.rc-picker-time-panel-column:hover {
overflow-y: auto;
}
.rc-picker-time-panel-column > li {
margin: 0;
padding: 0;
}
.rc-picker-time-panel-column > li .rc-picker-time-panel-cell-inner {
display: block;
width: 100%;
height: 28px;
margin: 0;
padding: 0 0 0 12px;
color: var(--color-gray-800);
line-height: 28px;
text-align: left;
cursor: pointer;
transition: background 0.2s;
}
.rc-picker-time-panel-column > li:hover .rc-picker-time-panel-cell-inner {
background: var(--color-gray-100);
}
.rc-picker-time-panel-column > li.rc-picker-time-panel-cell-selected .rc-picker-time-panel-cell-inner {
background: var(--color-brand-50);
color: var(--color-brand-600);
font-weight: 600;
}
.rc-picker-time-panel-column > li.rc-picker-time-panel-cell-disabled .rc-picker-time-panel-cell-inner {
color: var(--disabled-text);
cursor: not-allowed;
background: transparent;
}
/* Date + Time Layout */
.rc-picker-datetime-panel {
display: flex;
}
.rc-picker-datetime-panel .rc-picker-date-panel,
.rc-picker-datetime-panel .rc-picker-time-panel {
transition: opacity 0.3s;
}
/* =========================================
FOOTER
========================================= */
.rc-picker-footer {
width: auto;
min-width: 100%;
line-height: 38px;
text-align: center;
border-top: 1px solid var(--color-gray-100);
padding: 0 12px;
}
.rc-picker-footer-extra {
text-align: left;
}
/* Ranges (Presets) */
.rc-picker-ranges {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
display: flex;
justify-content: space-between;
align-items: center;
}
.rc-picker-ranges > li {
display: inline-block;
}
.rc-picker-ranges .rc-picker-preset > .rc-picker-preset-tag {
display: inline-block;
margin-right: 8px;
color: var(--color-brand-500);
cursor: pointer;
transition: color 0.2s;
}
.rc-picker-ranges .rc-picker-preset > .rc-picker-preset-tag:hover {
color: var(--color-brand-600);
}
.rc-picker-ranges .rc-picker-ok {
float: right;
}
/* OK Button */
.rc-picker-ok button {
background: var(--color-brand-500);
color: #fff;
border: none;
border-radius: var(--radius-sm);
padding: 2px 12px;
height: 24px;
cursor: pointer;
font-size: var(--text-sm);
transition: background 0.2s;
}
.rc-picker-ok button:hover {
background: var(--color-brand-600);
}
.rc-picker-ok button:disabled {
background: var(--disabled-bg);
color: var(--disabled-text);
cursor: not-allowed;
}
.rc-picker-now-btn {
color: var(--color-brand-500);
cursor: pointer;
transition: color 0.2s;
}
.rc-picker-now-btn:hover {
color: var(--color-brand-600);
}
/* =========================================
STATUS MODIFIERS (ERROR, WARNING, ETC)
========================================= */
.picker-status-error.rc-picker {
border-color: var(--color-error-500);
}
.picker-status-error.rc-picker:hover:not(.rc-picker-disabled) {
border-color: var(--color-error-600);
}
.picker-status-error.rc-picker.rc-picker-focused {
border-color: var(--color-error-600);
box-shadow: 0 0 0 3px var(--color-error-100);
}
.picker-status-warning.rc-picker {
border-color: var(--color-warning-500);
}
.picker-status-warning.rc-picker:hover:not(.rc-picker-disabled) {
border-color: var(--color-warning-600);
}
.picker-status-warning.rc-picker.rc-picker-focused {
border-color: var(--color-warning-600);
box-shadow: 0 0 0 3px var(--color-warning-100);
}
.picker-status-success.rc-picker {
border-color: var(--color-success-500);
}
.picker-status-success.rc-picker:hover:not(.rc-picker-disabled) {
border-color: var(--color-success-600);
}
.picker-status-success.rc-picker.rc-picker-focused {
border-color: var(--color-success-600);
box-shadow: 0 0 0 3px var(--color-success-100);
}
}
@@ -1,746 +0,0 @@
@layer components {
/* ==========================================================================
Ant Design DatePicker - Pure CSS Implementation
========================================================================== */
/* --------------------------------------------------------------------------
Variables & Tokens
-------------------------------------------------------------------------- */
:root {
/* Colors */
--ant-color-primary: #1677ff;
--ant-color-primary-hover: #4096ff;
--ant-color-primary-active: #0958d9;
--ant-color-primary-bg: #e6f4ff;
--ant-color-primary-border: #91caff;
--ant-color-bg-container: #ffffff;
--ant-color-bg-elevated: #ffffff;
--ant-color-bg-layout: #f5f5f5;
--ant-color-bg-mask: rgba(0, 0, 0, 0.45);
--ant-color-bg-spotlight: rgba(0, 0, 0, 0.85);
--ant-color-text: rgba(0, 0, 0, 0.88);
--ant-color-text-secondary: rgba(0, 0, 0, 0.65);
--ant-color-text-tertiary: rgba(0, 0, 0, 0.45);
--ant-color-text-quaternary: rgba(0, 0, 0, 0.25);
--ant-color-text-placeholder: rgba(0, 0, 0, 0.25);
--ant-color-text-disabled: rgba(0, 0, 0, 0.25);
--ant-color-text-heading: rgba(0, 0, 0, 0.88);
--ant-color-text-light-solid: #ffffff;
--ant-color-border: #d9d9d9;
--ant-color-split: rgba(5, 5, 5, 0.06);
--ant-color-error: #ff4d4f;
--ant-color-warning: #faad14;
/* Dimensions & Spacing */
--ant-control-height: 32px;
--ant-control-height-sm: 24px;
--ant-control-height-lg: 40px;
--ant-padding-xs: 8px;
--ant-padding-sm: 12px;
--ant-padding-md: 16px;
--ant-padding-xxs: 4px;
--ant-margin-xs: 8px;
--ant-margin-xxs: 4px;
--ant-border-radius: 6px;
--ant-border-radius-sm: 4px;
--ant-border-radius-lg: 8px;
--ant-line-width: 1px;
--ant-line-width-bold: 2px;
--ant-line-type: solid;
/* Font */
--ant-font-size: 14px;
--ant-font-size-sm: 12px;
--ant-font-size-lg: 16px;
--ant-line-height: 1.5714285714285714;
/* Animation */
--ant-motion-duration-mid: 0.2s;
--ant-motion-duration-slow: 0.3s;
/* Shadows */
--ant-box-shadow:
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
--ant-box-shadow-secondary:
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
/* Picker Specific Tokens */
--ant-picker-z-index-popup: 1050;
--ant-picker-cell-hover-bg: rgba(0, 0, 0, 0.04);
--ant-picker-cell-active-with-range-bg: var(--ant-color-primary-bg);
--ant-picker-basic-cell-hover-with-range-bg: #cce9ff; /* lighten(primary, 35) approx */
--ant-picker-date-hover-range-border-color: #7ec1ff; /* lighten(primary, 20) approx */
--ant-picker-panel-width: 280px; /* cellWidth * 7 + padding */
--ant-picker-year-month-cell-width: 60px;
--ant-picker-time-column-width: 56px;
--ant-picker-time-column-height: 224px;
--ant-picker-time-cell-height: 28px;
--ant-picker-cell-width: 36px;
--ant-picker-cell-height: 24px;
--ant-picker-text-height: 40px;
}
/* --------------------------------------------------------------------------
Base Styles (.rc-picker)
-------------------------------------------------------------------------- */
.rc-picker {
box-sizing: border-box;
margin: 0;
padding: 0;
color: var(--ant-color-text);
font-size: var(--ant-font-size);
line-height: var(--ant-line-height);
list-style: none;
position: relative;
display: inline-flex;
align-items: center;
background: var(--ant-color-bg-container);
border: var(--ant-line-width) var(--ant-line-type) var(--ant-color-border);
border-radius: var(--ant-border-radius);
transition:
border var(--ant-motion-duration-mid),
box-shadow var(--ant-motion-duration-mid);
padding: 4px 11px;
}
/* Base States */
.rc-picker:hover,
.rc-picker-focused {
border-color: var(--ant-color-primary);
border-inline-end-width: var(--ant-line-width);
}
.rc-picker-focused {
box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
outline: 0;
}
.rc-picker.rc-picker-disabled {
background: var(--ant-color-bg-layout);
border-color: var(--ant-color-border);
color: var(--ant-color-text-disabled);
cursor: not-allowed;
}
.rc-picker.rc-picker-disabled .rc-picker-suffix {
color: var(--ant-color-text-disabled);
}
/* Clean Input Style */
.rc-picker-input {
position: relative;
display: inline-flex;
align-items: center;
width: 100%;
}
.rc-picker-input > input {
position: relative;
display: inline-block;
width: 100%;
min-width: 1px; /* Firefox flex fix */
padding: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
background: transparent;
border: 0;
outline: 0;
transition: all var(--ant-motion-duration-mid);
flex: auto;
height: auto;
}
.rc-picker-input > input::placeholder {
color: var(--ant-color-text-placeholder);
}
.rc-picker-input > input:disabled {
color: var(--ant-color-text-disabled);
background: transparent;
cursor: not-allowed;
}
/* Suffix & Clear Icon */
.rc-picker-suffix {
display: flex;
flex: none;
align-self: center;
margin-inline-start: 4px;
color: var(--ant-color-text-quaternary);
line-height: 1;
pointer-events: none;
transition:
opacity var(--ant-motion-duration-mid),
color var(--ant-motion-duration-mid);
}
.rc-picker-clear {
position: absolute;
top: 50%;
inset-inline-end: 0;
color: var(--ant-color-text-quaternary);
line-height: 1;
background: var(--ant-color-bg-container);
transform: translateY(-50%);
cursor: pointer;
opacity: 0;
transition:
opacity var(--ant-motion-duration-mid),
color var(--ant-motion-duration-mid);
z-index: 1;
}
.rc-picker-clear:hover {
color: var(--ant-color-text-tertiary);
}
.rc-picker:hover .rc-picker-clear {
opacity: 1;
}
/* Hide suffix when clear is shown on hover (approximate behavior) */
.rc-picker:hover .rc-picker-clear + .rc-picker-suffix {
opacity: 0;
}
/* Sizes */
.rc-picker-large {
padding: 6.5px 11px;
font-size: var(--ant-font-size-lg);
border-radius: var(--ant-border-radius-lg);
}
.rc-picker-large .rc-picker-input > input {
font-size: var(--ant-font-size-lg);
}
.rc-picker-small {
padding: 0px 7px;
border-radius: var(--ant-border-radius-sm);
}
/* --------------------------------------------------------------------------
Range Picker Specifics
-------------------------------------------------------------------------- */
.rc-picker-range {
position: relative;
display: inline-flex;
}
.rc-picker-range .rc-picker-clear {
inset-inline-end: 11px; /* padding inline */
}
.rc-picker-range.rc-picker-small .rc-picker-clear {
inset-inline-end: 7px;
}
.rc-picker-range .rc-picker-active-bar {
bottom: -1px;
height: 2px;
margin-inline-start: 11px;
background: var(--ant-color-primary);
opacity: 0;
transition: all var(--ant-motion-duration-slow) ease-out;
pointer-events: none;
position: absolute;
left: 0;
width: 0; /* Dynamic width managed by JS usually, but styled here */
}
.rc-picker-range.rc-picker-focused .rc-picker-active-bar {
opacity: 1;
}
.rc-picker-range-separator {
align-items: center;
padding: 0 8px;
line-height: 1;
color: var(--ant-color-text-quaternary);
display: inline-flex;
}
/* --------------------------------------------------------------------------
Dropdown Panel
-------------------------------------------------------------------------- */
.rc-picker-dropdown {
box-sizing: border-box;
margin: 0;
padding: 0;
color: var(--ant-color-text);
font-size: var(--ant-font-size);
list-style: none;
position: absolute;
z-index: var(--ant-picker-z-index-popup);
top: -9999px;
left: -9999px;
}
.rc-picker-dropdown-hidden {
display: none;
}
.rc-picker-panel-container {
overflow: hidden;
vertical-align: top;
background: var(--ant-color-bg-elevated);
border-radius: var(--ant-border-radius-lg);
box-shadow: var(--ant-box-shadow-secondary);
transition: margin var(--ant-motion-duration-slow);
display: inline-block;
pointer-events: auto;
}
/* Layout */
.rc-picker-panel-layout {
display: flex;
flex-wrap: nowrap;
align-items: stretch;
}
.rc-picker-panels {
display: inline-flex;
flex-wrap: nowrap;
}
.rc-picker-panel {
display: inline-flex;
flex-direction: column;
text-align: center;
background: transparent;
outline: none;
vertical-align: top;
}
/* Header */
.rc-picker-header {
display: flex;
padding: 0 8px;
color: var(--ant-color-text-heading);
border-bottom: var(--ant-line-width) var(--ant-line-type) var(--ant-color-split);
min-width: var(--ant-picker-panel-width);
}
.rc-picker-header > * {
flex: none;
}
.rc-picker-header button {
padding: 0;
color: var(--ant-color-text-quaternary);
line-height: var(--ant-picker-text-height);
background: transparent;
border: 0;
cursor: pointer;
transition: color var(--ant-motion-duration-mid);
font-size: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.6em;
}
.rc-picker-header button:hover {
color: var(--ant-color-text);
}
.rc-picker-header-view {
flex: auto;
font-weight: 600; /* Strong */
line-height: var(--ant-picker-text-height);
display: flex;
justify-content: center;
}
.rc-picker-header-view button {
color: inherit;
font-weight: inherit;
min-width: auto;
padding: 0 4px;
}
.rc-picker-header-view button:hover {
color: var(--ant-color-primary);
}
/* Header Icons (Arrows) */
.rc-picker-prev-icon,
.rc-picker-next-icon,
.rc-picker-super-prev-icon,
.rc-picker-super-next-icon {
position: relative;
display: inline-block;
width: 7px;
height: 7px;
}
.rc-picker-prev-icon::before,
.rc-picker-next-icon::before,
.rc-picker-super-prev-icon::before,
.rc-picker-super-next-icon::before {
position: absolute;
top: 0;
left: 0;
width: 7px;
height: 7px;
border: 0 solid currentColor;
border-block-start-width: 1.5px;
border-inline-start-width: 1.5px;
content: '';
}
.rc-picker-super-prev-icon::after,
.rc-picker-super-next-icon::after {
position: absolute;
top: 4px;
left: 4px;
display: inline-block;
width: 7px;
height: 7px;
border: 0 solid currentColor;
border-block-start-width: 1.5px;
border-inline-start-width: 1.5px;
content: '';
}
.rc-picker-prev-icon,
.rc-picker-super-prev-icon {
transform: rotate(-45deg);
}
.rc-picker-next-icon,
.rc-picker-super-next-icon {
transform: rotate(135deg);
}
/* Body */
.rc-picker-body {
padding: 8px 12px;
}
.rc-picker-content {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.rc-picker-content th,
.rc-picker-content td {
position: relative;
min-width: var(--ant-picker-cell-width);
font-weight: normal;
}
.rc-picker-content th {
height: 30px;
color: var(--ant-color-text);
vertical-align: middle;
}
/* Cells */
.rc-picker-cell {
padding: 3px 0;
color: var(--ant-color-text-disabled);
cursor: pointer;
}
.rc-picker-cell-in-view {
color: var(--ant-color-text);
}
.rc-picker-cell::before {
position: absolute;
top: 50%;
left: 0;
right: 0;
z-index: 1;
height: var(--ant-picker-cell-height);
transform: translateY(-50%);
content: '';
pointer-events: none;
}
.rc-picker-cell-inner {
position: relative;
z-index: 2;
display: inline-block;
min-width: var(--ant-picker-cell-height);
height: var(--ant-picker-cell-height);
line-height: var(--ant-picker-cell-height);
border-radius: var(--ant-border-radius-sm);
transition: background-color var(--ant-motion-duration-mid);
}
/* Cell States */
.rc-picker-cell:hover:not(.rc-picker-cell-in-view):not(.rc-picker-cell-disabled) .rc-picker-cell-inner,
.rc-picker-cell:hover:not(.rc-picker-cell-selected):not(.rc-picker-cell-range-start):not(
.rc-picker-cell-range-end
):not(.rc-picker-cell-disabled)
.rc-picker-cell-inner {
background: var(--ant-picker-cell-hover-bg);
}
/* Today */
.rc-picker-cell-in-view.rc-picker-cell-today .rc-picker-cell-inner::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
border: 1px solid var(--ant-color-primary);
border-radius: var(--ant-border-radius-sm);
content: '';
}
/* Selected / Range Start / Range End */
.rc-picker-cell-in-view.rc-picker-cell-selected .rc-picker-cell-inner,
.rc-picker-cell-in-view.rc-picker-cell-range-start .rc-picker-cell-inner,
.rc-picker-cell-in-view.rc-picker-cell-range-end .rc-picker-cell-inner {
color: var(--ant-color-text-light-solid);
background: var(--ant-color-primary);
}
/* In Range Background */
.rc-picker-cell-in-view.rc-picker-cell-in-range::before,
.rc-picker-cell-in-view.rc-picker-cell-range-start::before,
.rc-picker-cell-in-view.rc-picker-cell-range-end::before {
background: var(--ant-picker-cell-active-with-range-bg);
}
.rc-picker-cell-in-view.rc-picker-cell-range-start:not(.rc-picker-cell-range-end) .rc-picker-cell-inner {
border-start-end-radius: 0;
border-end-end-radius: 0;
}
.rc-picker-cell-in-view.rc-picker-cell-range-end:not(.rc-picker-cell-range-start) .rc-picker-cell-inner {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
/* Disabled Cell */
.rc-picker-cell-disabled {
color: var(--ant-color-text-disabled);
cursor: not-allowed;
}
.rc-picker-cell-disabled .rc-picker-cell-inner {
background: transparent;
}
.rc-picker-cell-disabled::before {
background: var(--ant-color-bg-layout);
}
/* --------------------------------------------------------------------------
Special Panels (Year, Quarter, Month, Decade)
-------------------------------------------------------------------------- */
.rc-picker-year-panel .rc-picker-body,
.rc-picker-quarter-panel .rc-picker-body,
.rc-picker-month-panel .rc-picker-body {
padding: 0 8px;
}
.rc-picker-year-panel .rc-picker-cell-inner,
.rc-picker-quarter-panel .rc-picker-cell-inner,
.rc-picker-month-panel .rc-picker-cell-inner {
width: var(--ant-picker-year-month-cell-width);
padding: 0 8px;
}
.rc-picker-decade-panel .rc-picker-cell-inner {
padding: 0 4px;
}
.rc-picker-decade-panel .rc-picker-cell::before {
display: none;
}
/* --------------------------------------------------------------------------
Time Panel
-------------------------------------------------------------------------- */
.rc-picker-time-panel {
display: flex;
flex-direction: column;
width: auto;
min-width: auto;
}
.rc-picker-time-panel .rc-picker-content {
display: flex;
flex: auto;
height: 224px;
overflow: hidden;
}
.rc-picker-time-panel-column {
flex: 1 0 auto;
width: 56px;
margin: 4px 0;
padding: 0;
overflow-y: hidden;
text-align: start;
list-style: none;
transition: background-color var(--ant-motion-duration-mid);
overflow-x: hidden;
border-left: 1px solid var(--ant-color-split);
}
.rc-picker-time-panel-column:first-child {
border-left: none;
}
.rc-picker-time-panel-column:hover {
overflow-y: auto;
}
.rc-picker-time-panel-column > li {
margin: 0;
padding: 0;
}
.rc-picker-time-panel-cell .rc-picker-time-panel-cell-inner {
display: block;
width: 100%;
height: 28px;
margin: 0;
padding: 0 0 0 12px;
color: var(--ant-color-text);
line-height: 28px;
border-radius: var(--ant-border-radius-sm);
cursor: pointer;
transition: background-color var(--ant-motion-duration-mid);
}
.rc-picker-time-panel-cell:hover .rc-picker-time-panel-cell-inner {
background: var(--ant-picker-cell-hover-bg);
}
.rc-picker-time-panel-cell-selected .rc-picker-time-panel-cell-inner {
background: var(--ant-picker-cell-active-with-range-bg);
}
/* --------------------------------------------------------------------------
Footer
-------------------------------------------------------------------------- */
.rc-picker-footer {
width: min-content;
min-width: 100%;
line-height: 38px;
text-align: center;
border-top: 1px solid var(--ant-color-split);
}
.rc-picker-footer-extra {
padding: 0 12px;
line-height: 38px;
text-align: start;
}
.rc-picker-ranges {
margin: 0;
padding: 4px 12px;
overflow: hidden;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.rc-picker-now-btn {
color: var(--ant-color-primary);
cursor: pointer;
}
.rc-picker-ok {
margin-inline-start: auto;
}
/* --------------------------------------------------------------------------
Variants
-------------------------------------------------------------------------- */
.rc-picker-borderless {
background: transparent;
border-color: transparent;
box-shadow: none !important;
}
.rc-picker-filled {
background: var(--ant-color-bg-layout);
border-color: transparent;
}
.rc-picker-filled:hover,
.rc-picker-filled.rc-picker-focused {
background: var(--ant-color-bg-container);
border-color: var(--ant-color-primary);
}
/* --------------------------------------------------------------------------
Status
-------------------------------------------------------------------------- */
.rc-picker-status-error {
border-color: var(--ant-color-error) !important;
}
.rc-picker-status-error.rc-picker-focused {
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);
}
.rc-picker-status-warning {
border-color: var(--ant-color-warning) !important;
}
.rc-picker-status-warning.rc-picker-focused {
box-shadow: 0 0 0 2px rgba(250, 173, 20, 0.2);
}
/* --------------------------------------------------------------------------
Multiple
-------------------------------------------------------------------------- */
.rc-picker-multiple .rc-picker-selector {
display: flex;
flex-wrap: wrap;
align-items: center;
width: 100%;
padding: 1px 4px;
}
.rc-picker-multiple .rc-picker-selection-item {
display: flex;
align-items: center;
height: 24px;
margin-top: 2px;
margin-bottom: 2px;
margin-inline-end: 4px;
padding-inline-start: 8px;
padding-inline-end: 4px;
background: var(--ant-color-bg-layout);
border-radius: var(--ant-border-radius-sm);
cursor: default;
line-height: 22px;
}
.rc-picker-multiple .rc-picker-selection-item-remove {
color: var(--ant-color-text-quaternary);
font-size: 10px;
cursor: pointer;
display: inline-flex;
margin-left: 4px;
transition: color var(--ant-motion-duration-mid);
}
.rc-picker-multiple .rc-picker-selection-item-remove:hover {
color: var(--ant-color-text-tertiary);
}
}
@@ -1,398 +0,0 @@
@layer components {
/* =========================================
BASE WRAPPER & LABEL
========================================= */
.rc-switch-wrapper {
display: inline-flex;
align-items: center;
cursor: pointer;
font-size: var(--form-label-font-size);
font-weight: var(--form-label-font-weight);
color: var(--form-label-color);
line-height: 1;
gap: 8px; /* Distance between switch and label */
transition: all 0.2s;
vertical-align: middle;
}
.rc-switch-wrapper-disabled {
cursor: not-allowed;
color: var(--disabled-text);
}
.rc-switch-label-text {
line-height: var(--switch-height); /* Align text vertically with switch */
font-size: var(--form-label-font-size);
font-weight: var(--form-label-font-weight);
color: var(--form-label-color);
}
/* Container for relative positioning of loading state */
.rc-switch-container {
position: relative;
line-height: 0;
}
/* =========================================
CORE SWITCH STRUCTURE
========================================= */
.rc-switch {
position: relative;
display: inline-block;
box-sizing: border-box;
width: var(--switch-width);
height: var(--switch-height);
line-height: calc(var(--switch-height) - 2px);
padding: 0;
vertical-align: middle;
border-radius: 9999px; /* Pill shape */
border: 1px solid; /* Border color defined in variants */
background-color: transparent; /* Bg defined in variants */
cursor: pointer;
transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
overflow: visible; /* Changed to visible for focus rings or overflow effects if needed */
}
/* Focus Ring (Consistent with Checkbox) */
.rc-switch:focus-visible,
.rc-switch:focus {
outline: none;
box-shadow: 0 0 0 3px var(--focus-ring-color);
border-color: var(--focus-border-color);
}
/* =========================================
HANDLE (THE CIRCLE)
========================================= */
.rc-switch:after {
position: absolute;
content: ' ';
top: 1px; /* (Height - Handle) / 2 approx, adjusted for border */
left: var(--switch-padding);
width: var(--switch-handle-size);
height: var(--switch-handle-size);
border-radius: 50%;
background-color: #ffffff;
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
transform: scale(1);
transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
cursor: pointer;
}
/* Hover Effect on Handle */
.rc-switch:hover:after {
transform: scale(1.1);
}
.rc-switch:active:after {
transform: scale(1); /* Reset on click */
width: calc(var(--switch-handle-size) + 4px); /* Stretch effect like iOS */
}
/* =========================================
CHECKED STATE
========================================= */
.rc-switch-checked:after {
left: var(--switch-checked-pos);
}
.rc-switch-checked:active:after {
left: calc(var(--switch-checked-pos) - 4px); /* Adjust stretch position */
}
/* =========================================
INNER TEXT (ON/OFF LABELS)
========================================= */
.rc-switch-inner {
display: block;
box-sizing: border-box;
width: 100%; /* Make sure to take the full width of the container */
/* SETTING DEFAULT (STATE OFF / UNCHECKED) */
/* Handle on the Left -> Text must be on the Right */
/* Margin: Top | Right (Edge) | Bottom | Left (Handle) */
margin: 0 var(--switch-inner-indent-edge) 0 var(--switch-inner-indent-handle);
/* Typography */
font-size: 11px;
font-weight: 600;
line-height: calc(var(--switch-height) - 2px);
text-align: center;
/* Behavior */
pointer-events: none;
white-space: nowrap;
overflow: hidden;
/* Transition: Shift margin and change color */
transition:
margin 0.3s cubic-bezier(0.35, 0, 0.25, 1),
color 0.3s;
/* Default Color (OFF) */
color: var(--color-gray-500);
}
.rc-switch-checked .rc-switch-inner {
/* Handle on the Right -> Text must be on the Left */
/* Margin: Top | Right (Handle) | Bottom | Left (Edge) */
margin: 0 var(--switch-inner-indent-handle) 0 var(--switch-inner-indent-edge);
/* Color ON */
color: #ffffff;
}
/* Text Color When ON -> Always White (because the background is colored) */
.rc-switch-checked .rc-switch-inner,
.rc-switch-checked .rc-switch-inner {
color: #ffffff;
}
/* =========================================
LOADING STATE
========================================= */
.rc-switch-loading {
opacity: 0.7;
}
/* The SVG Icon inside the handle */
.rc-switch-loading-icon {
display: inline-block;
vertical-align: middle;
width: 12px;
height: 12px;
color: var(--color-brand-500); /* Loading icon color */
animation: rcSwitchLoading 1s infinite linear;
/* Center inside the handle */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
/* Override handle style when loading */
.rc-switch-loading .rc-switch:after {
background-color: #fff; /* Ensure handle is white */
}
@keyframes rcSwitchLoading {
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
/* =========================================
STATUS COLORS (Applied to Checked State)
========================================= */
/* --- DEFAULT (Brand) --- */
.switch-status-default .rc-switch {
background-color: var(--color-gray-50);
border-color: var(--color-gray-200);
}
.switch-status-default .rc-switch:hover {
border-color: var(--color-brand-500);
}
.switch-status-default .rc-switch-checked {
border-color: var(--color-brand-500);
background-color: var(--color-brand-500);
}
.switch-status-default .rc-switch:focus-visible,
.switch-status-default .rc-switch:focus {
box-shadow: 0 0 0 3px var(--color-brand-100);
border-color: var(--color-brand-500);
}
/* --- ERROR --- */
/* Hover Off state (Optional: Tint border red on hover even if off) */
.switch-status-error .rc-switch {
background-color: var(--color-gray-50);
border-color: var(--color-error-300);
}
.switch-status-error .rc-switch:hover {
border-color: var(--color-error-500);
}
.switch-status-error .rc-switch:after {
background-color: #fff;
}
/* Checked State */
.switch-status-error.switch-variant-outlined .rc-switch-checked {
border-color: var(--color-error-500);
background-color: var(--color-error-500);
}
.switch-status-error.switch-variant-outlined .rc-switch-checked:after {
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
}
.switch-status-error .rc-switch-checked {
border-color: var(--color-error-500);
background-color: var(--color-error-500);
}
/* Focus */
.switch-status-error .rc-switch:focus-visible,
.switch-status-error .rc-switch:focus {
box-shadow: 0 0 0 3px var(--color-error-100);
border-color: var(--color-error-500);
}
/* Loading Icon Color */
.switch-status-error .rc-switch-loading-icon {
color: var(--color-error-500);
}
/* --- SUCCESS --- */
.switch-status-success .rc-switch {
background-color: var(--color-gray-50);
border-color: var(--color-success-300);
}
.switch-status-success .rc-switch:hover {
border-color: var(--color-success-500);
}
.switch-status-success .rc-switch:after {
background-color: #fff;
}
/* Checked State */
.switch-status-success.switch-variant-outlined .rc-switch-checked {
border-color: var(--color-success-500);
background-color: var(--color-success-500);
}
.switch-status-success.switch-variant-outlined .rc-switch-checked:after {
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
}
.switch-status-success .rc-switch-checked {
border-color: var(--color-success-500);
background-color: var(--color-success-500);
}
/* Focus */
.switch-status-success .rc-switch:focus-visible,
.switch-status-success .rc-switch:focus {
box-shadow: 0 0 0 3px var(--color-success-100);
border-color: var(--color-success-500);
}
/* Loading Icon Color */
.switch-status-success .rc-switch-loading-icon {
color: var(--color-success-500);
}
/* --- WARNING --- */
.switch-status-warning .rc-switch {
background-color: var(--color-gray-50);
border-color: var(--color-warning-300);
}
.switch-status-warning .rc-switch:hover {
border-color: var(--color-warning-500);
}
.switch-status-warning .rc-switch:after {
background-color: #fff;
}
/* Checked State */
.switch-status-warning.switch-variant-outlined .rc-switch-checked {
border-color: var(--color-warning-500);
background-color: var(--color-warning-500);
}
.switch-status-warning.switch-variant-outlined .rc-switch-checked:after {
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
}
.switch-status-warning .rc-switch-checked {
border-color: var(--color-warning-500);
background-color: var(--color-warning-500);
}
/* Focus */
.switch-status-warning .rc-switch:focus-visible,
.switch-status-warning .rc-switch:focus {
box-shadow: 0 0 0 3px var(--color-warning-100);
border-color: var(--color-warning-500);
}
/* Loading Icon Color */
.switch-status-warning .rc-switch-loading-icon {
color: var(--color-warning-500);
}
/* --- INFO --- */
.switch-status-info .rc-switch {
background-color: var(--color-gray-50);
border-color: var(--color-info-300);
}
.switch-status-info .rc-switch:hover {
border-color: var(--color-info-500);
}
.switch-status-info .rc-switch:after {
background-color: #fff;
}
/* Checked State */
.switch-status-info.switch-variant-outlined .rc-switch-checked {
border-color: var(--color-info-500);
background-color: var(--color-info-500);
}
.switch-status-info.switch-variant-outlined .rc-switch-checked:after {
background-color: #fff;
box-shadow: 0 2px 4px 0 rgba(0, 35, 11, 0.2);
}
.switch-status-info .rc-switch-checked {
border-color: var(--color-info-500);
background-color: var(--color-info-500);
}
/* Focus */
.switch-status-info .rc-switch:focus-visible,
.switch-status-info .rc-switch:focus {
box-shadow: 0 0 0 3px var(--color-info-100);
border-color: var(--color-info-500);
}
/* Loading Icon Color */
.switch-status-info .rc-switch-loading-icon {
color: var(--color-info-500);
}
/* =========================================
DISABLED STATE
========================================= */
.rc-switch-disabled,
.rc-switch-wrapper-disabled .rc-switch {
cursor: not-allowed;
opacity: 0.6;
background-color: var(--disabled-bg) !important;
border-color: var(--disabled-border) !important;
}
.rc-switch-disabled:after,
.rc-switch-wrapper-disabled .rc-switch:after {
cursor: not-allowed;
background-color: #f9fafb; /* Lighter white/gray for disabled knob */
}
/* If checked and disabled */
.rc-switch-disabled.rc-switch-checked {
background-color: var(--disabled-border) !important; /* Darker gray to show it is "on" but disabled */
}
/* Disable animations/hovers */
.rc-switch-disabled:hover:after {
transform: scale(1);
animation-name: none;
}
}
@@ -1,192 +0,0 @@
@layer components {
/* =========================================
BASE STYLE: TEXTAREA (Standalone)
========================================= */
.rc-textarea {
box-sizing: border-box;
display: block;
width: 100%;
/* Global Variables */
min-height: var(--textarea-min-height);
padding: var(--textarea-padding-y) var(--textarea-padding-x);
font-size: var(--input-font-size);
line-height: 1.5;
color: var(--input-text);
/* Default Base (akan di-override jika ada wrapper) */
background-color: var(--input-bg);
border: 1px solid transparent;
border-radius: var(--input-radius);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
resize: vertical;
}
.rc-textarea::placeholder {
color: var(--input-placeholder);
opacity: 1;
}
/* Disabled State (Standalone) */
.rc-textarea:disabled {
background-color: var(--disabled-bg) !important;
color: var(--disabled-text);
border-color: var(--disabled-border) !important;
cursor: not-allowed;
}
/* =========================================
WRAPPER SETUP (allowClear / showCount)
Component akan memindahkan class Variant & Status ke div ini
========================================= */
.rc-textarea-affix-wrapper {
width: 100%;
position: relative;
display: inline-block;
/* Wrapper harus punya base style border/bg agar status works */
border: 1px solid transparent;
border-radius: var(--input-radius);
background-color: var(--input-bg);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* RESET INNER TEXTAREA */
/* Saat dibungkus, textarea menjadi transparan, wrapper yang menangani warna */
.rc-textarea-affix-wrapper .rc-textarea {
background-color: transparent;
border: none;
outline: none;
box-shadow: none !important; /* Hapus shadow default inner */
/* Padding tetap di textarea agar text flow benar */
}
/* Clear Icon Position */
.rc-textarea-clear-icon {
margin: 0;
padding: 0;
background: none;
border: none;
outline: none;
cursor: pointer;
position: absolute;
top: var(--textarea-padding-y);
right: var(--textarea-padding-x);
z-index: 5;
font-size: var(--text-sm);
line-height: 1;
color: var(--color-gray-400);
transition: color 0.2s;
}
.rc-textarea-clear-icon:hover {
color: var(--color-gray-500);
}
.rc-textarea-clear-icon:active {
color: var(--color-gray-600);
}
.rc-textarea-clear-icon-hidden {
display: none;
}
/* Padding adjustment jika ada clear icon */
.rc-textarea-has-clear {
padding-right: calc(var(--textarea-padding-x) + 1.25rem);
}
/* Disabled Wrapper */
.rc-textarea-affix-wrapper-disabled {
background-color: var(--disabled-bg) !important;
border-color: var(--disabled-border) !important;
color: var(--disabled-text);
cursor: not-allowed;
pointer-events: none;
}
.rc-textarea-affix-wrapper-disabled .rc-textarea {
cursor: not-allowed;
}
/* =========================================
LOGIC MATRIX: [VARIANT] x [STATUS]
Target: .rc-textarea (standalone) AND .rc-textarea-affix-wrapper (wrapped)
========================================= */
/* ------------------------------------------------
1. VARIANT: OUTLINED
------------------------------------------------ */
/* --- Default --- */
.textarea-status-default {
background-color: var(--input-bg);
border-color: var(--input-border);
}
/* Hover */
.textarea-status-default:hover:not(:disabled):not(.rc-textarea-affix-wrapper-disabled) {
border-color: var(--input-border-hover);
}
/* Focus (Standalone OR Wrapper Focused) */
.rc-textarea.textarea-status-default:focus,
.rc-textarea-affix-wrapper.textarea-status-default.rc-textarea-affix-wrapper-focused {
border-color: var(--focus-border-color);
box-shadow: 0 0 0 3px var(--focus-ring-color);
z-index: 1;
}
/* --- Error --- */
.textarea-status-error {
border-color: var(--color-error-500);
z-index: 1;
}
.textarea-status-error:hover:not(:disabled):not(.rc-textarea-affix-wrapper-disabled) {
border-color: var(--color-error-600);
}
.rc-textarea.textarea-status-error:focus,
.rc-textarea-affix-wrapper.textarea-status-error.rc-textarea-affix-wrapper-focused {
border-color: var(--color-error-600);
box-shadow: 0 0 0 3px var(--color-error-100);
}
/* --- Warning --- */
.textarea-status-warning {
border-color: var(--color-warning-500);
z-index: 1;
}
.textarea-status-warning:hover:not(:disabled):not(.rc-textarea-affix-wrapper-disabled) {
border-color: var(--color-warning-600);
}
.rc-textarea.textarea-status-warning:focus,
.rc-textarea-affix-wrapper.textarea-status-warning.rc-textarea-affix-wrapper-focused {
border-color: var(--color-warning-600);
box-shadow: 0 0 0 3px var(--color-warning-100);
}
/* --- Success --- */
.textarea-status-success {
border-color: var(--color-success-500);
z-index: 1;
}
.textarea-status-success:hover:not(:disabled):not(.rc-textarea-affix-wrapper-disabled) {
border-color: var(--color-success-600);
}
.rc-textarea.textarea-status-success:focus,
.rc-textarea-affix-wrapper.textarea-status-success.rc-textarea-affix-wrapper-focused {
border-color: var(--color-success-600);
box-shadow: 0 0 0 3px var(--color-success-100);
}
/* --- Info --- */
.textarea-status-info {
border-color: var(--color-info-500);
z-index: 1;
}
.textarea-status-info:hover:not(:disabled):not(.rc-textarea-affix-wrapper-disabled) {
border-color: var(--color-info-600);
}
.rc-textarea.textarea-status-info:focus,
.rc-textarea-affix-wrapper.textarea-status-info.rc-textarea-affix-wrapper-focused {
border-color: var(--color-info-600);
box-shadow: 0 0 0 3px var(--color-info-100);
}
}
@@ -1,62 +0,0 @@
import { forwardRef } from 'react';
import RcSwitch from '@rc-component/switch';
import { SwitchProps } from '../types';
/**
* LoadingSpinner
*
* Renders a compact SVG loading indicator intended for use inside switch components.
*
* - Stateless React component that returns an SVG element.
* - Decorative: marked with `aria-hidden="true"` so it is ignored by assistive technologies.
* - Styled via the `rc-switch-loading-icon` CSS class; color is controlled by `currentColor`.
*
* @returns {JSX.Element} An SVG element representing a loading spinner.
*/
const LoadingSpinner = () => (
<svg
viewBox="0 0 1024 1024"
focusable="false"
className="rc-switch-loading-icon"
data-icon="loading"
fill="currentColor"
aria-hidden="true"
>
<path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" />
</svg>
);
export const Switch = forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
const { label, status = 'default', className, children, style, loading, disabled, ...rest } = props;
const displayLabel = label || children;
const isEnabled = !disabled && !loading;
const wrapperClass = [
'rc-switch-wrapper',
`switch-status-${status}`,
!isEnabled ? 'rc-switch-wrapper-disabled' : '',
className,
]
.filter(Boolean)
.join(' ');
return (
<label className={wrapperClass} style={style}>
<span className={`rc-switch-container ${loading ? 'rc-switch-loading' : ''}`}>
<RcSwitch
ref={ref}
prefixCls="rc-switch"
disabled={!isEnabled}
loadingIcon={loading ? <LoadingSpinner /> : null}
{...rest}
/>
</span>
{displayLabel && <span className="rc-switch-label-text">{displayLabel}</span>}
</label>
);
});
Switch.displayName = 'Switch';
@@ -1,36 +0,0 @@
import { forwardRef } from 'react';
import RcTextArea, { TextAreaRef } from '@rc-component/textarea';
import { InputStatusType, TextAreaProps, VALID_INPUT_STATUSES } from '../types';
/**
* Forwarding React component that renders an RcTextArea with normalized status.
*
* The component computes a CSS class string from the optional `className`, a `textarea-status-<status>` class
* (falling back to 'default' when `status` is missing or not one of VALID_INPUT_STATUSES), and a
*
* The component forwards a ref to the underlying RcTextArea element.
*
* @remarks
* - `status` is validated against `VALID_INPUT_STATUSES`; if invalid or absent, `'default'` is used.
* - Computed className shape: [className?, `textarea-status-${status}`].filter(Boolean).join(' ')
*
* @param props.status - Optional input status (validated against VALID_INPUT_STATUSES).
* @param props.className - Optional additional class name(s) to apply.
* @param props.rest - All other props are forwarded to RcTextArea.
*
* @returns A React element: the wrapped RcTextArea with forwarded ref and computed classes.
*
* @example
* <TextArea status="error" placeholder="Enter text" />
*/
export const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
const { status, className, ...restProps } = props;
const currentStatus: InputStatusType = status && VALID_INPUT_STATUSES.includes(status) ? status : 'default';
const computedClassName = [className, `textarea-status-${currentStatus}`].filter(Boolean).join(' ');
return <RcTextArea ref={ref} className={computedClassName} {...restProps} />;
});
TextArea.displayName = 'TextArea';
@@ -1,105 +0,0 @@
import type { FormProps as RcFormProps } from '@rc-component/form';
import type { Rule } from '@rc-component/form/lib/interface';
import { ReactNode, ReactElement, ComponentProps, CSSProperties } from 'react';
import { InputProps as RcInputProps } from '@rc-component/input';
import { TextAreaProps as RcTextAreaProps } from '@rc-component/textarea';
import { InputNumberProps as RcInputNumberProps } from '@rc-component/input-number';
import { CheckboxProps as RcCheckboxProps } from '@rc-component/checkbox';
import type { PickerProps as RcPickerProps, RangePickerProps as RcRangePickerProps, PickerPanelProps as RcPickerPanelProps } from '@rc-component/picker';
import RcSwitch from '@rc-component/switch';
type RcSwitchProps = ComponentProps<typeof RcSwitch>;
/**
* Define possible input statuses
*/
export type InputStatusType = 'default' | 'error' | 'warning' | 'success' | 'info';
export const VALID_INPUT_STATUSES: InputStatusType[] = ['default', 'error', 'warning', 'success', 'info'];
/**
* Define form props
*/
export interface FormProps<Values = any> extends Omit<RcFormProps<Values>, 'children'> {
children?: ReactNode;
}
/**
* Define form item props
*/
export interface FormItemProps {
name?: string | (string | number)[];
label?: ReactNode;
children: ReactElement;
rules?: Rule[];
valuePropName?: string;
initialValue?: any;
status?: InputStatusType;
}
/**
* Define base component props
*/
interface BaseFormItemComponentProps {
status?: InputStatusType;
}
interface BaseFormInputComponentProps extends BaseFormItemComponentProps {}
/**
* Define specific component props
*/
export interface InputProps extends RcInputProps, BaseFormInputComponentProps {}
export interface InputPasswordProps extends InputProps {
visibilityToggle?: boolean; // Default: true
}
/**
* Define InputNumber and InputCurrency props
*/
export interface InputNumberProps extends RcInputNumberProps, BaseFormInputComponentProps {}
export interface InputCurrencyProps extends InputNumberProps, BaseFormInputComponentProps {
prefix?: string; // Optional: Custom prefix, default 'Rp '
decimalSeparator?: ',' | '.';
decimalScale?: number; // optional
}
/**
* Define TextArea props
*/
export interface TextAreaProps extends RcTextAreaProps, BaseFormInputComponentProps {}
/**
* Define Checkbox props
*/
export interface CheckboxProps extends RcCheckboxProps, BaseFormInputComponentProps {
label?: ReactNode;
}
/**
* Define Switch props
*/
export interface SwitchProps extends RcSwitchProps, BaseFormInputComponentProps {
label?: ReactNode;
loading?: boolean;
}
/**
* Define Picker props
* We omit generateConfig because it is injected by the generator.
* We make locale optional and default it in the implementation.
*/
export interface PickerProps<DateType extends object = any> extends Omit<RcPickerProps<DateType>, 'generateConfig' | 'locale'>, BaseFormInputComponentProps {
locale?: RcPickerProps<DateType>['locale'];
}
export interface RangePickerProps<DateType extends object = any> extends Omit<RcRangePickerProps<DateType>, 'generateConfig' | 'locale'>, BaseFormInputComponentProps {
locale?: RcRangePickerProps<DateType>['locale'];
}
export interface PickerPanelProps<DateType extends object = any> extends Omit<RcPickerPanelProps<DateType>, 'generateConfig' | 'locale'> {
locale?: RcPickerPanelProps<DateType>['locale'];
}
export interface SpecificPickerProps<DateType extends object> extends PickerProps<DateType> {};
export interface SpecificRangePickerProps<DateType extends object> extends RangePickerProps<DateType> {};
-12
View File
@@ -1,16 +1,4 @@
export * from './examples/header-example';
export * from './examples/counter-example';
export * from './examples/date-example';
export * from './examples/encryption-example';
export * from './examples/style-guide-1';
export * from './examples/style-guide-2';
export * from './examples/style-guide-3';
export * from './system-pages/coming-soon';
export * from './system-pages/forbidden';
export * from './system-pages/maintenance';
export * from './system-pages/not-found';
export * from './button/button';
export * from './forms';
-3
View File
@@ -1,5 +1,2 @@
// UI Components
export * from './components';
// UI utils
export * from './utils/cn';
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { ConfigProvider, App } from 'antd';
export const UIProvider = ({ children }: { children: React.ReactNode }) => {
return (
<ConfigProvider>
<App>{children}</App>
</ConfigProvider>
);
};
-11
View File
@@ -1,11 +0,0 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
/**
* Intelligently merges classnames.
* 1. clsx: handles conditional logic (true/false)
* 2. twMerge: removes conflicting Tailwind classes
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}