feat: Implement Calendar component, UiProvider, and a cn utility, while enhancing picker components with new icons and styles.
This commit is contained in:
@@ -0,0 +1,647 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user