Skip to content

Commit

Permalink
Fix [Datepicker] position should use window size (mlrun#2285)
Browse files Browse the repository at this point in the history
  • Loading branch information
Taras-Hlukhovetskyi authored and ilan7empest committed Feb 18, 2024
1 parent 2ff6ebf commit 090eaa3
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 126 deletions.
48 changes: 19 additions & 29 deletions src/common/DatePicker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ illegal under applicable law, and the grant of the foregoing license
under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import React, { useState, useRef, useEffect, useCallback, useLayoutEffect, useReducer } from 'react'
import React, { useState, useRef, useEffect, useLayoutEffect, useCallback, useReducer } from 'react'
import PropTypes from 'prop-types'
import { throttle } from 'lodash'

Expand Down Expand Up @@ -74,6 +74,7 @@ const DatePicker = ({
formatDate(isRange, isTime, splitCharacter, date, dateTo)
)
const [isInvalid, setIsInvalid] = useState(false)
const [position, setPosition] = useState('bottom-right')

const datePickerRef = useRef()
const datePickerViewRef = useRef()
Expand All @@ -97,7 +98,11 @@ const DatePicker = ({

const handleCloseDatePickerOutside = useCallback(
event => {
if (datePickerRef.current && !datePickerRef.current.contains(event.target)) {
if (
!event.target.closest('.date-picker__pop-up-wrapper') &&
datePickerViewRef.current &&
!datePickerViewRef.current.contains(event.target)
) {
if (isDatePickerOptionsOpened) {
setIsDatePickerOptionsOpened(false)
} else if (isDatePickerOpened) {
Expand All @@ -118,7 +123,7 @@ const DatePicker = ({
}
},
[
datePickerRef,
datePickerViewRef,
datePickerState.configFrom.date,
datePickerState.configTo.date,
isDatePickerOpened,
Expand Down Expand Up @@ -148,33 +153,13 @@ const DatePicker = ({
) {
const containerRect = datePickerRef.current.getBoundingClientRect()
const popUpRect = datePickerViewRef.current.getBoundingClientRect()
const padding = 5
const margin = 15

if (containerRect && popUpRect) {
let topPosition

if (
popUpRect.height + containerRect.bottom > window.innerHeight &&
containerRect.top - popUpRect.height > padding
) {
topPosition = -padding - popUpRect.height
} else {
topPosition =
popUpRect.height + containerRect.bottom > window.innerHeight
? containerRect.height +
(window.innerHeight - (popUpRect.height + containerRect.bottom)) -
padding
: containerRect.height + padding
}
datePickerViewRef.current.style.top = `${topPosition}px`

if (popUpRect.width + containerRect.left > window.innerWidth) {
datePickerViewRef.current.style.left = `${
window.innerWidth - popUpRect.width - containerRect.left - padding
}px`
} else {
datePickerViewRef.current.style.left = '0px'
}
containerRect.left + popUpRect.width + margin > window.innerWidth &&
containerRect.right - popUpRect.width > margin
? setPosition('bottom-left')
: setPosition('bottom-right')
}
}
}, [isDatePickerOpened, isDatePickerOptionsOpened])
Expand Down Expand Up @@ -260,7 +245,11 @@ const DatePicker = ({
useEffect(() => {
if (isDatePickerOpened || isDatePickerOptionsOpened) {
window.addEventListener('click', handleCloseDatePickerOutside, true)
return () => window.removeEventListener('click', handleCloseDatePickerOutside)
window.addEventListener('scroll', handleCloseDatePickerOutside, true)
return () => {
window.removeEventListener('click', handleCloseDatePickerOutside, true)
window.removeEventListener('scroll', handleCloseDatePickerOutside, true)
}
}
}, [handleCloseDatePickerOutside, isDatePickerOpened, isDatePickerOptionsOpened])

Expand Down Expand Up @@ -476,6 +465,7 @@ const DatePicker = ({
onPreviousMonth={onChangePreviousMonth}
onSelectOption={onSelectOption}
onTimeChange={onTimeChange}
position={position}
ref={{ datePickerRef, datePickerViewRef }}
required={required}
requiredText={requiredText}
Expand Down
209 changes: 115 additions & 94 deletions src/common/DatePicker/DatePickerView.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import classnames from 'classnames'

import ErrorMessage from '../ErrorMessage/ErrorMessage'
import TimePicker from '../TimePicker/TimePicker'
import { Button, Tip, Tooltip, TextTooltipTemplate } from 'igz-controls/components'
import { Button, Tip, Tooltip, TextTooltipTemplate, PopUpDialog } from 'igz-controls/components'
import { SelectOption } from 'igz-controls/elements'

import { SECONDARY_BUTTON } from 'igz-controls/constants'
Expand Down Expand Up @@ -66,6 +66,7 @@ const DatePickerView = React.forwardRef(
onPreviousMonth,
onSelectOption,
onTimeChange,
position,
required,
requiredText,
selectedOption,
Expand Down Expand Up @@ -245,104 +246,123 @@ const DatePickerView = React.forwardRef(
)}
</div>
{isDatePickerOptionsOpened && (
<div ref={ref.datePickerViewRef} className='date-picker__pop-up'>
{datePickerOptions.map(option => (
<SelectOption
item={option}
name={option.id}
key={option.id}
onClick={() => onSelectOption(option)}
withSelectedIcon
selectedId={selectedOption && selectedOption.id}
/>
))}
</div>
<PopUpDialog
className='date-picker__pop-up-wrapper'
headerIsHidden
customPosition={{
element: ref.datePickerRef,
position
}}
>
<div ref={ref.datePickerViewRef} className='date-picker__pop-up'>
{datePickerOptions.map(option => (
<SelectOption
item={option}
name={option.id}
key={option.id}
onClick={() => {
onSelectOption(option)
}}
selectType=''
/>
))}
</div>
</PopUpDialog>
)}
{isDatePickerOpened && (
<div ref={ref.datePickerViewRef} className='date-picker__pop-up date-picker'>
<div className='date-picker__calendars'>
{config.map(item => (
<div className={classnames('date-picker__calendar')} key={item.id}>
<div className='date-picker__header'>
<Arrow
data-testid='btn-previous-month'
className='date-picker__header-previous-month'
onClick={() => onPreviousMonth(item.id)}
/>
{isRange && <span className='date-picker__header-label'>{item.label}</span>}
<div>
<span className='date-picker__header-month'>
{months[item.visibleDate.getMonth()]}
</span>
<span className='date-picker__header-year'>
{item.visibleDate.getFullYear()}
</span>
</div>
<Arrow
data-testid='btn-next-month'
className='date-picker__header-next-month'
onClick={() => onNextMonth(item.id)}
/>
</div>
<div className='date-picker__weeks'>
{weekDay.map((day, index) => {
return (
<div key={`${day}${index}`} className='date-picker__weeks-day'>
{day.label}
</div>
)
})}
</div>
{item.calendar.map(({ week }, index) => (
<div key={`${week[0].day.getMonth()}${index}`} className='date-picker__week'>
{week.map(({ day }) => (
<div
key={`${day.getMonth()}${day.getDate()}${day.getFullYear()}`}
className={classnames(
'date-picker__week-day-wrapper',
item.id === 'configFrom' ? 'calendar-from' : 'calendar-to',
item.visibleDate.getMonth() === day.getMonth()
? 'current-month'
: 'not-current-month',
isSameDate(config[0].selectedDate, day) && 'selected-from',
isRange && isSameDate(config[1].selectedDate, day) && 'selected-to',
isRangeDateValid(day) && 'in-range',
isCalendarInvalid && 'invalid'
)}
onClick={() => {
item.visibleDate.getMonth() === day.getMonth() &&
setSelectedDate(item.id, day, item.selectedDate)
}}
>
<div className='date-picker__week-day'>{day.getDate()}</div>
</div>
))}
</div>
))}
{isTime && (
<div className='date-picker__time'>
<TimePicker
onChange={time => onTimeChange(item.id, time, item.selectedDate)}
value={`${String(item.selectedDate.getHours()).padStart(2, '0')}:${String(
item.selectedDate.getMinutes()
).padStart(2, '0')}`}
<PopUpDialog
className='date-picker__pop-up-wrapper'
headerIsHidden
customPosition={{
element: ref.datePickerRef,
position
}}
>
<div ref={ref.datePickerViewRef} className='date-picker__pop-up date-picker'>
<div className='date-picker__calendars'>
{config.map(item => (
<div className={classnames('date-picker__calendar')} key={item.id}>
<div className='date-picker__header'>
<Arrow
data-testid='btn-previous-month'
className='date-picker__header-previous-month'
onClick={() => onPreviousMonth(item.id)}
/>
{isRange && <span className='date-picker__header-label'>{item.label}</span>}
<div>
<span className='date-picker__header-month'>
{months[item.visibleDate.getMonth()]}
</span>
<span className='date-picker__header-year'>
{item.visibleDate.getFullYear()}
</span>
</div>
<Arrow
data-testid='btn-next-month'
className='date-picker__header-next-month'
onClick={() => onNextMonth(item.id)}
/>
</div>
)}
</div>
))}
</div>
<div className='date-picker__footer'>
{isCalendarInvalid && <ErrorMessage message='“To” must be later than “From”' />}
<Button
variant={SECONDARY_BUTTON}
label='Apply'
onClick={onApplyChanges}
className='date-picker__apply-btn'
disabled={isCalendarInvalid}
/>
<div className='date-picker__weeks'>
{weekDay.map((day, index) => {
return (
<div key={`${day}${index}`} className='date-picker__weeks-day'>
{day.label}
</div>
)
})}
</div>
{item.calendar.map(({ week }, index) => (
<div key={`${week[0].day.getMonth()}${index}`} className='date-picker__week'>
{week.map(({ day }) => (
<div
key={`${day.getMonth()}${day.getDate()}${day.getFullYear()}`}
className={classnames(
'date-picker__week-day-wrapper',
item.id === 'configFrom' ? 'calendar-from' : 'calendar-to',
item.visibleDate.getMonth() === day.getMonth()
? 'current-month'
: 'not-current-month',
isSameDate(config[0].selectedDate, day) && 'selected-from',
isRange && isSameDate(config[1].selectedDate, day) && 'selected-to',
isRangeDateValid(day) && 'in-range',
isCalendarInvalid && 'invalid'
)}
onClick={() => {
item.visibleDate.getMonth() === day.getMonth() &&
setSelectedDate(item.id, day, item.selectedDate)
}}
>
<div className='date-picker__week-day'>{day.getDate()}</div>
</div>
))}
</div>
))}
{isTime && (
<div className='date-picker__time'>
<TimePicker
onChange={time => onTimeChange(item.id, time, item.selectedDate)}
value={`${String(item.selectedDate.getHours()).padStart(2, '0')}:${String(
item.selectedDate.getMinutes()
).padStart(2, '0')}`}
/>
</div>
)}
</div>
))}
</div>
<div className='date-picker__footer'>
{isCalendarInvalid && <ErrorMessage message='“To” must be later than “From”' />}
<Button
variant={SECONDARY_BUTTON}
label='Apply'
onClick={onApplyChanges}
className='date-picker__apply-btn'
disabled={isCalendarInvalid}
/>
</div>
</div>
</div>
</PopUpDialog>
)}
{tip && <Tip text={tip} className='input__tip' />}
</div>
Expand Down Expand Up @@ -383,6 +403,7 @@ DatePickerView.propTypes = {
onPreviousMonth: PropTypes.func.isRequired,
onSelectOption: PropTypes.func.isRequired,
onTimeChange: PropTypes.func.isRequired,
position: PropTypes.string.isRequired,
required: PropTypes.bool.isRequired,
requiredText: PropTypes.string.isRequired,
setSelectedDate: PropTypes.func.isRequired,
Expand Down
10 changes: 7 additions & 3 deletions src/common/DatePicker/datePicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@
left: 65px;
font-size: 14px;
}
}

.date-picker__pop-up-wrapper {
.pop-up-dialog {
width: auto;
padding: 0;
}

.date-picker__pop-up {
position: absolute;
left: 0;
z-index: 5;
display: flex;
flex-direction: column;
justify-content: center;
Expand Down

0 comments on commit 090eaa3

Please sign in to comment.