initial
This commit is contained in:
216
layers/ui/components/calendar/index.vue
Normal file
216
layers/ui/components/calendar/index.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div :class="cn.b()">
|
||||
<div :class="cn.e('header')">
|
||||
<UiButton
|
||||
icon="chevron-left"
|
||||
size="small"
|
||||
type="ghost"
|
||||
color="secondary"
|
||||
@click="previousMonth"
|
||||
/>
|
||||
<p :class="cn.e('title')">
|
||||
{{ currentMonth }}
|
||||
</p>
|
||||
<UiButton
|
||||
icon="chevron-right"
|
||||
size="small"
|
||||
type="ghost"
|
||||
color="secondary"
|
||||
@click="nextMonth"
|
||||
/>
|
||||
</div>
|
||||
<div :class="cn.e('week-days')">
|
||||
<div
|
||||
v-for="dayOfWeek in daysOfWeek"
|
||||
:key="dayOfWeek"
|
||||
>
|
||||
{{ dayOfWeek }}
|
||||
</div>
|
||||
</div>
|
||||
<div :class="cn.e('days')">
|
||||
<div
|
||||
v-for="day in monthDays"
|
||||
:key="day.date"
|
||||
:class="getDayClasses(day)"
|
||||
@click="selectDay(day)"
|
||||
>
|
||||
{{ day.day }}
|
||||
</div>
|
||||
</div>
|
||||
<UiButton
|
||||
type="outlined"
|
||||
color="secondary"
|
||||
:class="cn.e('apply-button')"
|
||||
@click="apply"
|
||||
>
|
||||
{{ $t('apply') }}
|
||||
</UiButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// TODO: TypeScript
|
||||
import { computed, reactive, ref, toRef, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import UiButton from '../button/index.vue'
|
||||
import useClassname from '../../composables/use-classname'
|
||||
|
||||
const props = defineProps({
|
||||
allowRangeSelection: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const cn = useClassname('ui-calendar')
|
||||
|
||||
const modelValue = toRef(props, 'modelValue')
|
||||
|
||||
const today = dayjs()
|
||||
const dateRange = reactive({
|
||||
start: null,
|
||||
end: null,
|
||||
})
|
||||
const currentDate = ref(today)
|
||||
const daysOfWeek = dayjs.weekdaysMin()
|
||||
|
||||
const currentMonth = computed(() => currentDate.value.format('MMMM, YYYY'))
|
||||
|
||||
const monthDays = computed(() => {
|
||||
const daysInMonth = currentDate.value.daysInMonth()
|
||||
const leadingDays = currentDate.value.startOf('month').day()
|
||||
|
||||
const days = []
|
||||
|
||||
for (let i = 0; i < leadingDays; i++) {
|
||||
const date = currentDate.value
|
||||
.subtract(1, 'month')
|
||||
.endOf('month')
|
||||
.subtract(leadingDays - i - 1, 'day')
|
||||
|
||||
days.push(date)
|
||||
}
|
||||
|
||||
for (let i = 0; i < daysInMonth; i++) {
|
||||
const date = currentDate.value.date(i + 1)
|
||||
|
||||
days.push(date)
|
||||
}
|
||||
|
||||
const num = 7 - (days.length % 7 || 7)
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
const date = currentDate.value.add(1, 'month').date(i + 1)
|
||||
|
||||
days.push(date)
|
||||
}
|
||||
|
||||
return days.map(formatDateObject)
|
||||
})
|
||||
|
||||
function formatDateObject(date) {
|
||||
return {
|
||||
raw: date,
|
||||
day: date.date(),
|
||||
month: date.get('month'),
|
||||
isToday: date.isToday(),
|
||||
isCurrentMonth: date.isSame(currentDate.value, 'month'),
|
||||
isPeriod:
|
||||
!!dateRange.start
|
||||
&& !!dateRange.end
|
||||
&& date.isBetween(dateRange.start, dateRange.end, 'day', '[]'),
|
||||
isPeriodStart: !!dateRange.start && date.isSame(dateRange.start, 'day'),
|
||||
isPeriodEnd: !!dateRange.end && date.isSame(dateRange.end, 'day'),
|
||||
}
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
currentDate.value = currentDate.value.add(1, 'month')
|
||||
}
|
||||
function previousMonth() {
|
||||
currentDate.value = currentDate.value.subtract(1, 'month')
|
||||
}
|
||||
|
||||
function selectDay(day) {
|
||||
let { start, end } = dateRange
|
||||
|
||||
if (props.allowRangeSelection) {
|
||||
const isSameStartDay = day.raw.isSame(start, 'day')
|
||||
|
||||
if (isSameStartDay) {
|
||||
start = null
|
||||
end = null
|
||||
}
|
||||
else if (!start || end) {
|
||||
start = day.raw
|
||||
end = null
|
||||
}
|
||||
else {
|
||||
end = day.raw
|
||||
|
||||
if (dayjs(start).isAfter(dayjs(end), 'day'))
|
||||
[start, end] = [end, start]
|
||||
}
|
||||
}
|
||||
else {
|
||||
start = day.raw
|
||||
end = null
|
||||
}
|
||||
|
||||
dateRange.start = start
|
||||
dateRange.end = end
|
||||
}
|
||||
|
||||
watch(
|
||||
modelValue,
|
||||
(value) => {
|
||||
const [start, end] = value
|
||||
|
||||
dateRange.start = !!start && dayjs(start)
|
||||
dateRange.end = !!end && dayjs(end)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function getDayClasses(day) {
|
||||
const classes = [cn.e('cell')]
|
||||
|
||||
if (day.isCurrentMonth)
|
||||
classes.push(cn.em('cell', 'current-month'))
|
||||
|
||||
if (day.isPeriod)
|
||||
classes.push(cn.em('cell', 'period'))
|
||||
|
||||
if (day.isPeriodStart)
|
||||
classes.push(cn.em('cell', 'period-start'))
|
||||
else if (day.isPeriodEnd)
|
||||
classes.push(cn.em('cell', 'period-end'))
|
||||
|
||||
if (day.isToday)
|
||||
classes.push(cn.em('cell', 'current-day'))
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function apply() {
|
||||
const value = []
|
||||
|
||||
if (dateRange.start)
|
||||
value.push(dateRange.start.startOf('day').valueOf())
|
||||
|
||||
if (dateRange.end)
|
||||
value.push(dateRange.end.startOf('day').valueOf())
|
||||
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'styles';
|
||||
</style>
|
||||
140
layers/ui/components/calendar/styles.scss
Normal file
140
layers/ui/components/calendar/styles.scss
Normal file
@@ -0,0 +1,140 @@
|
||||
@use '../../styles/variables' as *;
|
||||
@use '../../styles/mixins' as *;
|
||||
|
||||
.ui-calendar {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
background-color: var(--calendar-background);
|
||||
color: var(--calendar-color);
|
||||
padding: 16px 16px 24px 16px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
|
||||
i {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include txt-i-sb;
|
||||
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&__week-days,
|
||||
&__days {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 32px);
|
||||
color: var(--calendar-day-color);
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__week-days {
|
||||
@include txt-s;
|
||||
|
||||
grid-auto-rows: 24px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&__days {
|
||||
@include txt-i-m;
|
||||
|
||||
grid-auto-rows: 32px;
|
||||
}
|
||||
|
||||
&__apply-button {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&__cell {
|
||||
transition: 0.2s ease-out;
|
||||
transition-property: background-color;
|
||||
|
||||
&:not(&--period-start, &--period-end, &--period):hover {
|
||||
background: var(--calendar-day-background-hover-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
&--current-month {
|
||||
color: var(--calendar-current-month-day-color);
|
||||
}
|
||||
|
||||
&--current-day {
|
||||
color: var(--calendar-today-color);
|
||||
}
|
||||
|
||||
&--period {
|
||||
background-color: var(--calendar-period-background);
|
||||
}
|
||||
|
||||
&--period-start,
|
||||
&--period-end {
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
color: var(--calendar-period-start-end-color);
|
||||
z-index: 2;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&::before {
|
||||
width: 50%;
|
||||
background-color: var(--calendar-period-background);
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
background-color: var(--calendar-period-start-end-background);
|
||||
border-radius: inherit;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
&--period-start {
|
||||
&::before {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--period-end {
|
||||
&::before {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* prettier-ignore */
|
||||
@include element-variant(
|
||||
'calendar',
|
||||
'',
|
||||
(
|
||||
'color': $clr-black,
|
||||
'background': $clr-white,
|
||||
'day-color': $clr-grey-500,
|
||||
'current-month-day-color': $clr-black,
|
||||
'today-color': $clr-cyan-500,
|
||||
'arrow-color': $clr-grey-600,
|
||||
'period-start-end-color': $clr-white,
|
||||
'period-start-end-background': $clr-cyan-500,
|
||||
'period-background': $clr-grey-100,
|
||||
'day-background-hover-color': $clr-grey-100
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user