Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions live-editing/configs/CalendarConfigGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<div class="sample-wrapper">
<article class="calendar-wrapper">
<igx-calendar #calendar [value]="selectedDate" (selected)="onSelection($event)">
<!-- Header title: the number of events for the selected date -->
<ng-template igxCalendarHeaderTitle let-parts>
@let count = getEvents(parts.date).length;
<span>{{ count ? pluralize(count) : 'No events' }}</span>
</ng-template>

<!-- Header: the selected date, composed from the formatted date parts -->
<ng-template igxCalendarHeader let-parts>
<span>{{ parts.weekday.value }}, {{ parts.month.value }} {{ parts.day.value }}</span>
</ng-template>

<!-- Subheader: month and year buttons that switch views, plus the month's event count -->
<ng-template igxCalendarSubheader let-parts>
@if (calendar.activeView === 'month') {
<span
class="igx-calendar-picker__date"
tabindex="0"
role="button"
[attr.aria-label]="(parts.date | date: 'LLLL') + ', select month'"
(click)="switchView($event, calendar, 'year')"
(keydown)="onSubheaderKeydown($event, calendar, 'year')">
{{ parts.month.value }}
</span>
}
<span
class="igx-calendar-picker__date"
tabindex="0"
role="button"
[attr.aria-label]="(parts.date | date: 'yyyy') + ', select year'"
(click)="switchView($event, calendar, 'decade')"
(keydown)="onSubheaderKeydown($event, calendar, 'decade')">
{{ parts.year.value }}
</span>
@if (calendar.activeView === 'month') {
@let monthCount = getMonthEventsCount(parts.date);
@if (monthCount) {
<span class="month-badge">{{ pluralize(monthCount) }}</span>
}
}
</ng-template>

<!-- Day: the date with a dot for each event -->
<ng-template igxCalendarDay let-date let-formattedDate="formattedDate" let-selected="selected">
@let events = getEvents(date);
<span class="day" [class.day--selected]="selected">
<span>{{ formattedDate }}</span>
<span class="day__dots">
@for (event of events; track $index) {
<span class="day__dot day__dot--{{ event.type }}"></span>
}
</span>
@if (events.length) {
<!-- Readable text for screen readers, announced as the cell description -->
<span class="visually-hidden">{{ pluralize(events.length) }}</span>
}
</span>
</ng-template>
</igx-calendar>
</article>

<section class="agenda">
<h5 class="agenda__title">{{ selectedDate | date: 'fullDate' }}</h5>
@for (event of getEvents(selectedDate); track $index) {
<div class="agenda__item">
<span class="agenda__marker day__dot--{{ event.type }}"></span>
<span>{{ event.title }}</span>
</div>
} @empty {
<p class="agenda__empty">No events scheduled.</p>
}
</section>
</div>
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<number, CalendarEvent[]> = {
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<HTMLElement>('[role="grid"]');
calendar.activeView = view;
grid?.focus();
}

private isCurrentMonth(date: Date): boolean {
return date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth();
}
}
1 change: 1 addition & 0 deletions src/app/scheduling/scheduling-routes-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
6 changes: 6 additions & 0 deletions src/app/scheduling/scheduling.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'],
Expand Down
Loading