diff --git a/live-editing/configs/CalendarConfigGenerator.ts b/live-editing/configs/CalendarConfigGenerator.ts
index 8bd3922d9..726795d00 100644
--- a/live-editing/configs/CalendarConfigGenerator.ts
+++ b/live-editing/configs/CalendarConfigGenerator.ts
@@ -73,6 +73,12 @@ export class CalendarConfigGenerator implements IConfigGenerator {
shortenComponentPathBy: "/scheduling/calendar/"
}));
+ configs.push(new Config({
+ component: 'CalendarTemplatesComponent',
+ appConfig: BaseAppConfig,
+ shortenComponentPathBy: "/scheduling/calendar/"
+ }));
+
configs.push(new Config({
component: 'CalendarStylingSampleComponent',
appConfig: BaseAppConfig,
diff --git a/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.html b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.html
new file mode 100644
index 000000000..8b20f6664
--- /dev/null
+++ b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+ @let count = getEvents(parts.date).length;
+ {{ count ? pluralize(count) : 'No events' }}
+
+
+
+
+ {{ parts.weekday.value }}, {{ parts.month.value }} {{ parts.day.value }}
+
+
+
+
+ @if (calendar.activeView === 'month') {
+
+ {{ parts.month.value }}
+
+ }
+
+ {{ parts.year.value }}
+
+ @if (calendar.activeView === 'month') {
+ @let monthCount = getMonthEventsCount(parts.date);
+ @if (monthCount) {
+ {{ pluralize(monthCount) }}
+ }
+ }
+
+
+
+
+ @let events = getEvents(date);
+
+ {{ formattedDate }}
+
+ @for (event of events; track $index) {
+
+ }
+
+ @if (events.length) {
+
+ {{ pluralize(events.length) }}
+ }
+
+
+
+
+
+
+ {{ selectedDate | date: 'fullDate' }}
+ @for (event of getEvents(selectedDate); track $index) {
+
+
+ {{ event.title }}
+
+ } @empty {
+ No events scheduled.
+ }
+
+
diff --git a/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.scss b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.scss
new file mode 100644
index 000000000..0090d15a3
--- /dev/null
+++ b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.scss
@@ -0,0 +1,104 @@
+.sample-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+ margin: 8px;
+}
+
+.calendar-wrapper {
+ max-width: 400px;
+ min-width: 200px;
+ flex: 1;
+}
+
+.igx-calendar {
+ box-shadow: 0 1px 3px 0 rgba(0,0,0,.26),
+ 0 1px 1px 0 rgba(0,0,0,.12),
+ 0 2px 1px -1px rgba(0,0,0,.08);
+}
+
+.month-badge {
+ align-self: center;
+ margin-inline-start: 8px;
+ padding: 2px 8px;
+ border-radius: 12px;
+ font-size: 0.75rem;
+ line-height: 1.5;
+ color: var(--ig-primary-500-contrast);
+ background: var(--ig-primary-500);
+}
+
+.day {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ line-height: 1;
+
+ &__dots {
+ display: flex;
+ gap: 2px;
+ height: 4px;
+ margin-top: 2px;
+ }
+
+ &__dot {
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: currentColor;
+
+ &--meeting {
+ color: var(--ig-primary-500);
+ }
+
+ &--deadline {
+ color: var(--ig-error-500);
+ }
+
+ &--personal {
+ color: var(--ig-success-500);
+ }
+ }
+
+ // Fall back to the cell foreground so the dots stay readable on the selected background.
+ &--selected &__dot {
+ color: inherit;
+ }
+}
+
+.agenda {
+ min-width: 200px;
+ padding: 16px;
+
+ &__title {
+ margin: 0 0 12px;
+ }
+
+ &__item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ &__marker {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: currentColor;
+ }
+
+ &__empty {
+ margin: 0;
+ color: var(--ig-gray-600);
+ }
+}
+
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ white-space: nowrap;
+}
diff --git a/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.ts b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.ts
new file mode 100644
index 000000000..beeb21c4c
--- /dev/null
+++ b/src/app/scheduling/calendar/calendar-templates/calendar-templates.component.ts
@@ -0,0 +1,86 @@
+import { Component } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import {
+ IGX_CALENDAR_DIRECTIVES,
+ IgxCalendarComponent,
+ IgxCalendarView
+} from 'igniteui-angular/calendar';
+
+interface CalendarEvent {
+ title: string;
+ type: 'meeting' | 'deadline' | 'personal';
+}
+
+const today = new Date();
+
+// Events are placed relative to the current month so the sample always has data to show.
+const eventsByDay: Record = {
+ 3: [{ title: 'Sprint planning', type: 'meeting' }],
+ 7: [{ title: 'Design review', type: 'meeting' }, { title: 'Dentist', type: 'personal' }],
+ 12: [{ title: 'Release candidate', type: 'deadline' }],
+ 15: [
+ { title: 'Team sync', type: 'meeting' },
+ { title: 'Budget report', type: 'deadline' },
+ { title: 'Gym', type: 'personal' }
+ ],
+ 21: [{ title: 'Customer demo', type: 'meeting' }],
+ 26: [{ title: 'Code freeze', type: 'deadline' }, { title: 'Retrospective', type: 'meeting' }]
+};
+
+@Component({
+ selector: 'app-calendar-templates',
+ styleUrls: ['./calendar-templates.component.scss'],
+ templateUrl: './calendar-templates.component.html',
+ imports: [
+ IgxCalendarComponent,
+ IGX_CALENDAR_DIRECTIVES,
+ DatePipe
+ ]
+})
+export class CalendarTemplatesComponent {
+ public selectedDate: Date = new Date(today.getFullYear(), today.getMonth(), 15);
+
+ public getEvents(date: Date): CalendarEvent[] {
+ if (!this.isCurrentMonth(date)) {
+ return [];
+ }
+ return eventsByDay[date.getDate()] ?? [];
+ }
+
+ public getMonthEventsCount(date: Date): number {
+ if (!this.isCurrentMonth(date)) {
+ return 0;
+ }
+ return Object.values(eventsByDay).reduce((count, events) => count + events.length, 0);
+ }
+
+ public pluralize(count: number): string {
+ return `${count} ${count === 1 ? 'event' : 'events'}`;
+ }
+
+ public onSelection(date: Date | Date[]) {
+ this.selectedDate = date as Date;
+ }
+
+ public onSubheaderKeydown(event: KeyboardEvent, calendar: IgxCalendarComponent, view: IgxCalendarView) {
+ // Keep the key from reaching the calendar's own keyboard navigation.
+ event.stopPropagation();
+
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ this.switchView(event, calendar, view);
+ }
+ }
+
+ public switchView(event: Event, calendar: IgxCalendarComponent, view: IgxCalendarView) {
+ // The subheader is re-rendered for the new view, which removes the focused button.
+ // Move focus to the calendar body (the grid around the subheader) so keyboard navigation continues in the new view.
+ const grid = (event.target as HTMLElement).closest('[role="grid"]');
+ calendar.activeView = view;
+ grid?.focus();
+ }
+
+ private isCurrentMonth(date: Date): boolean {
+ return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth();
+ }
+}
diff --git a/src/app/scheduling/scheduling-routes-data.ts b/src/app/scheduling/scheduling-routes-data.ts
index 882051a88..a5557f58d 100644
--- a/src/app/scheduling/scheduling-routes-data.ts
+++ b/src/app/scheduling/scheduling-routes-data.ts
@@ -9,6 +9,7 @@ export const schedulingRoutesData = {
'calendar-sample-8': { displayName: 'Calendar Range Mode', parentName: 'Calendar' },
'calendar-sample-9': { displayName: 'Calendar Orientation', parentName: 'Calendar' },
'calendar-rtl': { displayName: 'Calendar RTL', parentName: 'Calendar' },
+ 'calendar-templates': { displayName: 'Calendar Templates', parentName: 'Calendar' },
'calendar-days-view': { displayName: 'Calendar Days View', parentName: 'Calendar' },
'calendar-months-view': { displayName: 'Calendar Months View', parentName: 'Calendar' },
'calendar-years-view': { displayName: 'Calendar Years View', parentName: 'Calendar' },
diff --git a/src/app/scheduling/scheduling.routes.ts b/src/app/scheduling/scheduling.routes.ts
index 593b2512b..661328259 100644
--- a/src/app/scheduling/scheduling.routes.ts
+++ b/src/app/scheduling/scheduling.routes.ts
@@ -11,6 +11,7 @@ import { CalendarSample7Component } from './calendar/calendar-sample-7/calendar-
import { CalendarSample8Component } from './calendar/calendar-sample-8/calendar-sample-8.component';
import { CalendarSample9Component } from './calendar/calendar-sample-9/calendar-sample-9.component';
import { CalendarRtlComponent } from './calendar/calendar-rtl/calendar-rtl.component';
+import { CalendarTemplatesComponent } from './calendar/calendar-templates/calendar-templates.component';
import { CalendarStylingSampleComponent } from './calendar/calendar-styling-sample/calendar-styling-sample.component';
import { CalendarTailwindStylingSampleComponent } from './calendar/calendar-tailwind-styling-sample/calendar-tailwind-styling-sample.component';
import { CalendarYearsViewComponent } from './calendar/calendar-years-view/calendar-years-view.component';
@@ -102,6 +103,11 @@ export const SchedulingRoutes: Routes = [
data: schedulingRoutesData['calendar-rtl'],
path: 'calendar-rtl'
},
+ {
+ component: CalendarTemplatesComponent,
+ data: schedulingRoutesData['calendar-templates'],
+ path: 'calendar-templates'
+ },
{
component: CalendarStylingSampleComponent,
data: schedulingRoutesData['calendar-styling-sample'],