From 1ceb0fad533b590a7cf08c227bcba0cb33120540 Mon Sep 17 00:00:00 2001
From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com>
Date: Tue, 3 Feb 2026 17:20:05 +0700
Subject: [PATCH] feat: add date, time, and range picker components to UI
library with a showcase
---
.../components/date-picker.showcase.tsx | 176 +++++
packages/ui/src/components/forms/index.ts | 1 +
.../picker/generate-picker.component.tsx | 179 +++++
.../ui/src/components/forms/picker/index.tsx | 24 +
.../components/forms/styles/form.style.css | 1 +
.../components/forms/styles/picker.style.css | 636 ++++++++++++++++++
.../ui/src/components/forms/types/index.ts | 20 +
7 files changed, 1037 insertions(+)
create mode 100644 packages/ui/src/components/forms/picker/generate-picker.component.tsx
create mode 100644 packages/ui/src/components/forms/picker/index.tsx
diff --git a/apps/web/src/apps/modules/example/showcase/components/date-picker.showcase.tsx b/apps/web/src/apps/modules/example/showcase/components/date-picker.showcase.tsx
index e69de29..c557463 100644
--- a/apps/web/src/apps/modules/example/showcase/components/date-picker.showcase.tsx
+++ b/apps/web/src/apps/modules/example/showcase/components/date-picker.showcase.tsx
@@ -0,0 +1,176 @@
+import React from 'react';
+import dayjs from 'dayjs';
+import {
+ TimePicker,
+ DatePicker,
+ WeekPicker,
+ MonthPicker,
+ QuarterPicker,
+ YearPicker,
+ TimeRangePicker,
+ DateRangePicker,
+ WeekRangePicker,
+ MonthRangePicker,
+ QuarterRangePicker,
+ YearRangePicker,
+ InputStatusType
+} from '@repo/ui'; // Sesuaikan path
+
+// --- UI Helper Components ---
+const Section = ({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) => (
+
+
+
{title}
+ {description &&
{description}
}
+
+
+ {children}
+
+
+);
+
+const Card = ({ label, code, children }: { label: string; code?: string; children: React.ReactNode }) => (
+
+
+
+ {code && {code}}
+
+
+ {children}
+
+
+);
+
+export default function PickerShowcase({status}: {status: InputStatusType}) {
+ return (
+
+
+
+ {/* Header */}
+
+
+ Component Picker Showcase
+
+
+ Fully typed, Tailwind-styled, and robust date manipulation components.
+
+
+
+ {/* 1. Single Pickers */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 2. Range Pickers */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 3. Validation States */}
+
+
+
+ This field is required.
+
+
+
+
+ Warning: Date is in the past.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 4. Advanced & Customization */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/packages/ui/src/components/forms/index.ts b/packages/ui/src/components/forms/index.ts
index f0b3015..3e09210 100644
--- a/packages/ui/src/components/forms/index.ts
+++ b/packages/ui/src/components/forms/index.ts
@@ -9,3 +9,4 @@ export * from './input/input-number.component';
export * from './textarea/textarea.component';
export * from './checkbox/checkbox.component';
export * from './switch/switch.component';
+export * from "./picker";
\ No newline at end of file
diff --git a/packages/ui/src/components/forms/picker/generate-picker.component.tsx b/packages/ui/src/components/forms/picker/generate-picker.component.tsx
new file mode 100644
index 0000000..7fa8605
--- /dev/null
+++ b/packages/ui/src/components/forms/picker/generate-picker.component.tsx
@@ -0,0 +1,179 @@
+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 {
+ Picker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ RangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ Calendar: React.ForwardRefExoticComponent & React.RefAttributes>;
+
+ DatePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ WeekPicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ MonthPicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ QuarterPicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ YearPicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ TimePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+
+ DateRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ WeekRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ MonthRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ QuarterRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ YearRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+ TimeRangePicker: React.ForwardRefExoticComponent & React.RefAttributes>;
+}
+
+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(
+ generateConfig: GenerateConfig,
+): PickerGenerateReturns {
+ // 1. Generic Picker
+ const Picker = forwardRef>((props, ref) => {
+ const { status, className, locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
+ return (
+
+ ref={ref}
+ prefixCls={prefixCls}
+ generateConfig={generateConfig}
+ locale={locale || enUS}
+ className={getStatusClassName(prefixCls, className, status, props.disabled)}
+ {...restProps}
+ />
+ );
+ });
+ Picker.displayName = 'Picker';
+
+ // 2. Generic Range Picker
+ const RangePicker = forwardRef>((props, ref) => {
+ const { status, className, locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
+ return (
+
+ 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>((props, ref) => {
+ const { locale, prefixCls = COMPONENT_PREFIX, ...restProps } = props;
+ return (
+
+ ref={ref}
+ prefixCls={prefixCls}
+ generateConfig={generateConfig}
+ locale={locale || enUS}
+ {...restProps}
+ />
+ );
+ });
+ Calendar.displayName = 'Calendar';
+
+ // ================== Specific Pickers ==================
+ const DatePicker = forwardRef>((props, ref) => (
+
+ ));
+ DatePicker.displayName = 'DatePicker';
+
+ const TimePicker = forwardRef>((props, ref) => (
+
+ ));
+ TimePicker.displayName = 'TimePicker';
+
+ const WeekPicker = forwardRef>((props, ref) => (
+
+ ));
+ WeekPicker.displayName = 'WeekPicker';
+
+ const MonthPicker = forwardRef>((props, ref) => (
+
+ ));
+ MonthPicker.displayName = 'MonthPicker';
+
+ const QuarterPicker = forwardRef>((props, ref) => (
+
+ ));
+ QuarterPicker.displayName = 'QuarterPicker';
+
+ const YearPicker = forwardRef>((props, ref) => (
+
+ ));
+ YearPicker.displayName = 'YearPicker';
+
+ // ================== Specific Range Pickers ==================
+
+ const DateRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ DateRangePicker.displayName = 'DateRangePicker';
+
+ const TimeRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ TimeRangePicker.displayName = 'TimeRangePicker';
+
+ const WeekRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ WeekRangePicker.displayName = 'WeekRangePicker';
+
+ const MonthRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ MonthRangePicker.displayName = 'MonthRangePicker';
+
+ const QuarterRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ QuarterRangePicker.displayName = 'QuarterRangePicker';
+
+ const YearRangePicker = forwardRef>((props, ref) => (
+
+ ));
+ YearRangePicker.displayName = 'YearRangePicker';
+
+ return {
+ Picker,
+ RangePicker,
+ Calendar,
+ DatePicker,
+ WeekPicker,
+ MonthPicker,
+ QuarterPicker,
+ YearPicker,
+ TimePicker,
+ DateRangePicker,
+ WeekRangePicker,
+ MonthRangePicker,
+ QuarterRangePicker,
+ YearRangePicker,
+ TimeRangePicker,
+ };
+}
diff --git a/packages/ui/src/components/forms/picker/index.tsx b/packages/ui/src/components/forms/picker/index.tsx
new file mode 100644
index 0000000..2ac0be0
--- /dev/null
+++ b/packages/ui/src/components/forms/picker/index.tsx
@@ -0,0 +1,24 @@
+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(dayjsGenerateConfig);
diff --git a/packages/ui/src/components/forms/styles/form.style.css b/packages/ui/src/components/forms/styles/form.style.css
index 5ebcca3..be6f5bc 100644
--- a/packages/ui/src/components/forms/styles/form.style.css
+++ b/packages/ui/src/components/forms/styles/form.style.css
@@ -4,3 +4,4 @@
@import './textarea.style.css';
@import './checkbox.style.css';
@import './switch.style.css';
+@import './picker.style.css';
diff --git a/packages/ui/src/components/forms/styles/picker.style.css b/packages/ui/src/components/forms/styles/picker.style.css
index e69de29..14bd93d 100644
--- a/packages/ui/src/components/forms/styles/picker.style.css
+++ b/packages/ui/src/components/forms/styles/picker.style.css
@@ -0,0 +1,636 @@
+@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);
+ }
+}
diff --git a/packages/ui/src/components/forms/types/index.ts b/packages/ui/src/components/forms/types/index.ts
index 1262ada..8d0a2f1 100644
--- a/packages/ui/src/components/forms/types/index.ts
+++ b/packages/ui/src/components/forms/types/index.ts
@@ -7,6 +7,7 @@ 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;
@@ -83,3 +84,22 @@ 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 extends Omit, 'generateConfig' | 'locale'>, BaseFormInputComponentProps {
+ locale?: RcPickerProps['locale'];
+}
+export interface RangePickerProps extends Omit, 'generateConfig' | 'locale'>, BaseFormInputComponentProps {
+ locale?: RcRangePickerProps['locale'];
+}
+
+export interface PickerPanelProps extends Omit, 'generateConfig' | 'locale'> {
+ locale?: RcPickerPanelProps['locale'];
+}
+
+export interface SpecificPickerProps extends PickerProps {};
+export interface SpecificRangePickerProps extends RangePickerProps {};