Run the CLI
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add date-pickerA date picker component that combines a button trigger with a calendar popup for intuitive date selection.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ZardDatePickerComponent } from '../date-picker.component';
@Component({
selector: 'zard-demo-date-picker-default',
imports: [ZardDatePickerComponent],
standalone: true,
template: `
<z-date-picker [value]="selectedDate()" placeholder="Pick a date" (dateChange)="onDateChange($event)" />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoDatePickerDefaultComponent {
readonly selectedDate = signal<Date | null>(null);
onDateChange(date: Date | null) {
this.selectedDate.set(date);
console.log('Selected date:', date);
}
}
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add date-pickerpnpm dlx @ngzard/ui@latest add date-pickeryarn dlx @ngzard/ui@latest add date-pickerbunx @ngzard/ui@latest add date-pickerCreate the component directory structure and add the following files to your project.
import { DatePipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
forwardRef,
inject,
input,
model,
output,
viewChild,
ViewEncapsulation,
type TemplateRef,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms';
import type { ClassValue } from 'clsx';
import { datePickerVariants, type ZardDatePickerVariants } from './date-picker.variants';
import { mergeClasses } from '../../shared/utils/utils';
import { ZardButtonComponent } from '../button/button.component';
import { ZardCalendarComponent } from '../calendar/calendar.component';
import { ZardIconComponent } from '../icon/icon.component';
import { ZardPopoverComponent, ZardPopoverDirective } from '../popover/popover.component';
const HEIGHT_BY_SIZE: Record<NonNullable<ZardDatePickerVariants['zSize']>, string> = {
sm: 'h-8',
default: 'h-10',
lg: 'h-12',
};
@Component({
selector: 'z-date-picker, [z-date-picker]',
imports: [ZardButtonComponent, ZardCalendarComponent, ZardPopoverComponent, ZardPopoverDirective, ZardIconComponent],
standalone: true,
template: `
<button
z-button
type="button"
[zType]="zType()"
[zSize]="zSize()"
[disabled]="disabled()"
[class]="buttonClasses()"
zPopover
#popoverDirective="zPopover"
[zContent]="calendarTemplate"
zTrigger="click"
(zVisibleChange)="onPopoverVisibilityChange($event)"
[attr.aria-expanded]="false"
[attr.aria-haspopup]="true"
aria-label="Choose date"
>
<z-icon zType="calendar" />
<span [class]="textClasses()">
{{ displayText() }}
</span>
</button>
<ng-template #calendarTemplate>
<z-popover [class]="popoverClasses()">
<z-calendar
#calendar
class="border-0"
[value]="value()"
[minDate]="minDate()"
[maxDate]="maxDate()"
[disabled]="disabled()"
(dateChange)="onDateChange($event)"
/>
</z-popover>
</ng-template>
`,
providers: [
DatePipe,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ZardDatePickerComponent),
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {},
exportAs: 'zDatePicker',
})
export class ZardDatePickerComponent implements ControlValueAccessor {
private readonly datePipe = inject(DatePipe);
readonly calendarTemplate = viewChild.required<TemplateRef<unknown>>('calendarTemplate');
readonly popoverDirective = viewChild.required<ZardPopoverDirective>('popoverDirective');
readonly calendar = viewChild.required<ZardCalendarComponent>('calendar');
readonly class = input<ClassValue>('');
readonly zType = input<ZardDatePickerVariants['zType']>('outline');
readonly zSize = input<ZardDatePickerVariants['zSize']>('default');
readonly value = model<Date | null>(null);
readonly placeholder = input<string>('Pick a date');
readonly zFormat = input<string>('MMMM d, yyyy');
readonly minDate = input<Date | null>(null);
readonly maxDate = input<Date | null>(null);
readonly disabled = model<boolean>(false);
readonly dateChange = output<Date | null>();
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onChange: (value: Date | null) => void = () => {};
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onTouched: () => void = () => {};
protected readonly classes = computed(() =>
mergeClasses(
datePickerVariants({
zSize: this.zSize(),
}),
this.class(),
),
);
protected readonly buttonClasses = computed(() => {
const hasValue = !!this.value();
const size: NonNullable<ZardDatePickerVariants['zSize']> = this.zSize() ?? 'default';
const height = HEIGHT_BY_SIZE[size];
return mergeClasses(
'justify-start text-left font-normal',
!hasValue && 'text-muted-foreground',
height,
'min-w-[240px]',
);
});
protected readonly textClasses = computed(() => {
const hasValue = !!this.value();
return mergeClasses(!hasValue && 'text-muted-foreground');
});
protected readonly popoverClasses = computed(() => mergeClasses('w-auto p-0'));
protected readonly displayText = computed(() => {
const date = this.value();
if (!date) {
return this.placeholder();
}
return this.formatDate(date, this.zFormat());
});
protected onDateChange(date: Date | Date[]): void {
// Date picker always uses single mode, so we can safely cast
const singleDate = Array.isArray(date) ? (date[0] ?? null) : date;
this.value.set(singleDate);
this.onChange(singleDate);
this.onTouched();
this.dateChange.emit(singleDate);
this.popoverDirective().hide();
}
protected onPopoverVisibilityChange(visible: boolean): void {
if (visible) {
setTimeout(() => {
if (this.calendar()) {
this.calendar().resetNavigation();
}
});
}
}
private formatDate(date: Date, format: string): string {
return this.datePipe.transform(date, format) ?? '';
}
writeValue(value: Date | null): void {
this.value.set(value);
}
registerOnChange(fn: (value: Date | null) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
}
}
import { cva, type VariantProps } from 'class-variance-authority';
const datePickerVariants = cva('', {
variants: {
zSize: {
sm: '',
default: '',
lg: '',
},
zType: {
default: '',
outline: '',
ghost: '',
},
},
defaultVariants: {
zSize: 'default',
zType: 'outline',
},
});
export { datePickerVariants };
export type ZardDatePickerVariants = VariantProps<typeof datePickerVariants>;
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ZardDatePickerComponent } from '../date-picker.component';
@Component({
selector: 'zard-demo-date-picker-default',
imports: [ZardDatePickerComponent],
standalone: true,
template: `
<z-date-picker [value]="selectedDate()" placeholder="Pick a date" (dateChange)="onDateChange($event)" />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoDatePickerDefaultComponent {
readonly selectedDate = signal<Date | null>(null);
onDateChange(date: Date | null) {
this.selectedDate.set(date);
console.log('Selected date:', date);
}
}
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ZardDatePickerComponent } from '../date-picker.component';
@Component({
selector: 'zard-demo-date-picker-sizes',
imports: [ZardDatePickerComponent],
standalone: true,
template: `
<div class="flex flex-col gap-4">
<div class="space-y-2">
<h4 class="text-sm font-medium">Small</h4>
<z-date-picker
zSize="sm"
[value]="selectedDateSm()"
placeholder="Pick a date"
(dateChange)="onDateChangeSm($event)"
/>
</div>
<div class="space-y-2">
<h4 class="text-sm font-medium">Default</h4>
<z-date-picker
zSize="default"
[value]="selectedDateDefault()"
placeholder="Pick a date"
(dateChange)="onDateChangeDefault($event)"
/>
</div>
<div class="space-y-2">
<h4 class="text-sm font-medium">Large</h4>
<z-date-picker
zSize="lg"
[value]="selectedDateLg()"
placeholder="Pick a date"
(dateChange)="onDateChangeLg($event)"
/>
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoDatePickerSizesComponent {
readonly selectedDateSm = signal<Date | null>(null);
readonly selectedDateDefault = signal<Date | null>(null);
readonly selectedDateLg = signal<Date | null>(null);
onDateChangeSm(date: Date | null) {
this.selectedDateSm.set(date);
console.log('Selected date (sm):', date);
}
onDateChangeDefault(date: Date | null) {
this.selectedDateDefault.set(date);
console.log('Selected date (default):', date);
}
onDateChangeLg(date: Date | null) {
this.selectedDateLg.set(date);
console.log('Selected date (lg):', date);
}
}
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ZardDatePickerComponent } from '../date-picker.component';
@Component({
selector: 'z-date-picker-formats-demo',
imports: [ZardDatePickerComponent],
standalone: true,
template: `
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">Default Format (MMMM d, yyyy)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" />
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">US Format (MM/dd/yyyy)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" zFormat="MM/dd/yyyy" />
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">European Format (dd-MM-yyyy)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" zFormat="dd-MM-yyyy" />
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">Short Format (MMM d, yy)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" zFormat="MMM d, yy" />
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">With Day Name (EEE, MMMM d)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" zFormat="EEE, MMMM d" />
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">ISO Format (yyyy-MM-dd)</label>
<z-date-picker [value]="selectedDate" (dateChange)="selectedDate = $event" zFormat="yyyy-MM-dd" />
</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDatePickerFormatsComponent {
selectedDate: Date | null = new Date();
}
export default ZardDatePickerFormatsComponent;