Browse Source

feat: add choose month mode

yhhu 5 years ago
parent
commit
44ccfeb942

+ 22 - 13
src/app.js

@@ -3,21 +3,30 @@ import React from 'react'
 import ReactDOM from 'react-dom'
 import DatePicker from './index'
 
-const disabledDate = current => (
-  // start & end
-  // ['2018-01-02', current]
+const { MonthPicker } = DatePicker
 
-  // end
-  [current]
-)
+// const disabledDate = current => (
+// start & end
+// ['2018-01-02', current]
+
+// end
+// [current]
+// )
 
 const App = () => (
-  <DatePicker
-    // disable
-    disabledDate={current => disabledDate(current)}
-    defaultDate="2018-01-31"
-    placeholder="please choose date"
-    onSelectDate={day => console.log(day)}
-  />
+  <React.Fragment>
+    <DatePicker
+      // disable
+      // disabledDate={current => disabledDate(current)}
+      defaultDate="2018-01-31"
+      placeholder="please choose date"
+      onSelectDate={day => console.log(day)}
+    />
+    <MonthPicker
+      placeholder="Select month"
+      year="2018"
+      month="01"
+    />
+  </React.Fragment>
 )
 ReactDOM.render(<App />, document.getElementById('container'))

+ 3 - 0
src/components/calendar/header.css

@@ -5,6 +5,9 @@
   grid-auto-rows: 34px;
   border-bottom: 1px solid #e8e8e8;
 }
+.wrapper-3 {
+  grid-template-columns: 25px auto 25px;
+}
 .wrapper i {
   cursor: pointer;
 }

+ 1 - 5
src/components/index/DatePicker.js

@@ -116,7 +116,6 @@ class DatePicker extends Component {
 
     const weekTags = getWeekSort(nextModel)
     const changeModelDays = getDaysOfMonth(year, month, nextModel)
-    // debugger
     this._setDateFromSepcialDay(
       { originDays: changeModelDays, setDay: value, currentDay: value },
       { model: nextModel, weekTags: weekTags },
@@ -268,10 +267,7 @@ class DatePicker extends Component {
           }
         }
       >
-        <Modal
-          isMounted={showModal}
-          delayTime={200}
-        />
+        <Modal isMounted={showModal} delayTime={200} />
       </DateContext.Provider>
     )
   }

+ 158 - 0
src/components/index/MonthPicker.js

