Skip to content

Commit b3006eb

Browse files
committed
Add support for portalContainer prop
1 parent c9cd7cc commit b3006eb

File tree

5 files changed

+70
-24
lines changed

5 files changed

+70
-24
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ Displays an input field complete with custom inputs, native input, a calendar, a
120120
| onClockClose | Function called when the clock closes. | n/a | `() => alert('Clock closed')` |
121121
| onClockOpen | Function called when the clock opens. | n/a | `() => alert('Clock opened')` |
122122
| openWidgetsOnFocus | Whether to open the widgets on input focus. | `true` | `false` |
123+
| portalContainer | Element to render the widgets in using portal. | n/a | `document.getElementById('my-div')` |
123124
| returnValue | Which dates shall be passed by the calendar to the onChange function and onClick{Period} functions. Can be `"start"`, `"end"` or `"range"`. The latter will cause an array with start and end values to be passed. | ` "start"` | `"range"` |
124125
| required | Whether datetime input should be required. | `false` | `true` |
125126
| secondAriaLabel | `aria-label` for the second input. | n/a | `"Second"` |

src/DateTimePicker.jsx

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { PureComponent } from 'react';
2+
import { createPortal } from 'react-dom';
23
import PropTypes from 'prop-types';
34
import makeEventProps from 'make-event-props';
45
import mergeClassNames from 'merge-class-names';
@@ -316,31 +317,39 @@ export default class DateTimePicker extends PureComponent {
316317
className: dateTimePickerClassName, // Unused, here to exclude it from calendarProps
317318
maxDetail: dateTimePickerMaxDetail, // Unused, here to exclude it from calendarProps
318319
onChange,
320+
portalContainer,
319321
value,
320322
...calendarProps
321323
} = this.props;
322324

323325
const className = `${baseClassName}__calendar`;
326+
const classNames = mergeClassNames(
327+
className,
328+
`${className}--${isCalendarOpen ? 'open' : 'closed'}`,
329+
);
324330

325-
return (
331+
const calendar = (
332+
<Calendar
333+
className={calendarClassName}
334+
onChange={(value) => this.onDateChange(value)}
335+
value={value || null}
336+
{...calendarProps}
337+
/>
338+
);
339+
340+
return portalContainer ? (
341+
createPortal(<div className={classNames}>{calendar}</div>, portalContainer)
342+
) : (
326343
<Fit>
327344
<div
328345
ref={(ref) => {
329346
if (ref && !isCalendarOpen) {
330347
ref.removeAttribute('style');
331348
}
332349
}}
333-
className={mergeClassNames(
334-
className,
335-
`${className}--${isCalendarOpen ? 'open' : 'closed'}`,
336-
)}
350+
className={classNames}
337351
>
338-
<Calendar
339-
className={calendarClassName}
340-
onChange={(value) => this.onDateChange(value)}
341-
value={value || null}
342-
{...calendarProps}
343-
/>
352+
{calendar}
344353
</div>
345354
</Fit>
346355
);
@@ -359,32 +368,44 @@ export default class DateTimePicker extends PureComponent {
359368
className: dateTimePickerClassName, // Unused, here to exclude it from clockProps
360369
maxDetail,
361370
onChange,
371+
portalContainer,
362372
value,
363373
...clockProps
364374
} = this.props;
365375

366376
const className = `${baseClassName}__clock`;
377+
const classNames = mergeClassNames(
378+
className,
379+
`${className}--${isClockOpen ? 'open' : 'closed'}`,
380+
);
381+
367382
const [valueFrom] = [].concat(value);
368383

369384
const maxDetailIndex = allViews.indexOf(maxDetail);
370385

371-
return (
386+
const clock = (
387+
<Clock
388+
className={clockClassName}
389+
renderMinuteHand={maxDetailIndex > 0}
390+
renderSecondHand={maxDetailIndex > 1}
391+
value={valueFrom}
392+
{...clockProps}
393+
/>
394+
);
395+
396+
return portalContainer ? (
397+
createPortal(<div className={classNames}>{clock}</div>, portalContainer)
398+
) : (
372399
<Fit>
373400
<div
374401
ref={(ref) => {
375402
if (ref && !isClockOpen) {
376403
ref.removeAttribute('style');
377404
}
378405
}}
379-
className={mergeClassNames(className, `${className}--${isClockOpen ? 'open' : 'closed'}`)}
406+
className={classNames}
380407
>
381-
<Clock
382-
className={clockClassName}
383-
renderMinuteHand={maxDetailIndex > 0}
384-
renderSecondHand={maxDetailIndex > 1}
385-
value={valueFrom}
386-
{...clockProps}
387-
/>
408+
{clock}
388409
</div>
389410
</Fit>
390411
);
@@ -503,6 +524,7 @@ DateTimePicker.propTypes = {
503524
onClockOpen: PropTypes.func,
504525
onFocus: PropTypes.func,
505526
openWidgetsOnFocus: PropTypes.bool,
527+
portalContainer: PropTypes.object,
506528
required: PropTypes.bool,
507529
secondAriaLabel: PropTypes.string,
508530
secondPlaceholder: PropTypes.string,

src/DateTimePicker.less

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,6 @@
9898

9999
&__calendar,
100100
&__clock {
101-
position: absolute;
102-
top: 100%;
103-
left: 0;
104101
z-index: 1;
105102

106103
&--closed {

test/Test.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useRef, useState } from 'react';
22
import DateTimePicker from 'react-datetime-picker/src/entry.nostyle';
33
import 'react-datetime-picker/src/DateTimePicker.less';
44
import 'react-calendar/src/Calendar.less';
@@ -42,11 +42,13 @@ const nineteenNinetyFive = new Date(1995, now.getUTCMonth() + 1, 15, 12);
4242
const fifteenthOfNextMonth = new Date(now.getUTCFullYear(), now.getUTCMonth() + 1, 15, 12);
4343

4444
export default function Test() {
45+
const portalContainer = useRef();
4546
const [disabled, setDisabled] = useState(false);
4647
const [locale, setLocale] = useState(null);
4748
const [maxDate, setMaxDate] = useState(fifteenthOfNextMonth);
4849
const [maxDetail, setMaxDetail] = useState('minute');
4950
const [minDate, setMinDate] = useState(nineteenNinetyFive);
51+
const [renderInPortal, setRenderInPortal] = useState(false);
5052
const [required, setRequired] = useState(true);
5153
const [showLeadingZeros, setShowLeadingZeros] = useState(true);
5254
const [showNeighboringMonth, setShowNeighboringMonth] = useState(false);
@@ -73,7 +75,9 @@ export default function Test() {
7375
<ValueOptions setValue={setValue} value={value} />
7476
<ViewOptions
7577
disabled={disabled}
78+
renderInPortal={renderInPortal}
7679
setDisabled={setDisabled}
80+
setRenderInPortal={setRenderInPortal}
7781
setShowLeadingZeros={setShowLeadingZeros}
7882
setShowNeighboringMonth={setShowNeighboringMonth}
7983
setShowWeekNumbers={setShowWeekNumbers}
@@ -108,12 +112,14 @@ export default function Test() {
108112
onChange={setValue}
109113
onClockClose={() => console.log('Clock closed')}
110114
onClockOpen={() => console.log('Clock opened')}
115+
portalContainer={renderInPortal ? portalContainer.current : undefined}
111116
required={required}
112117
showLeadingZeros={showLeadingZeros}
113118
showNeighboringMonth={showNeighboringMonth}
114119
showWeekNumbers={showWeekNumbers}
115120
value={value}
116121
/>
122+
<div ref={portalContainer} />
117123
<br />
118124
<br />
119125
<button id="submit" type="submit">

test/ViewOptions.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
33

44
export default function ViewOptions({
55
disabled,
6+
renderInPortal,
67
setDisabled,
8+
setRenderInPortal,
79
setShowLeadingZeros,
810
setShowNeighboringMonth,
911
setShowWeekNumbers,
@@ -35,6 +37,12 @@ export default function ViewOptions({
3537
setShowNeighboringMonth(checked);
3638
}
3739

40+
function onRenderInPortalChange(event) {
41+
const { checked } = event.target;
42+
43+
setRenderInPortal(checked);
44+
}
45+
3846
return (
3947
<fieldset id="viewoptions">
4048
<legend htmlFor="viewoptions">View options</legend>
@@ -73,13 +81,25 @@ export default function ViewOptions({
7381
/>
7482
<label htmlFor="showNeighboringMonth">{"Show neighboring month's days"}</label>
7583
</div>
84+
85+
<div>
86+
<input
87+
checked={renderInPortal}
88+
id="renderInPortal"
89+
onChange={onRenderInPortalChange}
90+
type="checkbox"
91+
/>
92+
<label htmlFor="renderInPortal">Render in portal</label>
93+
</div>
7694
</fieldset>
7795
);
7896
}
7997

8098
ViewOptions.propTypes = {
8199
disabled: PropTypes.bool.isRequired,
100+
renderInPortal: PropTypes.bool.isRequired,
82101
setDisabled: PropTypes.func.isRequired,
102+
setRenderInPortal: PropTypes.func.isRequired,
83103
setShowLeadingZeros: PropTypes.func.isRequired,
84104
setShowNeighboringMonth: PropTypes.func.isRequired,
85105
setShowWeekNumbers: PropTypes.func.isRequired,

0 commit comments

Comments
 (0)