Skip to content

Commit 670cd51

Browse files
authored
Merge pull request #3544 from plotly/bugfix/datepicker-feedback
Bugfix/datepicker feedback
2 parents 6c40591 + e895020 commit 670cd51

File tree

9 files changed

+336
-100
lines changed

9 files changed

+336
-100
lines changed

components/dash-core-components/src/components/css/calendar.css

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
font-weight: normal;
1717
color: var(--Dash-Text-Weak);
1818
text-align: center;
19-
border-radius: 4px;
2019
cursor: default;
2120
user-select: none;
2221
-webkit-user-select: none;
@@ -37,6 +36,7 @@
3736
.dash-datepicker-calendar td:focus {
3837
outline: 2px solid var(--Dash-Fill-Interactive-Strong);
3938
outline-offset: -2px;
39+
border-radius: 4px;
4040
z-index: 1;
4141
position: relative;
4242
}
@@ -55,6 +55,28 @@
5555
color: var(--Dash-Fill-Inverse-Strong);
5656
}
5757

58+
.dash-datepicker-calendar td.dash-datepicker-calendar-date-selected {
59+
background-color: var(--Dash-Fill-Interactive-Strong);
60+
color: var(--Dash-Fill-Inverse-Strong);
61+
}
62+
63+
/* start_date selector: a selected date which is preceded by an unselected date */
64+
.dash-datepicker-calendar
65+
td:not(.dash-datepicker-calendar-date-highlighted)
66+
+ td.dash-datepicker-calendar-date-selected {
67+
border-start-start-radius: 4px;
68+
border-end-start-radius: 4px;
69+
}
70+
71+
/* end_date selector: a selected date which is followed by an unselected date */
72+
.dash-datepicker-calendar
73+
td.dash-datepicker-calendar-date-selected:not(
74+
:has(+ td.dash-datepicker-calendar-date-highlighted)
75+
) {
76+
border-start-end-radius: 4px;
77+
border-end-end-radius: 4px;
78+
}
79+
5880
.dash-datepicker-calendar td.dash-datepicker-calendar-date-disabled {
5981
opacity: 0.6;
6082
cursor: not-allowed;

components/dash-core-components/src/components/css/datepickers.css

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
transition: transform 0.15s;
100100
}
101101

