chore: Delete various UI components, form elements, and associated example/showcase pages from the UI package and web app.
This commit is contained in:
@@ -12,18 +12,8 @@
|
||||
"test:watch": "vitest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@rc-component/checkbox": "^1.0.1",
|
||||
"@rc-component/form": "^1.6.2",
|
||||
"@rc-component/input": "^1.1.2",
|
||||
"@rc-component/input-number": "^1.6.2",
|
||||
"@rc-component/picker": "^1.9.0",
|
||||
"@rc-component/switch": "^1.0.3",
|
||||
"@rc-component/textarea": "^1.1.2",
|
||||
"@repo/utils": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"antd": "^6.2.3",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
|
||||
@@ -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 < 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 > Application > 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 };
|
||||
@@ -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> {};
|
||||
@@ -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';
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
// UI Components
|
||||
export * from './components';
|
||||
|
||||
// UI utils
|
||||
export * from './utils/cn';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user