@@ -0,0 +1,158 @@
+/* eslint-disable no-underscore-dangle */
+import React from 'react'
+import classNames from 'classnames'
+import PropTypes from 'prop-types'
+import Styles from './picker.css'
+import {
+  MONTH_DEFAULT_PLACEHOLDER, noop, MONTH_MODE, MONTH_DECADE_MODE,
+} from '../../const'
+import MonthModal from '../modal/MonthModal'
+import { getCurrentYear, getCurrentMonth, formatMonthOrDay } from '../../utils'
+
+class MonthPicker extends React.Component {
+  constructor(props) {
+    super(props)
+    const { year, month } = this.props
+    this.state = {
+      showModal: false,
+      year: year,
+      month: month,
+      mode: MONTH_MODE,
+      value: `${year}-${formatMonthOrDay(month)}`,
+    }
+  }
+
+  onModalOpen = () => {
+    this.setState({ showModal: true })
+  }
+
+  onModalClose = () => {
+    this.setState({ showModal: false })
+  }
+
+  onInputChange = noop
+
+  _getTitleByMode = () => {
+    const { year } = this.state
+    return year
+  }
+
+  onChangeMode = () => {
+    const { mode } = this.state
+    if (mode === MONTH_MODE) {
+      this.setState({ mode: MONTH_DECADE_MODE })
+    }
+  }
+
+  onSelectYearOrMonth = val => {
+    const { mode } = this.state
+    if (mode === MONTH_DECADE_MODE) {
+      this.setState({ mode: MONTH_MODE, year: val })
+    } else {
+      const { year } = this.state
+      this.setState({ month: val, value: `${year}-${formatMonthOrDay(val)}` }, () => this.onModalClose())
+    }
+  }
+
+  onPrev = () => {
+    const { mode } = this.state
+    if (mode === MONTH_DECADE_MODE) {
+      console.log('MONTH_DECADE_MODE')
+    } else {
+      const { year } = this.state
+      this.setState({ year: +year - 1 })
+    }
+  }
+
+  onNext = () => {
+    const { mode } = this.state
+    if (mode === MONTH_DECADE_MODE) {
+      console.log('MONTH_DECADE_MODE')
+    } else {
+      const { year } = this.state
+      this.setState({ year: +year + 1 })
+    }
+  }
+
+  renderInput = () => {
+    const { inline, placeholder, disable } = this.props
+    const { value } = this.state
+
+    return (
+      <React.Fragment>
+        <div
+          className={Styles.container}
+          style={inline ? { display: 'inline-block' } : {}}
+        >
+          <span className={Styles.inputWrapper}>
+            <input
+              type="text"
+              disabled={disable}
+              readOnly={disable}
+              placeholder={placeholder}
+              className={classNames(Styles.input, { [Styles.disable]: disable })}
+              value={value}
+              onChange={e => this.onInputChange(e)}
+              onFocus={e => this.onModalOpen(e)}
+            />
+          </span>
+          <i className={Styles.calendar} />
+          <i
+            className={Styles.close}
+            onClick={this.onInputClear}
+            role="presentation"
+          />
+          <div className={Styles.line} />
+        </div>
+        { disable && <div className={Styles.inputDisable} /> }
+      </React.Fragment>
+    )
+  }
+
+  renderModal = () => {
+    const {
+      showModal, year, month, mode,
+    } = this.state
+    return (
+      <MonthModal
+        isMounted={showModal}
+        delayTime={200}
+        year={year}
+        month={month}
+        title={this._getTitleByMode(mode)}
+        mode={mode}
+        onChangeMode={this.onChangeMode}
+        onSelectYearOrMonth={this.onSelectYearOrMonth}
+        onPrev={this.onPrev}
+        onNext={this.onNext}
+      />
+    )
+  }
+
+  render() {
+    return (
+      <div className={`picker-wrapper ${Styles.wrapper}`}>
+        { this.renderInput() }
+        { this.renderModal() }
+      </div>
+    )
+  }
+}
+
+MonthPicker.defaultProps = {
+  inline: false,
+  placeholder: MONTH_DEFAULT_PLACEHOLDER,
+  disable: false,
+  year: getCurrentYear(),
+  month: getCurrentMonth(),
+}
+
+MonthPicker.propTypes = {
+  inline: PropTypes.bool,
+  placeholder: PropTypes.string,
+  year: PropTypes.string,
+  month: PropTypes.string,
+  disable: PropTypes.bool,
+}
+
+export default MonthPicker

+ 133 - 0
src/components/modal/MonthModal.js

