isuke
11/1/2017 - 3:13 PM

VerticalTimeline.vue

<template lang="pug">
.vertical-timeline
  .date(
    v-for="(pair, index) in AdjacentPairs" ,
    v-bind:style="{ 'grid-row': index+1 +  ' / span 1', 'grid-column': '1 / span 1'}"
  )
    slot(name="date", :from="pair[0]", :to="pair[1]")
  .item(
    v-for="(item, index) in items"
    v-bind:style="{ 'grid-row': gridRow(item)}"
  )
    slot(name="item", :item="item")
  .row(
    v-for="(n, index) in AdjacentPairs.length"
    v-bind:style="{ 'grid-row': index+1 +  ' / span 1', 'grid-column': '1 / auto'}"
  )
</template>

<script lang="coffee">
import Moment from 'moment'
import { extendMoment } from 'moment-range'

moment = extendMoment(Moment)

export default
  props:
    startStr:
      type: String
      require: false
      default: null
    endStr:
      type: String
      require: false
      default: null
    format:
      type: String
      require: false
      default: 'YYYY-MM-DD'
    unit:
      type: String
      require: true
      validator: (val) ->
        ['years', 'quarters', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'].includes(val)
    step:
      type: Number
      require: false
      default: 1
    items:
      type: Array
      require: true
  computed:
    startStrOfItems: -> _.minBy(@items, (item) => item.startStr).startStr
    endStrOfItems:   -> _.maxBy(@items, (item) => item.endStr).endStr
    start: ->
      s = if @startStr? then @startStr else @startStrOfItems
      moment(s, @format)
    end: ->
      e = if @endStr? then @endStr else @endStrOfItems
      moment(e, @format)
    range: ->
      moment.range(@start, @end)
    rangeByUnit: -> Array.from(@range.by(@unit, { step: @step }))
    AdjacentPairs: ->
      _.map @rangeByUnit, (a) =>
        [_.cloneDeep(a).startOf(@unit), _.cloneDeep(a).add(@step - 1, @unit).endOf(@unit)]
  methods:
    gridRowIndex: (dateStr) ->
      result = null
      target = moment(dateStr, @format)
      _.forEach @AdjacentPairs, (pair, index) =>
        range = moment.range(pair[0], pair[1])
        if range.contains(target, { exclusive: false })
          result = index
          false
      result
    gridRow: (item) ->
      "#{@gridRowIndex(item.startStr) + 1} / #{@gridRowIndex(item.endStr) + 2}"
</script>

<style lang="stylus" scoped>
.vertical-timeline
  position: relative
  display: grid
  grid-template-columns: auto
  grid-auto-columns: 1fr


  > .date
    z-index: base-z-index + 1

  > .item
    z-index: base-z-index + 1

  > .row
    position: absolute
    top: 0
    bottom: 0
    left: 0
    right: 0
    z-index: base-z-index
</style>