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 CalendarComponent renders injected components via NgComponentOutlet (which passes inputs by canonical name), CalendarPresetComponent overrides cssClass as input<string>('') without the class alias. Bind it as [cssClass] when using the <smart-calendar-preset> selector directly, or just pass class on <smart-calendar> (the wrapper forwards it). Note also that when used through the wrapper the value two-way binding is re-applied as a one-way input on every change-detection (and referenceDate is not forwarded at all, since its canonical input name is referenceDateInput), 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

InputTypeDefaultDescription
optionsInputSignal<ICalendarOptions | undefined>-Configuration (view, weekStart, slot templates, …)
valueModelSignal<Date | null>nullSelected day (two-way binding)
referenceDateInputSignal<Date | undefined>-Initial reference date (current month focus). Defaults to today inside the wrapper if undefined
eventsInputSignal<ICalendarEvent[]>[]Events for eventsForDay(); the standard and preset mark days that have events
classInputSignal<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: NgComponentOutlet does not propagate output bindings. When a custom component is injected via the token, the value two-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.