Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <template>
- <div class="DateTimeSelector w-100">
- <WCard>
- <Scrollable
- class="DaysWrapper"
- horizontal
- >
- <div
- class="Days min-w-100 flex row-stretch-center position-relative"
- :style="{ width: daysWrapperWidth }"
- >
- <div class="position-absolute-fill">
- <div class="DayCircleArea h-100 position-relative">
- <div
- class="DayCircle position-absolute"
- :style="dayCircleStyle"
- />
- </div>
- </div>
- <div
- v-if="multiple"
- class="position-absolute-fill"
- >
- <div class="DayCircleArea h-100 position-relative">
- <div
- class="DayCircle position-absolute"
- :style="secondDayCircleStyle"
- />
- </div>
- </div>
- <a
- v-for="{ date, weekDay, dayInMonth, outOfRange } in days"
- :key="date"
- class="Day"
- :class="{ selected: getSelected(date), between: getStylesDatesBetween(date) && multiple, outOfRange }"
- @click.prevent="handleDates(outOfRange ? undefined : date)"
- >
- <WText
- class="WeekDay"
- weight="semi"
- align="center"
- >
- {{ weekDay }}
- </WText>
- <WText
- class="DayInMonth mb-0"
- weight="bold"
- align="center"
- >
- {{ dayInMonth }}
- </WText>
- </a>
- </div>
- </Scrollable>
- <div class="Hours flex row-stretch-center flex-wrap position-relative">
- <div class="position-absolute-fill">
- <div class="HourPillArea h-100 position-relative">
- <div
- class="HourPill position-absolute"
- :style="hourPillStyle"
- />
- </div>
- </div>
- <fragment
- v-for="(hour, index) in hours"
- :key="hour.value"
- >
- <a
- class="Hour"
- :class="{ selected: hour.value === selectedHour }"
- @click.prevent="selectHour(hour.value)"
- >
- <WText
- class="mb-0"
- weight="semi"
- align="center"
- >
- {{ hour.label }}
- </WText>
- </a>
- <div
- v-if="index === (hours.length / 2) - 1"
- class="HourLineBreak"
- />
- </fragment>
- </div>
- </WCard>
- </div>
- </template>
- <script>
- import VueTypes from 'vue-types'
- import moment from 'moment'
- import range from 'lodash/range'
- import Scrollable from '../Scrollable/Scrollable.vue'
- const UI_FORMAT_WEEKDAY = 'dd'
- const UI_FORMAT_DAY = 'D'
- const UI_FORMAT_HOUR = 'HH:mm'
- export default {
- name: 'DateTimeSelector',
- components: {
- Scrollable,
- },
- props: {
- multiple: VueTypes.bool.def(false),
- dateStart: VueTypes.string.isRequired,
- dateEnd: VueTypes.string.isRequired,
- formatDate: VueTypes.string.def('YYYY-MM-DD'),
- formatHour: VueTypes.string.def('HH:mm'),
- value: VueTypes.oneOf([VueTypes.string, VueTypes.object]).optional,
- },
- data () {
- const arbitraryMoment = moment().minute(0)
- return {
- hours: range(0, 24).map((h) => {
- arbitraryMoment.hour(h)
- return {
- value: arbitraryMoment.format(this.formatHour),
- label: arbitraryMoment.format(UI_FORMAT_HOUR),
- }
- }),
- hoursInTwoRows: false,
- matchMediaForSmallScreens: undefined,
- selectedDates: {
- init: '',
- final: '',
- },
- }
- },
- computed: {
- formatValue () {
- if (this.value) {
- const {
- init: initialDate,
- final: finalDate,
- } = Object.keys(this.value).includes('init', 'final') ? this.value : { init: this.value }
- if (finalDate) return { initialDate, finalDate }
- return { initialDate }
- }
- return ''
- },
- momentStart () {
- return moment(this.dateStart)
- },
- momentEnd () {
- return moment(this.dateEnd)
- },
- momentValue () {
- return moment(this.formatValue.initialDate)
- },
- secondMomentValue () {
- return moment((this.formatValue.finalDate))
- },
- numDays () {
- return this.momentEnd.diff(this.momentStart, 'days') + 1 + 2
- },
- days () {
- return range(-1, this.numDays - 1).map((delta, index) => {
- const currentDate = this.momentStart.clone().add(delta, 'days')
- return {
- date: currentDate.format(this.formatDate),
- weekDay: currentDate.format(UI_FORMAT_WEEKDAY),
- dayInMonth: currentDate.format(UI_FORMAT_DAY),
- outOfRange: index === 0 || index === this.numDays - 1,
- }
- })
- },
- selectedInitialDate () {
- return this.momentValue.format(this.formatDate)
- },
- selectedFinalDate () {
- return this.secondMomentValue.format(this.formatDate)
- },
- selectedHour () {
- return this.momentValue.format(this.formatHour)
- },
- daysWrapperWidth () {
- return `${this.numDays * 40}px`
- },
- dayCircleStyle () {
- const deltaDays = this.momentValue.diff(this.momentStart, 'days') + 1
- return { left: this.getLeftByDay(deltaDays) }
- },
- secondDayCircleStyle () {
- const deltaDays = this.secondMomentValue.diff(this.momentStart, 'days') + 1
- return { left: this.getLeftByDay(deltaDays) }
- },
- hourPillStyle () {
- if (this.hoursInTwoRows) {
- const deltaHours = this.momentValue.hour()
- const left = `${Math.round(((deltaHours % 12) / (12 - 1)) * 10000) / 100}%`
- const top = `${deltaHours >= 12 ? 50 : 0}%`
- return { left, top }
- }
- const deltaHours = this.momentValue.hour()
- const left = `${Math.round((deltaHours / (24 - 1)) * 10000) / 100}%`
- return { left, top: 0 }
- },
- },
- mounted () {
- if (window && window.matchMedia) {
- this.matchMediaForSmallScreens = window.matchMedia('(max-width: 1024px)')
- this.matchMediaForSmallScreens.addListener(this.checkSmallScreens)
- this.hoursInTwoRows = this.matchMediaForSmallScreens.matches
- }
- },
- beforeDestroy () {
- this.matchMediaForSmallScreens && this.matchMediaForSmallScreens.removeListener(this.checkSmallScreens)
- },
- methods: {
- getStylesDatesBetween (date) {
- return (this.selectedInitialDate < date && date < this.selectedFinalDate) && this.selectedDates.final
- },
- getSelected (date) {
- return [this.selectedInitialDate, this.selectedFinalDate].includes(date)
- },
- getLeftByDay (deltaDays) {
- return `${Math.round((deltaDays / (this.numDays - 1)) * 10000) / 100}%`
- },
- handleDates (date) {
- if (this.multiple) {
- const [first, second] = Object.values(this.selectedDates)
- if (first && second) {
- this.selectedDates = {
- ...this.selectedDates,
- init: date,
- final: '',
- }
- } else if (first && second.includes('')) {
- this.selectedDates = {
- ...this.selectedDates,
- final: date,
- }
- } else {
- this.selectedDates = {
- ...this.selectedDates,
- init: date,
- }
- }
- return this.selectDateAndHour(this.selectedDates, this.selectedHour)
- }
- return this.selectDateAndHour(date, this.selectedHour)
- },
- selectHour (hour) {
- this.multiple ? this.selectDateAndHour(this.selectedDates, hour) : this.selectDateAndHour(this.selectedInitialDate, hour)
- },
- selectDateAndHour (date, hour) {
- const { init, final } = date
- if (hour && this.multiple) {
- this.$emit('input', init && final ? {
- init: moment(`${init} ${hour}`).toApiStandard(),
- final: moment(`${final} ${hour}`).toApiStandard(),
- } : { init: moment(`${init} ${hour}`).toApiStandard() })
- } else {
- this.$emit('input', moment(`${date} ${hour}`).toApiStandard())
- }
- },
- checkSmallScreens (e) {
- this.hoursInTwoRows = e.matches
- },
- },
- }
- </script>
- <style scoped lang="scss">
- $daySize: 25px;
- $hourWidth: 36px;
- $hourHeight: 18px;
- $transitionDuration: 0.3s;
- $selectedColor: $primary-medium;
- $hoverColor: lighten($selectedColor, 35%);
- .DateTimeSelector {
- padding-top: 30px;
- }
- .DaysWrapper {
- margin-top: -35px;
- }
- .Days .between {
- .WText.DayInMonth {
- background: $hoverColor;
- opacity: 0.4;
- color: white;
- }
- }
- .DayCircleArea {
- margin-right: $daySize;
- }
- .DayCircle {
- bottom: 0;
- width: $daySize;
- height: $daySize;
- border-radius: $daySize;
- background: $selectedColor;
- transition: all $transitionDuration ease-in-out;
- }
- .Day {
- width: $daySize;
- min-width: $daySize;
- z-index: 1;
- &.outOfRange {
- pointer-events: none;
- }
- }
- .WText.WeekDay {
- width: 100%;
- margin-bottom: 22px;
- font-size: 12px;
- opacity: 0.3;
- transition: opacity $transitionDuration ease-in-out;
- .Day.selected > & {
- opacity: 1;
- }
- }
- .WText.DayInMonth {
- line-height: $daySize;
- border-radius: $daySize;
- font-size: 14px;
- color: $dark;
- transition: all $transitionDuration ease-in-out;
- .Day.selected > & {
- color: white;
- }
- .Day.outOfRange > & {
- color: $gray-medium;
- }
- .Day:not(.selected):hover > & {
- background: $hoverColor;
- }
- }
- .Hours {
- margin-top: 20px;
- }
- .HourPillArea {
- margin-right: $hourWidth;
- }
- .HourPill {
- top: 0;
- width: $hourWidth + 8px;
- height: $hourHeight + 4px;
- margin: -2px -4px;
- border-radius: $hourHeight;
- background: $selectedColor;
- transition: all $transitionDuration ease-in-out;
- }
- .Hour {
- width: $hourWidth;
- min-width: $hourWidth;
- height: $hourHeight;
- z-index: 1;
- .WText {
- font-size: 11px;
- line-height: $hourHeight;
- border-radius: $hourHeight;
- color: $dark;
- opacity: 0.85;
- transition: all $transitionDuration ease-in-out;
- }
- &:nth-child(12):after {
- content: '';
- flex-basis: 100%;
- }
- &LineBreak {
- flex-basis: 100%;
- @media screen and (min-width: 1024px) {
- display: none;
- }
- }
- &.selected .WText {
- color: white;
- font-size: 12px;
- opacity: 1;
- }
- &:not(.selected):hover .WText {
- background: $hoverColor;
- }
- }
- </style>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement