Skip to content

Commit d51ac09

Browse files
authored
Merge pull request #3553 from plotly/feature/datepicker-portals
Feature: redesigned datepicker portals
2 parents 5f76332 + 384787a commit d51ac09

File tree

5 files changed

+554
-8
lines changed

5 files changed

+554
-8
lines changed

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,74 @@
146146
overscroll-behavior: contain;
147147
}
148148

149+
.dash-datepicker
150+
[data-radix-popper-content-wrapper]:has(.dash-datepicker-portal) {
151+
transform: none !important;
152+
}
153+
154+
.dash-datepicker-portal {
155+
position: fixed;
156+
inset: 0;
157+
width: 100vw;
158+
height: 100vh;
159+
max-width: 100vw;
160+
display: flex;
161+
align-items: center;
162+
justify-content: center;
163+
background: var(--Dash-Shading-Strong);
164+
border: none;
165+
box-shadow: none;
166+
overflow: visible;
167+
padding: 0;
168+
pointer-events: none;
169+
}
170+
171+
.dash-datepicker-portal .dash-datepicker-calendar-wrapper {
172+
background: var(--Dash-Fill-Inverse-Strong);
173+
border-radius: var(--Dash-Spacing);
174+
border: 1px solid var(--Dash-Stroke-Strong);
175+
padding: 16px;
176+
box-shadow: 0px 10px 38px -10px var(--Dash-Shading-Strong),
177+
0px 10px 20px -15px var(--Dash-Shading-Weak);
178+
z-index: 1;
179+
pointer-events: auto;
180+
width: fit-content;
181+
max-width: 95vw;
182+
}
183+
184+
.dash-datepicker-fullscreen {
185+
pointer-events: auto;
186+
background: var(--Dash-Fill-Inverse-Strong);
187+
}
188+
189+
.dash-datepicker-close-button {
190+
position: fixed;
191+
top: calc(var(--Dash-Spacing) * 2);
192+
right: calc(var(--Dash-Spacing) * 2);
193+
width: 24px;
194+
height: 24px;
195+
display: flex;
196+
align-items: center;
197+
justify-content: center;
198+
background: var(--Dash-Fill-Inverse-Strong);
199+
border: none;
200+
border-radius: var(--Dash-Spacing);
201+
color: var(--Dash-Text-Strong);
202+
cursor: pointer;
203+
z-index: 501;
204+
pointer-events: auto;
205+
}
206+
207+
.dash-datepicker-close-button:hover {
208+
background: var(--Dash-Fill-Weak);
209+
color: var(--Dash-Fill-Interactive-Strong);
210+
}
211+
212+
.dash-datepicker-close-button:focus {
213+
outline: 2px solid var(--Dash-Fill-Interactive-Strong);
214+
outline-offset: 2px;
215+
}
216+
149217
.dash-datepicker-calendar-wrapper {
150218
display: flex;
151219
flex-direction: column;
@@ -156,6 +224,7 @@
156224
display: flex;
157225
align-items: flex-start;
158226
gap: calc(var(--Dash-Spacing) * 4);
227+
flex-wrap: wrap;
159228
}
160229

