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'],