@@ -0,0 +1,133 @@
+/* eslint-disable no-underscore-dangle */
+import React from 'react'
+import classNames from 'classnames'
+import PropTypes from 'prop-types'
+import Styles from './modal.css'
+import HeaderStyles from '../calendar/header.css'
+import BodyStyles from './month.css'
+import delayUnmounting from '../delayUnmounting'
+import { MONTH_MODE, MONTH_DECADE_MODE, noop } from '../../const'
+import { getDecadeByGivenYear, getChineseMonth } from '../../helper'
+
+class MonthModal extends React.Component {
+  _getRenderBody = () => {
+    const { year, month, mode } = this.props
+    if (mode === MONTH_MODE) {
+      return getChineseMonth(month)
+    }
+    return getDecadeByGivenYear(year)
+  }
+
+  _getPrevAndNextTitles = () => {
+    const { mode } = this.props
+    if (mode === MONTH_MODE) {
+      return {
+        prev: '上一年',
+        next: '下一年',
+      }
+    }
+    return {
+      prev: '上一年代',
+      next: '下一年代',
+    }
+  }
+
+  renderHeader = () => {
+    const {
+      title, onPrev, onNext, onChangeMode, mode,
+    } = this.props
+    const titles = this._getPrevAndNextTitles()
+    return (
+      <div className={`${HeaderStyles.wrapper} ${HeaderStyles.wrapper3}`}>
+        <i
+          className={HeaderStyles.prevYear}
+          role="presentation"
+          title={titles.prev}
+          onClick={e => onPrev(e)}
+        />
+        <div className={HeaderStyles.text}>
+          <span
+            role="presentation"
+            className={classNames({ [HeaderStyles.link]: mode === MONTH_MODE })}
+            onClick={mode === MONTH_DECADE_MODE ? noop : e => onChangeMode(e)}
+          >
+            {title}
+          </span>
+        </div>
+        <i
+          className={HeaderStyles.nextYear}
+          role="presentation"
+          title={titles.next}
+          onClick={e => onNext(e)}
+        />
+      </div>
+    )
+  }
+
+  renderBody = () => {
+    const { onSelectYearOrMonth } = this.props
+    const bodyArr = this._getRenderBody()
+    return (
+      <div className={BodyStyles.wrapper}>
+        { bodyArr.map(body => (
+          <div
+            key={body.value}
+            className={BodyStyles.cell}
+          >
+            <span
+              className={classNames(BodyStyles.td, {
+                [BodyStyles.current]: body.flag === 'current',
+                [BodyStyles.last]: body.flag === 'prev',
+                [BodyStyles.next]: body.flag === 'next',
+              })}
+              role="presentation"
+              onClick={e => onSelectYearOrMonth(body.code, e)}
+            >
+              {body.value}
+            </span>
+          </div>
+        )) }
+      </div>
+    )
+  }
+
+  render() {
+    const { isMounted } = this.props
+
+    return (
+      <React.Fragment>
+        <div className={classNames(
+          `${Styles.container} ${Styles.monthContainer}`,
+          {
+            [Styles.in]: isMounted,
+            [Styles.out]: !isMounted,
+          },
+        )}
+        >
+          <div className={`${Styles.panel} ${Styles.monthPanel}`}>
+            { this.renderHeader() }
+            { this.renderBody() }
+          </div>
+        </div>
+      </React.Fragment>
+    )
+  }
+}
+
+MonthModal.defaultProps = {
+  isMounted: false,
+}
+
+MonthModal.propTypes = {
+  isMounted: PropTypes.bool,
+  title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+  year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+  month: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+  mode: PropTypes.string.isRequired,
+  onPrev: PropTypes.func.isRequired,
+  onNext: PropTypes.func.isRequired,
+  onChangeMode: PropTypes.func.isRequired,
+  onSelectYearOrMonth: PropTypes.func.isRequired,
+}
+
+export default delayUnmounting(MonthModal)

+ 10 - 0
src/components/modal/modal.css

@@ -57,6 +57,9 @@
   z-index: 9;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
 }
+.month-container {
+  z-index: 10;
+}
 .in {
   animation: slideDownIn 0.2s;
 }
@@ -65,4 +68,11 @@
 }
 .panel {
   position: relative;
+}
+.month-panel {
+  height: 100%;
+  min-height: 298px;
+  display: grid;
+  grid-auto-columns: 1fr;
+  grid-template-rows: 35px auto;
 }

+ 42 - 0
src/components/modal/month.css

@@ -0,0 +1,42 @@
+.wrapper {
+  width: 100%;
+  height: 100%;
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  grid-template-rows: repeat(4, 1fr);
+}
+.cell {
+  display: flex;
+  align-content: center;
+  justify-content: center;
+  align-items: center;
+}
+.td {
+  display: inline-block;
+  margin: 0 auto;
+  color: rgba(0, 0, 0, 0.65);
+  background-color: transparent;
+  text-align: center;
+  height: 24px;
+  line-height: 24px;
+  padding: 0 8px;
+  border-radius: 2px;
+  transition: background-color 0.3s ease;
+}
+.td:hover {
+  color: #40a9ff;
+  background: #e6f7ff;
+  cursor: pointer;
+}
+.current {
+  background: #1890ff;
+  color: #fff;
+}
+.current:hover {
+  background: #1890ff;
+  color: #fff;
+}
+.last, .next {
+  user-select: none;
+  color: rgba(0, 0, 0, 0.25);
+}

