Calendar
Added 14 October 2026
React
Codecomponents/react/calendar-demo.tsx
import {useState} from 'react';
import type {DateRange} from 'react-day-picker';
import {Calendar} from '@stroeer-labs/design-library/react';
export function CalendarDemo() {
const [range, setRange] = useState<DateRange | undefined>();
const [single, setSingle] = useState<Date | undefined>();
return (
<div className="space-y-6">
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">Range (2 months)</p>
<Calendar
mode="range"
numberOfMonths={2}
selected={range}
onSelect={setRange}
weekStartsOn={1}
showOutsideDays={false}
className="rounded-md border border-border"
/>
</div>
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">Single date</p>
<Calendar mode="single" selected={single} onSelect={setSingle} weekStartsOn={1} className="rounded-md border border-border" />
</div>
</div>
);
}Angular
Codecomponents/angular/calendar-demo.ts
import {Component, computed, signal} from '@angular/core';
import {NgIcon, provideIcons} from '@ng-icons/core';
import {lucideChevronLeft, lucideChevronRight} from '@ng-icons/lucide';
import {HlmButton, HlmCalendarImports, buildCalendarI18n, provideBrnCalendarI18n} from '@stroeer-labs/design-library/angular';
// spartan has no native multi-month range calendar, so the "Range (2 months)" demo
// composes two hlm-calendar-range instances sharing one start/end with linked navigation
// (the same approach the DateRangePicker uses internally).
@Component({
selector: 'dl-calendar-demo',
standalone: true,
imports: [HlmCalendarImports, HlmButton, NgIcon],
viewProviders: [provideIcons({lucideChevronLeft, lucideChevronRight})],
// English calendar locale (matches React), independent of the OS/browser locale.
providers: [provideBrnCalendarI18n(buildCalendarI18n('en'))],
template: `
<div class="space-y-6">
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">Range (2 months)</p>
<div class="relative w-fit rounded-md border border-border">
<div
class="pointer-events-none absolute inset-x-0 top-0 z-10 flex items-center justify-between p-3"
>
<button
hlmBtn
variant="ghost"
class="pointer-events-auto size-8 p-0"
(click)="shift(-1)"
>
<ng-icon name="lucideChevronLeft" />
</button>
<button
hlmBtn
variant="ghost"
class="pointer-events-auto size-8 p-0"
(click)="shift(1)"
>
<ng-icon name="lucideChevronRight" />
</button>
</div>
<div class="flex">
@for (month of months(); track key(month)) {
<hlm-calendar-range
class="p-3 [&_.order-first]:invisible [&_.order-last]:invisible [&_[data-outside-month]]:invisible"
[defaultFocusedDate]="month"
[startDate]="start()"
[endDate]="end()"
[weekStartsOn]="1"
(startDateChange)="onStart($event)"
(endDateChange)="onEnd($event)"
/>
}
</div>
</div>
</div>
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">Single date</p>
<hlm-calendar
class="w-fit rounded-md border border-border"
[date]="single()"
(dateChange)="single.set($event)"
[weekStartsOn]="1"
/>
</div>
</div>
`,
})
export class CalendarDemoComponent {
protected readonly single = signal<Date | undefined>(undefined);
protected readonly start = signal<Date | undefined>(undefined);
protected readonly end = signal<Date | undefined>(undefined);
protected readonly anchorMonth = signal(new Date(new Date().getFullYear(), new Date().getMonth(), 1));
protected readonly months = computed(() => [this.anchorMonth(), this.addMonths(this.anchorMonth(), 1)]);
protected shift(delta: number): void {
this.anchorMonth.update((m) => this.addMonths(m, delta));
}
protected onStart(date: Date | undefined): void {
this.start.set(date);
this.end.set(undefined);
}
protected onEnd(date: Date | undefined): void {
if (date) {
this.end.set(date);
}
}
protected key(date: Date): number {
return date.getFullYear() * 12 + date.getMonth();
}
private addMonths(date: Date, count: number): Date {
return new Date(date.getFullYear(), date.getMonth() + count, 1);
}
}