Просмотр исходного кода

Improve `DateTimePicker` when scrolled

The `DateTimePicker` dropdown is now positioned dynamically based on
display space available and scroll position.
Sergiu Miclea 8 лет назад
Родитель
Сommit
d1044e2934
2 измененных файлов с 91 добавлено и 38 удалено
  1. 34 21
      src/components/atoms/DropdownButton/index.jsx
  2. 57 17
      src/components/molecules/DatetimePicker/index.jsx

+ 34 - 21
src/components/atoms/DropdownButton/index.jsx

@@ -117,30 +117,43 @@ const Arrow = styled.div`
 type Props = {
 type Props = {
   value: string,
   value: string,
   onClick?: (event: Event) => void,
   onClick?: (event: Event) => void,
+  customRef?: (ref: HTMLElement) => void,
+  innerRef?: (ref: HTMLElement) => void,
   className?: string,
   className?: string,
   disabled?: boolean,
   disabled?: boolean,
 }
 }
-const DropdownButton = (props: Props) => {
-  return (
-    <Wrapper
-      {...props}
-      onClick={e => { if (!props.disabled && props.onClick) props.onClick(e) }}
-    >
-      <Label
-        {...props}
-        onClick={() => {}}
-        data-test-id=""
-        disabled={props.disabled}
-      >{props.value}</Label>
-      <Arrow
-        {...props}
-        onClick={() => {}}
-        data-test-id=""
-        disabled={props.disabled}
-        dangerouslySetInnerHTML={{ __html: arrowImage }}
-      />
-    </Wrapper>
-  )
+class DropdownButton extends React.Component<Props> {
+  render() {
+    return (
+      <Wrapper
+        {...this.props}
+        innerRef={e => {
+          if (this.props.customRef) {
+            this.props.customRef(e)
+          } else if (this.props.innerRef) {
+            this.props.innerRef(e)
+          }
+        }}
+        onClick={e => { if (!this.props.disabled && this.props.onClick) this.props.onClick(e) }}
+      >
+        <Label
+          {...this.props}
+          onClick={() => {}}
+          innerRef={() => {}}
+          data-test-id=""
+          disabled={this.props.disabled}
+        >{this.props.value}</Label>
+        <Arrow
+          {...this.props}
+          innerRef={() => {}}
+          onClick={() => {}}
+          data-test-id=""
+          disabled={this.props.disabled}
+          dangerouslySetInnerHTML={{ __html: arrowImage }}
+        />
+      </Wrapper>
+    )
+  }
 }
 }
 
 
 export default DropdownButton
 export default DropdownButton

+ 57 - 17
src/components/molecules/DatetimePicker/index.jsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 // @flow
 
 
 import React from 'react'
 import React from 'react'
+import ReactDOM from 'react-dom'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled, { injectGlobal } from 'styled-components'
 import styled, { injectGlobal } from 'styled-components'
 import Datetime from 'react-datetime'
 import Datetime from 'react-datetime'
@@ -33,22 +34,21 @@ require('moment/locale/en-gb')
 injectGlobal`${style}`
 injectGlobal`${style}`
 
 
 const Wrapper = styled.div`
 const Wrapper = styled.div`
-  position: relative;
   width: ${StyleProps.inputSizes.regular.width}px;
   width: ${StyleProps.inputSizes.regular.width}px;
 `
 `
 const DropdownButtonStyled = styled(DropdownButton)`
 const DropdownButtonStyled = styled(DropdownButton)`
   font-size: 12px;
   font-size: 12px;
 `
 `
-const DatetimeStyled = styled(Datetime)`
+const Portal = styled.div`
   position: absolute;
   position: absolute;
-  right: -11px;
-  top: 49px;
   z-index: 10;
   z-index: 10;
-
-  .rdtPicker {
-    display: ${props => props.open ? 'block' : 'none'};
+  &.hideTip {
+    .rdtPicker:after {
+      content: none;
+    }
   }
   }
 `
 `
+const DatetimeStyled = styled(Datetime)``
 
 
 type Props = {
 type Props = {
   value: ?Date,
   value: ?Date,
@@ -64,6 +64,8 @@ type State = {
 @observer
 @observer
 class DatetimePicker extends React.Component<Props, State> {
 class DatetimePicker extends React.Component<Props, State> {
   itemMouseDown: boolean
   itemMouseDown: boolean
+  portalRef: HTMLElement
+  buttonRef: HTMLElement
 
 
   constructor() {
   constructor() {
     super()
     super()
@@ -87,10 +89,34 @@ class DatetimePicker extends React.Component<Props, State> {
     window.addEventListener('mousedown', this.handlePageClick, false)
     window.addEventListener('mousedown', this.handlePageClick, false)
   }
   }
 
 
+  componentDidUpdate() {
+    this.setPortalPosition()
+  }
+
   componentWillUnmount() {
   componentWillUnmount() {
     window.removeEventListener('mousedown', this.handlePageClick, false)
     window.removeEventListener('mousedown', this.handlePageClick, false)
   }
   }
 
 
+  setPortalPosition() {
+    if (!this.portalRef || !this.buttonRef) {
+      return
+    }
+
+    const buttonRect = this.buttonRef.getBoundingClientRect()
+    const leftOffset = (buttonRect.left - (this.portalRef.offsetWidth - buttonRect.width)) + 10
+    const tipHeight = 12
+    let topOffset = buttonRect.top + this.buttonRef.offsetHeight + tipHeight
+    let listHeight = this.portalRef.offsetHeight
+
+    if (topOffset + listHeight > window.innerHeight) {
+      topOffset = window.innerHeight - listHeight - 10
+      this.portalRef.classList.add('hideTip')
+    }
+
+    this.portalRef.style.top = `${topOffset + window.pageYOffset}px`
+    this.portalRef.style.left = `${leftOffset}px`
+  }
+
   isValidDate(currentDate: Date, selectedDate: Date): boolean {
   isValidDate(currentDate: Date, selectedDate: Date): boolean {
     if (!this.props.isValidDate) {
     if (!this.props.isValidDate) {
       return true
       return true
@@ -127,6 +153,28 @@ class DatetimePicker extends React.Component<Props, State> {
     this.setState({ date })
     this.setState({ date })
   }
   }
 
 
+  renderDateTimePicker(timezoneDate: ?moment$Moment) {
+    if (!this.state.showPicker) {
+      return null
+    }
+
+    let body: any = document.body
+    return ReactDOM.createPortal((
+      <Portal innerRef={e => { this.portalRef = e }}>
+        <DatetimeStyled
+          input={false}
+          value={timezoneDate}
+          style={{ top: 0, right: 0 }}
+          onChange={date => { this.handleChange(date) }}
+          dateFormat="DD/MM/YYYY"
+          timeFormat="hh:mm A"
+          locale="en-gb"
+          isValidDate={(currentDate, selectedDate) => this.isValidDate(currentDate, selectedDate)}
+        />
+      </Portal>
+    ), body)
+  }
+
   render() {
   render() {
     let timezoneDate = this.state.date
     let timezoneDate = this.state.date
     if (this.props.timezone === 'utc' && timezoneDate) {
     if (this.props.timezone === 'utc' && timezoneDate) {
@@ -136,6 +184,7 @@ class DatetimePicker extends React.Component<Props, State> {
     return (
     return (
       <Wrapper>
       <Wrapper>
         <DropdownButtonStyled
         <DropdownButtonStyled
+          customRef={e => { this.buttonRef = e }}
           width={207}
           width={207}
           value={(timezoneDate && moment(timezoneDate).format('DD/MM/YYYY hh:mm A')) || '-'}
           value={(timezoneDate && moment(timezoneDate).format('DD/MM/YYYY hh:mm A')) || '-'}
           centered
           centered
@@ -144,16 +193,7 @@ class DatetimePicker extends React.Component<Props, State> {
           onMouseDown={() => { this.itemMouseDown = true }}
           onMouseDown={() => { this.itemMouseDown = true }}
           onMouseUp={() => { this.itemMouseDown = false }}
           onMouseUp={() => { this.itemMouseDown = false }}
         />
         />
-        <DatetimeStyled
-          input={false}
-          value={timezoneDate}
-          open={this.state.showPicker}
-          onChange={date => { this.handleChange(date) }}
-          dateFormat="DD/MM/YYYY"
-          timeFormat="hh:mm A"
-          locale="en-gb"
-          isValidDate={(currentDate, selectedDate) => this.isValidDate(currentDate, selectedDate)}
-        />
+        {this.renderDateTimePicker(timezoneDate)}
       </Wrapper>
       </Wrapper>
     )
     )
   }
   }