Skip to content

Commit

Permalink
feat: add weekly view to v2
Browse files Browse the repository at this point in the history
  • Loading branch information
luisrondow committed Jan 16, 2024
1 parent fc45e54 commit a4e16c7
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 7 deletions.
5 changes: 5 additions & 0 deletions docs/.vuepress/components/guide/layouts/weekly-view.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div class="example">
<v-calendar view="weekly" />
</div>
</template>
10 changes: 10 additions & 0 deletions docs/layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ However, these empty weeks can be 'trimmed' by setting the `trim-weeks` prop.
<v-calendar trim-weeks>
```

## Weekly View

Set the `view` prop to display the calendar in 'weekly' view.

<guide-layouts-weekly-view />

```html
<v-calendar view="weekly" />
```

## Week Numbers :tada:

Show week numbers in the calendar using the `show-weeknumbers` prop.
Expand Down
227 changes: 223 additions & 4 deletions src/components/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export default {
attrs: {
...this.$attrs,
},
key: arrayHasItems(this.pages) ? this.pages[0].key : '',
key: arrayHasItems(this.pages) ? `${this.pages[0].key}${this.pages[0].view === 'weekly' ? this.pages[0].currentWeek : ''}` : '',
},
panes,
),
Expand Down Expand Up @@ -291,6 +291,14 @@ export default {
attributes: [Object, Array],
trimWeeks: Boolean,
disablePageSwipe: Boolean,
view: {
type: String,
default: 'monthly',
require: false,
validator(value) {
return ['monthly', 'weekly'].includes(value);
},
},
},
data() {
return {
Expand Down Expand Up @@ -394,6 +402,10 @@ export default {
}
}
},
view() {
this.refreshPages({ page: this.pages[0], ignoreCache: true });
this.refreshAttrs(this.pages, this.store.list, null, true);
},
},
created() {
this.refreshLocale();
Expand Down Expand Up @@ -481,17 +493,147 @@ export default {
}
// Hide nav popover for good measure
this.$refs.navPopover.hide({ hideDelay: 0 });
// Move pages on weekly view
const [currentPage] = this.pages;
const isHeaderNav = arg && typeof arg === 'object' && Object.keys(arg).includes(...['month', 'year']);
if (!isHeaderNav && currentPage && currentPage.view === 'weekly') {
if (opts.focusOnDay) {
return Promise.all([
this.refreshPages({
...opts,
page: opts.fromPage,
position: 1,
force: true,
}),
this.setCurrentWeekByDay(opts.focusOnDay.day)
]);
}
const shouldMovePageWeek = this.adjustWeeklyPage(currentPage, opts.fromPage);
if (shouldMovePageWeek) {
const isFirstWeek = currentPage.currentWeek === 0;
const isLastWeek = currentPage.currentWeek === currentPage.lastWeek;
if (isFirstWeek && !currentPage.weekDays[0][0].inMonth) {
const currentDate = {
month: currentPage.month,
year: currentPage.year
};
const prevMonthDate = {
month: currentPage.prevMonthComps.month,
year: currentPage.prevMonthComps.year
};
currentPage.title = this.mixedWeekTitle(prevMonthDate, currentDate);
} else if (isLastWeek && !currentPage.weekDays[currentPage.lastWeek][6].inMonth) {
const currentDate = {
month: currentPage.month,
year: currentPage.year
};
const nextMonthDate = {
month: currentPage.nextMonthComps.month,
year: currentPage.nextMonthComps.year
};
currentPage.title = this.mixedWeekTitle(currentDate, nextMonthDate);
} else {
currentPage.title = this.$locale.format(new Date(currentPage.year, currentPage.month - 1, 15), this.$locale.masks.title);
}
this.refreshAttrs(this.pages, this.store.list, null, true);
return Promise.resolve(true);
}
}
// Move to new `fromPage` if it's different from the current one
if (opts.fromPage && !pageIsEqualToPage(opts.fromPage, this.firstPage)) {
return this.refreshPages({
...opts,
page: opts.fromPage,
position: 1,
force: true,
currentWeek: isHeaderNav ? 0 : null
});
}
return Promise.resolve(true);
},
adjustWeeklyPage(currentPage, fromPage) {
if (!currentPage || currentPage.view !== 'weekly') {
return false;
}
if (fromPage.year !== currentPage.year) {
if (fromPage.year > currentPage.year && currentPage.currentWeek < currentPage.lastWeek) {
currentPage.currentWeek++;
this.transitionName = this.getWeekpageTransition(false);
return true;
}
if (fromPage.year < currentPage.year && currentPage.currentWeek > 0) {
currentPage.currentWeek--;
this.transitionName = this.getWeekpageTransition(true);
return true;
}
return false;
}
const shouldIncrementWeek = fromPage.month > currentPage.month && currentPage.currentWeek < currentPage.lastWeek;
if (shouldIncrementWeek) {
currentPage.currentWeek++;
this.transitionName = this.getWeekpageTransition(false);
return true;
}
const shouldDecrementWeek = fromPage.month < currentPage.month && currentPage.currentWeek > 0;
if (shouldDecrementWeek) {
currentPage.currentWeek--;
this.transitionName = this.getWeekpageTransition(true);
return true;
}
return false;
},
setCurrentWeekByDay(day) {
return new Promise((resolve, reject) => {
const currentPage = this.pages[0];
const { month } = currentPage;
const { currentWeek, lastWeek } = currentPage;
const weekDay = currentPage.days.find(d => d.day === day && d.month === month);
if (!weekDay) {
reject(new Error('Day not found in current page.'));
return;
}
const week = weekDay.week;
const isSameWeek = week === currentWeek;
if (isSameWeek) {
resolve(false);
return;
}
if (week > lastWeek) {
currentPage.currentWeek = lastWeek;
resolve(true);
return;
}
currentPage.currentWeek = week - 1;
resolve(true);
});
},
focusDate(date, opts = {}) {
// Move to the given date
return this.move(date, opts).then(() => {
Expand Down Expand Up @@ -580,7 +722,7 @@ export default {
}
return page;
},
refreshPages({ page, position = 1, force, transition, ignoreCache } = {}) {
refreshPages({ page, position = 1, force, transition, ignoreCache, currentWeek } = {}) {
return new Promise((resolve, reject) => {
const { fromPage, toPage } = this.getTargetPageRange(page, {
position,
Expand All @@ -589,7 +731,7 @@ export default {
// Create the new pages
const pages = [];
for (let i = 0; i < this.count; i++) {
pages.push(this.buildPage(addPages(fromPage, i), ignoreCache));
pages.push(this.buildPage({ ...addPages(fromPage, i), ignoreCache, currentWeek }));
}
// Refresh disabled days for new pages
this.refreshDisabledDays(pages);
Expand Down Expand Up @@ -649,6 +791,16 @@ export default {
// Horizontal slide
return movePrev ? 'slide-right' : 'slide-left';
},
getWeekpageTransition(isPrev, transition = this.transition) {
if (transition === 'none') return transition;
// Vertical slide
if (transition === 'slide-v') {
return isPrev ? 'slide-down' : 'slide-up';
}
// Horizontal slide
return isPrev ? 'slide-right' : 'slide-left';
},
getPageForAttributes() {
let page = null;
const attr = this.store.pinAttr;
Expand All @@ -659,7 +811,7 @@ export default {
}
return page;
},
buildPage({ month, year }, ignoreCache) {
buildPage({ month, year, currentWeek }, ignoreCache) {
const key = `${year.toString()}-${month.toString()}`;
let page = this.pages.find(p => p.key === key);
if (!page || ignoreCache) {
Expand All @@ -671,6 +823,7 @@ export default {
key,
month,
year,
view: this.view,
weeks: this.trimWeeks ? monthComps.weeks : 6,
title: this.$locale.format(date, this.$locale.masks.title),
shortMonthLabel: this.$locale.format(date, 'MMM'),
Expand All @@ -680,6 +833,7 @@ export default {
monthComps,
prevMonthComps,
nextMonthComps,
weekDays: [],
canMove: pg => this.canMove(pg),
move: pg => this.move(pg),
moveThisMonth: () => this.moveThisMonth(),
Expand All @@ -689,9 +843,74 @@ export default {
};
// Assign day info
page.days = this.$locale.getCalendarDays(page);
if (this.view === 'weekly') {
const pageWeeks = page.days.reduce((acc, day) => {
const { weeks } = acc;
const lastWeekFull = weeks[weeks.length - 1] && weeks[weeks.length - 1].length >= 7;
if (day.inNextMonth && lastWeekFull) {
return acc;
}
if (!weeks[weeks.length - 1] || lastWeekFull) {
return {
weeks: [...weeks, [day]]
};
}
return {
weeks: [...weeks.slice(0, -1), [...weeks[weeks.length - 1], day]]
};
}, { weeks: [] });
page.weekDays = pageWeeks.weeks;
page.lastWeek = page.weekDays.length - 1;
const [currentPage] = this.pages;
if (typeof currentWeek === 'number') {
page.currentWeek = currentWeek;
} else if (currentPage) {
const isFirstWeekdayInNewMonth = page.weekDays[0][0].inMonth ? 0 : 1;
const isLastWeekdayInNewMonth = page.weekDays[page.weekDays.length - 1][6].inMonth ? 0 : 1;
const isNewPageAfterCurrent = page.year > currentPage.year || (page.year === currentPage.year && page.month > currentPage.month);
page.currentWeek = isNewPageAfterCurrent ? isFirstWeekdayInNewMonth : page.lastWeek - isLastWeekdayInNewMonth;
} else {
page.currentWeek = 0;
}
if (page.currentWeek === 0) {
const currentDate = {
month: page.month,
year: page.year
};
const prevMonthDate = {
month: page.prevMonthComps.month,
year: page.prevMonthComps.year
};
page.title = this.mixedWeekTitle(prevMonthDate, currentDate);
}
}
}
return page;
},
mixedWeekTitle(oldDate, newDate) {
const { month: oldMonth, year: oldYear } = oldDate;
const { month: newMonth, year: newYear } = newDate;
const oldTitle = this.$locale.format(new Date(oldYear, oldMonth - 1, 1), 'MMM');
const newTitle = this.$locale.format(new Date(newYear, newMonth - 1, 1), 'MMM');
return oldYear === newYear
? `${oldTitle} - ${newTitle} ${oldYear}`
: `${oldTitle} ${oldYear} - ${newTitle} ${newYear}`;
},
initStore() {
// Create a new attribute store
this.store = new AttributeStore(
Expand Down
5 changes: 3 additions & 2 deletions src/components/CalendarDay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default {
'vc-day',
...this.day.classes,
{ 'vc-day-box-center-center': !this.$scopedSlots['day-content'] },
{ 'is-not-in-month': !this.inMonth },
{ 'is-not-in-month': !this.inMonth && !this.isWeeklyView },
],
},
[backgroundsLayer(), contentLayer(), dotsLayer(), barsLayer()],
Expand All @@ -121,6 +121,7 @@ export default {
inject: ['sharedState'],
props: {
day: { type: Object, required: true },
isWeeklyView: { type: Boolean, default: false },
},
data() {
return {
Expand Down Expand Up @@ -185,7 +186,7 @@ export default {
let tabindex;
if (this.day.isFocusable) {
tabindex = '0';
} else if (this.day.inMonth) {
} else if (this.day.inMonth || this.isWeeklyView) {
tabindex = '-1';
}
return {
Expand Down
5 changes: 4 additions & 1 deletion src/components/CalendarPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ export default {
// Day cells
const dayCells = [];
const { daysInWeek } = this.locale;
this.page.days.forEach((day, i) => {
const renderDays = this.page.view !== 'weekly' ? this.page.days : this.page.weekDays[this.page.currentWeek];
renderDays.forEach((day, i) => {
const mod = i % daysInWeek;
// Insert weeknumber cell on left side if needed
if (
Expand All @@ -103,6 +105,7 @@ export default {
h(CalendarDay, {
attrs: {
day,
isWeeklyView: this.page.view === 'weekly',
},
on: {
...this.$listeners,
Expand Down

0 comments on commit a4e16c7

Please sign in to comment.