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 {};