+ 6 - 0
src/const/index.js

@@ -1,4 +1,5 @@
 export * from './week'
+export * from './month'
 
 export const CHINESE_MODEL = 'CHINESE_MODEL'
 export const WESTERN_MODEL = 'WESTERN_MODEL'
@@ -10,3 +11,8 @@ export const NEXT_DAY = 'NEXT_DAY'
 export const CURRENT_DAY = 'CURRENT_DAY'
 
 export const INPUT_DEFAULT_PLACEHOLDER = '请选择日期'
+export const MONTH_DEFAULT_PLACEHOLDER = '请选择月份'
+
+export const MONTH_YEAR_MODE = 'YEAR_MODE'
+export const MONTH_DECADE_MODE = 'DECADE_MODE'
+export const MONTH_MODE = 'MONTH_MODE'

+ 29 - 0
src/const/month.js

@@ -0,0 +1,29 @@
+const JAN = '一月'
+const FEB = '二月'
+const MAR = '三月'
+const APR = '四月'
+const MAY = '五月'
+const JUN = '六月'
+const JUL = '七月'
+const AUG = '八月'
+const SEP = '九月'
+const OCT = '十月'
+const NOV = '十一月'
+const DEC = '十二月'
+
+const monthMap = new Map()
+monthMap
+  .set(1, JAN)
+  .set(2, FEB)
+  .set(3, MAR)
+  .set(4, APR)
+  .set(5, MAY)
+  .set(6, JUN)
+  .set(7, JUL)
+  .set(8, AUG)
+  .set(9, SEP)
+  .set(10, OCT)
+  .set(11, NOV)
+  .set(12, DEC)
+
+export { monthMap }

+ 63 - 1
src/helper.js

@@ -1,5 +1,5 @@
 import {
-  weekMap, CHINESE_MODEL, PREV_DAY, CURRENT_DAY, NEXT_DAY,
+  weekMap, CHINESE_MODEL, PREV_DAY, CURRENT_DAY, NEXT_DAY, monthMap,
 } from './const'
 import {
   getWeekOfMonth,
@@ -193,3 +193,65 @@ export const resetCalendarFromSpecialDay = (originDays, date,
   const afterDays = setSelectedDaysAndRange(days, date, disabledRange)
   return { afterDays }
 }
+
+export const getDecadeByGivenYear = year => {
+  const tempYear = +year
+  const factor = Math.floor(tempYear / 10)
+  const result = []
+
+  for (let i = -1; i <= 10; i++) {
+    const r = 10 * factor + i
+
+    if (i === -1) {
+      result.push({
+        flag: 'prev',
+        value: r,
+        code: r,
+      })
+    } else if (r < tempYear) {
+      result.push({
+        flag: 'normal',
+        value: r,
+        code: r,
+      })
+    } else if (r === tempYear) {
+      result.push({
+        flag: 'current',
+        value: r,
+        code: r,
+      })
+    } else if (i === 10) {
+      result.push({
+        flag: 'next',
+        value: r,
+        code: r,
+      })
+    } else {
+      result.push({
+        flag: 'normal',
+        value: r,
+        code: r,
+      })
+    }
+  }
+  return result
+}
+
+export const getChineseMonth = month => {
+  const months = [...monthMap.values()]
+  return months.map((m, i) => {
+    const code = i + 1
+    if (code === +month) {
+      return {
+        flag: 'current',
+        value: m,
+        code: code,
+      }
+    }
+    return {
+      flag: 'normal',
+      value: m,
+      code: code,
+    }
+  })
+}

+ 3 - 0
src/index.js

@@ -1,4 +1,7 @@
 import DatePicker from './components/index/DatePicker'
+import MonthPicker from './components/index/MonthPicker'
 import './styles/index.css'
 
+DatePicker.MonthPicker = MonthPicker
+
 export default DatePicker