102+
.dash-datepicker-range-arrow {
103+
color: var(--Dash-Text-Strong);
104+
width: 1em;
105+
height: 1em;
106+
}
107+
102108
.dash-datepicker-input-wrapper[aria-expanded='true']
103109
.dash-datepicker-caret-icon {
104110
transform: rotate(180deg);
@@ -162,15 +168,25 @@
162168

163169
.dash-datepicker-controls .dash-dropdown {
164170
flex: 0 0 auto;
165-
min-width: 122px;
166-
width: auto;
167171
margin: 0;
168172
}
169173

174+
.dash-datepicker-month-sizer {
175+
height: 0;
176+
grid-area: 1 / 1;
177+
visibility: hidden;
178+
pointer-events: none;
179+
white-space: nowrap;
180+
padding: 0 24px;
181+
font-family: inherit;
182+
font-size: inherit;
183+
}
184+
170185
.dash-datepicker-controls .dash-input {
171186
flex-shrink: 0;
172187
flex-grow: 0;
173-
width: 102px;
188+
min-width: 102px;
189+
width: calc(5ch + 40px);
174190
}
175191

176192
.dash-datepicker-controls .dash-input-stepper {

components/dash-core-components/src/fragments/DatePickerRange.tsx

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,23 @@ const DatePickerRange = ({
150150
}
151151
}, [internalStartDate, internalEndDate, updatemode]);
152152

153+
const isDateAllowed = useCallback(
154+
(date?: Date): date is Date => {
155+
return (
156+
!!date && !isDateDisabled(date, minDate, maxDate, disabledDates)
157+
);
158+
},
159+
[minDate, maxDate, disabledDates]
160+
);
161+
153162
const sendStartInputAsDate = useCallback(
154163
(focusCalendar = false) => {
155164
if (startInputValue) {
156165
setInternalStartDate(undefined);
157166
}
158167
const parsed = strAsDate(startInputValue, display_format);
159-
const isValid =
160-
parsed &&
161-
!isDateDisabled(parsed, minDate, maxDate, disabledDates);
162168

163-
if (isValid) {
169+
if (isDateAllowed(parsed)) {
164170
setInternalStartDate(parsed);
165171
if (focusCalendar) {
166172
calendarRef.current?.focusDate(parsed);
@@ -178,14 +184,7 @@ const DatePickerRange = ({
178184
}
179185
}
180186
},
181-
[
182-
startInputValue,
183-
display_format,
184-
start_date,
185-
minDate,
186-
maxDate,
187-
disabledDates,
188-
]
187+
[startInputValue, display_format, start_date, isDateAllowed]
189188
);
190189

191190
const sendEndInputAsDate = useCallback(
@@ -194,11 +193,8 @@ const DatePickerRange = ({
194193
setInternalEndDate(undefined);
195194
}
196195
const parsed = strAsDate(endInputValue, display_format);
197-
const isValid =
198-
parsed &&
199-
!isDateDisabled(parsed, minDate, maxDate, disabledDates);
200196

201-
if (isValid) {
197+
if (isDateAllowed(parsed)) {
202198
setInternalEndDate(parsed);
203199
if (focusCalendar) {
204200
calendarRef.current?.focusDate(parsed);
@@ -216,14 +212,7 @@ const DatePickerRange = ({
216212
}
217213
}
218214
},
219-
[
220-
endInputValue,
221-
display_format,
222-
end_date,
223-
minDate,
224-
maxDate,
225-
disabledDates,
226-
]
215+
[endInputValue, display_format, end_date, isDateAllowed]
227216
);
228217

229218
const clearSelection = useCallback(
@@ -242,15 +231,14 @@ const DatePickerRange = ({
242231

243232
const handleStartInputKeyDown = useCallback(
244233
(e: React.KeyboardEvent<HTMLInputElement>) => {
245-
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
234+
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
246235
e.preventDefault();
247-
sendStartInputAsDate(true);
248236
if (!isCalendarOpen) {
249-
// open the calendar after resolving prop changes, so that
250-
// it opens with the correct date showing
251-
setTimeout(() => setIsCalendarOpen(true), 0);
237+
setIsCalendarOpen(true);
252238
}
253-
} else if (['Enter', 'Tab'].includes(e.key)) {
239+
// Wait for calendar to mount before focusing
240+
requestAnimationFrame(() => sendStartInputAsDate(true));
241+
} else if (e.key === 'Tab') {
254242
sendStartInputAsDate();
255243
}
256244
},
@@ -259,15 +247,14 @@ const DatePickerRange = ({
259247

260248
const handleEndInputKeyDown = useCallback(
261249
(e: React.KeyboardEvent<HTMLInputElement>) => {
262-
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
250+
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
263251
e.preventDefault();
264-
sendEndInputAsDate(true);
265252
if (!isCalendarOpen) {
266-
// open the calendar after resolving prop changes, so that
267-
// it opens with the correct date showing
268-
setTimeout(() => setIsCalendarOpen(true), 0);
253+
setIsCalendarOpen(true);
269254
}
270-
} else if (['Enter', 'Tab'].includes(e.key)) {
255+
// Wait for calendar to mount before focusing
256+
requestAnimationFrame(() => sendEndInputAsDate(true));
257+
} else if (e.key === 'Tab') {
271258
sendEndInputAsDate();
272259
}
273260
},
@@ -349,18 +336,16 @@ const DatePickerRange = ({
349336
onChange={e => setStartInputValue(e.target.value)}
350337
onKeyDown={handleStartInputKeyDown}
351338
onFocus={() => {
352-
if (internalStartDate) {
353-
calendarRef.current?.setVisibleDate(
354-
internalStartDate
355-
);
339+
if (isCalendarOpen) {
340+
sendStartInputAsDate();
356341
}
357342
}}
358343
placeholder={start_date_placeholder_text}
359344
disabled={disabled}
360345
dir={direction}
361346
aria-label={start_date_placeholder_text}
362347
/>
363-
<ArrowIcon />
348+
<ArrowIcon className="dash-datepicker-range-arrow" />
364349
<AutosizeInput
365350
inputRef={node => {
366351
endInputRef.current = node;
@@ -372,10 +357,8 @@ const DatePickerRange = ({
372357
onChange={e => setEndInputValue(e.target.value)}
373358
onKeyDown={handleEndInputKeyDown}
374359
onFocus={() => {
375-
if (internalEndDate) {
376-
calendarRef.current?.setVisibleDate(
377-
internalEndDate
378-
);
360+
if (isCalendarOpen) {
361+
sendEndInputAsDate();
379362
}
380363
}}
381364
placeholder={end_date_placeholder_text}

components/dash-core-components/src/fragments/DatePickerSingle.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,14 @@ const DatePickerSingle = ({
124124

125125
const handleInputKeyDown = useCallback(
126126
(e: React.KeyboardEvent<HTMLInputElement>) => {
127-
if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
127+
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
128128
e.preventDefault();
129-
parseUserInput(true);
130129
if (!isCalendarOpen) {
131-
// open the calendar after resolving prop changes, so that
132-
// it opens with the correct date showing
133-
setTimeout(() => setIsCalendarOpen(true), 0);
130+
setIsCalendarOpen(true);
134131
}
135-
} else if (['Enter', 'Tab'].includes(e.key)) {
132+
// Wait for calendar to mount before focusing
133+
requestAnimationFrame(() => parseUserInput(true));
134+
} else if (e.key === 'Tab') {
136135
parseUserInput();
137136
}
138137
},

0 commit comments

Comments
 (0)