Calendar
The <smart-calendar> component renders a date calendar with shared month-grid logic, navigation (prev/next/today), single-day selection, and per-day event awareness. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract CalendarBaseComponent defines the shared API and contains the date logic (pure month-grid construction, period navigation, day selection, event filtering). CalendarStandardComponent is a barebones placeholder concrete implementation that renders a 6×7 month grid. CalendarComponent is the public wrapper that renders CalendarStandardComponent by default and accepts a custom replacement via CALENDAR_STANDARD_COMPONENT_TOKEN.
Usage
<smart-calendar
[options]="options"
[referenceDate]="referenceDate"
[events]="events"
[(value)]="selectedDate"
/>
Components
CalendarComponent (<smart-calendar>)
Main wrapper component. Renders CalendarStandardComponent by default. When CALENDAR_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. The wrapper exposes a two-way value model (Date | null), an optional referenceDate input (defaults to today), and an events input.
CalendarStandardComponent (<smart-calendar-standard>)
Barebones placeholder concrete implementation. Renders a wrapper <div> containing an optional toolbar (<button.prev>, <button.today-btn>, <button.next>, optional toolbarActionsTpl slot) and a <div class="view-grid"> with one <div class="week"> per week and one <button class="day"> per day. Each day exposes data-current-month, data-today, and data-selected attributes plus an aria-label of the date string. The standard marks event days: a day with eventsForDay(date).length > 0 gets data-events="<count>", its aria-label gains the count (e.g. "Thu Sep 03 2026, 1 event", ", 2 events"; built by the base's dayAriaLabel(date), which the preset uses too), and — without dayCellTpl — a <span class="smart-calendar-event" data-role="event-dot" aria-hidden="true"> marker after the day number. If dayCellTpl is provided in options, it replaces the default day content (day number and marker). The external cssClass is applied to the root wrapper. It does not include any visual styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.
CalendarPresetComponent (<smart-calendar-preset>)
Styled single date-picker variation that extends CalendarBaseComponent and is a drop-in replacement for CalendarStandardComponent. Register it via CALENDAR_STANDARD_COMPONENT_TOKEN to restyle every <smart-calendar>, or use the <smart-calendar-preset> selector directly. It reproduces Preline's single date-picker visual (a w-80 rounded popover card: month/year navigation header with chevron prev/next buttons, a weekday header row, and a 6×7 grid of circular day buttons) translated to smart:-prefixed vanilla Tailwind with explicit dark: variants. Month navigation (prevPeriod/nextPeriod) and day selection (selectDay) are driven entirely by Angular signals — the Preline datepicker JS plugin is not required. It honors options.weekStart (rotates the weekday header; default 1 → Monday) and options.showToolbar (toggles the navigation header). Selected, today, default, and out-of-month day states each get distinct styling; out-of-month days are rendered disabled. Days that have matching events show a small dot marker, and options.dayCellTpl overrides the default day content. The class recipes live in preset/preset-classes.util.ts (getCalendarPresetDayClasses plus the CALENDAR_PRESET_* layout constants).
Because
CalendarComponentrenders injected components viaNgComponentOutlet(which passes inputs by canonical name),CalendarPresetComponentoverridescssClassasinput<string>('')without theclassalias. Bind it as[cssClass]when using the<smart-calendar-preset>selector directly, or just passclasson<smart-calendar>(the wrapper forwards it). Note also that when used through the wrapper thevaluetwo-way binding is re-applied as a one-way input on every change-detection (andreferenceDateis not forwarded at all, since its canonical input name isreferenceDateInput), so for interactive selection / external reference-date control use the<smart-calendar-preset>selector directly with[(value)]and[referenceDate].
CalendarBaseComponent (abstract)
Abstract base directive containing the shared calendar logic. Exposes inputs and signals for state, plus methods for navigation and selection.
Inputs: options (ICalendarOptions | undefined), value (Date | null, two-way model), referenceDate (Date | undefined), events (ICalendarEvent[]), class (string)
Computed signals: view (current view from options, default 'month'), weekStart (0 | 1, default 1), showToolbar (default true), reference (effective reference date — internal writable, seeded from referenceDate input), monthGrid (current 6×7 grid).
Methods: selectDay(date), goToToday(), prevPeriod(), nextPeriod(), eventsForDay(day), dayAriaLabel(day) (date string plus ", N event(s)" when the day has events).
Static method: buildMonthGrid(reference, weekStart, selected): ICalendarDayCell[][] — pure function returning a 6×7 grid suitable for rendering.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<ICalendarOptions | undefined> | - | Configuration (view, weekStart, slot templates, …) |
value | ModelSignal<Date | null> | null | Selected day (two-way binding) |
referenceDate | InputSignal<Date | undefined> | - | Initial reference date (current month focus). Defaults to today inside the wrapper if undefined |
events | InputSignal<ICalendarEvent[]> | [] | Events for eventsForDay(); the standard and preset mark days that have events |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
ICalendarDayCell
The default CalendarStandardComponent renders the month view; monthsCount, view: 'week' | 'day' | 'year', eventListTpl, sidePanelTpl, and eventTpl are hints / slots intended for custom implementations registered via the token. weekStart defaults to 1 (Monday).
CALENDAR_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default CalendarStandardComponent with a custom implementation. Provide a Type<CalendarBaseComponent> to override.
Note:
NgComponentOutletdoes not propagate output bindings. When a custom component is injected via the token, thevaluetwo-way binding becomes one-way (input only). Custom implementations should expose their selection events through dedicated outputs (e.g.,valueChange) and the host application should wire those manually.
Extending the base class
import { DatePipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import {
CalendarBaseComponent,
CalendarComponent,
CALENDAR_STANDARD_COMPONENT_TOKEN,
ICalendarEvent,
ICalendarOptions,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-calendar',
imports: [DatePipe],
template: `
<div [class]="containerClasses()">
@if (showToolbar()) {
<div class="docs-calendar__toolbar">
<button
type="button"
class="docs-calendar__prev"
(click)="prevPeriod()"
>
Previous
</button>
<h2 class="docs-calendar__title">
{{ reference() | date: 'LLLL yyyy' }}
</h2>
<button
type="button"
class="docs-calendar__today"
(click)="goToToday()"
>
Today
</button>
<button
type="button"
class="docs-calendar__next"
(click)="nextPeriod()"
>
Next
</button>
</div>
}
<div class="docs-calendar__grid">
@for (week of monthGrid(); track $index) {
<div class="docs-calendar__week">
@for (cell of week; track cell.date.getTime()) {
<button
type="button"
class="docs-calendar__day"
[class.docs-calendar__day--outside]="!cell.isCurrentMonth"
[attr.data-today]="cell.isToday ? 'true' : null"
[attr.data-selected]="cell.isSelected ? 'true' : null"
(click)="selectDay(cell.date)"
>
{{ cell.date.getDate() }}
@for (event of eventsForDay(cell.date); track event.id) {
<span class="docs-calendar__event">{{ event.title }}</span>
}
</button>
}
</div>
}
</div>
</div>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomCalendarComponent extends CalendarBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() =>
['docs-calendar', this.cssClass()].filter(Boolean).join(' '),
);
}
@Component({
selector: 'docs-calendar-custom-example',
imports: [CalendarComponent],
providers: [
{
provide: CALENDAR_STANDARD_COMPONENT_TOKEN,
useValue: CustomCalendarComponent,
},
],
template: `
<smart-calendar
[referenceDate]="referenceDate"
[events]="events"
[options]="options"
/>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CalendarCustomExampleComponent {
referenceDate = new Date(2026, 0, 1);
options: ICalendarOptions = { view: 'month', weekStart: 1 };
events: ICalendarEvent[] = [
{
id: 'review',
title: 'Design review',
start: new Date(2026, 0, 15, 10, 0),
end: new Date(2026, 0, 15, 11, 0),
},
];
}
Source
The component lives in packages/shared/angular/src/lib/components/calendar and is documented for Claude Code by the angular-components-calendar skill.