161230
.dash-datepicker-controls {

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const DatePickerRange = ({
5151
end_date_id,
5252
start_date_placeholder_text = 'Start Date',
5353
end_date_placeholder_text = 'End Date',
54+
with_portal = false,
55+
with_full_screen_portal = false,
5456
}: DatePickerRangeProps) => {
5557
const [internalStartDate, setInternalStartDate] = useState(
5658
strAsDate(start_date)
@@ -102,6 +104,7 @@ const DatePickerRange = ({
102104
const startInputRef = useRef<HTMLInputElement | null>(null);
103105
const endInputRef = useRef<HTMLInputElement | null>(null);
104106
const calendarRef = useRef<CalendarHandle>(null);
107+
const hasPortal = with_portal || with_full_screen_portal;
105108

106109
useEffect(() => {
107110
setInternalStartDate(strAsDate(start_date));
@@ -381,9 +384,21 @@ const DatePickerRange = ({
381384

382385
<Popover.Portal container={containerRef.current}>
383386
<Popover.Content
384-
className="dash-datepicker-content"
385-
align="start"
386-
sideOffset={5}
387+
className={`dash-datepicker-content${
388+
hasPortal ? ' dash-datepicker-portal' : ''
389+
}${
390+
with_full_screen_portal
391+
? ' dash-datepicker-fullscreen'
392+
: ''
393+
}`}
394+
align={hasPortal ? 'center' : 'start'}
395+
sideOffset={hasPortal ? 0 : 5}
396+
avoidCollisions={!hasPortal}
397+
onInteractOutside={
398+
with_full_screen_portal
399+
? e => e.preventDefault()
400+
: undefined
401+
}
387402
onOpenAutoFocus={e => e.preventDefault()}
388403
onCloseAutoFocus={e => {
389404
e.preventDefault();
@@ -404,6 +419,15 @@ const DatePickerRange = ({
404419
}
405420
}}
406421
>
422+
{with_full_screen_portal && (
423+
<button
424+
className="dash-datepicker-close-button"
425+
onClick={() => setIsCalendarOpen(false)}
426+
aria-label="Close calendar"
427+
>
428+
<Cross1Icon />
429+
</button>
430+
)}
407431
<Calendar
408432
ref={calendarRef}
409433
initialVisibleDate={initialCalendarDate}

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const DatePickerSingle = ({
3939
day_size = 34,
4040
number_of_months_shown = 1,
4141
calendar_orientation,
42+
with_portal = false,
43+
with_full_screen_portal = false,
4244
}: DatePickerSingleProps) => {
4345
const [internalDate, setInternalDate] = useState(strAsDate(date));
4446
const direction = is_RTL
@@ -61,6 +63,7 @@ const DatePickerSingle = ({
6163
const containerRef = useRef<HTMLDivElement>(null);
6264
const inputRef = useRef<HTMLInputElement | null>(null);
6365
const calendarRef = useRef<CalendarHandle>(null);
66+
const hasPortal = with_portal || with_full_screen_portal;
6467

6568
useEffect(() => {
6669
setInternalDate(strAsDate(date));
@@ -200,9 +203,21 @@ const DatePickerSingle = ({
200203

201204
<Popover.Portal container={containerRef.current}>
202205
<Popover.Content
203-
className="dash-datepicker-content"
204-
align="start"
205-
sideOffset={5}
206+
className={`dash-datepicker-content${
207+
hasPortal ? ' dash-datepicker-portal' : ''
208+
}${
209+
with_full_screen_portal
210+
? ' dash-datepicker-fullscreen'
211+
: ''
212+
}`}
213+
align={hasPortal ? 'center' : 'start'}
214+
sideOffset={hasPortal ? 0 : 5}
215+
avoidCollisions={!hasPortal}
216+
onInteractOutside={
217+
with_full_screen_portal
218+
? e => e.preventDefault()
219+
: undefined
220+
}
206221
onOpenAutoFocus={e => e.preventDefault()}
207222
onCloseAutoFocus={e => {
208223
e.preventDefault();
@@ -212,6 +227,15 @@ const DatePickerSingle = ({
212227
}
213228
}}
214229
>
230+
{with_full_screen_portal && (
231+
<button
232+
className="dash-datepicker-close-button"
233+
onClick={() => setIsCalendarOpen(false)}
234+
aria-label="Close calendar"
235+
>
236+
<Cross1Icon />
237+
</button>
238+
)}
215239
<Calendar
216240
ref={calendarRef}
217241
initialVisibleDate={initialMonth}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,6 @@ const Dropdown = (props: DropdownProps) => {
394394
}
395395
}}
396396
className={`dash-dropdown ${className ?? ''}`}
397-
style={style}
398397
aria-labelledby={`${accessibleId}-value-count ${accessibleId}-value`}
399398
aria-haspopup="listbox"
400399
aria-expanded={isOpen}
@@ -540,7 +539,11 @@ const Dropdown = (props: DropdownProps) => {
540539
);
541540

542541
return (
543-
<div ref={positioningContainerRef} className="dash-dropdown-wrapper">
542+
<div
543+
ref={positioningContainerRef}
544+
className="dash-dropdown-wrapper"
545+
style={style}
546+
>
544547
{popover}
545548
</div>
546549
);

0 commit comments

Comments
 (0)