Date Picker
A Popover + Calendar composition for picking a single date or a date range.
Installation
npx kosmesis add date-pickerpnpm dlx kosmesis add date-pickeryarn dlx kosmesis add date-pickerbunx kosmesis add date-pickerInstall the following dependencies:
npm install @morphos/overlays @morphos/iconspnpm add @morphos/overlays @morphos/iconsyarn add @morphos/overlays @morphos/iconsbun add @morphos/overlays @morphos/iconsThis component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): button, calendar, popover.
Copy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Icon } from "@morphos/icons";import { type Popover, PopoverTrigger } from "@morphos/overlays";import { buttonVariants } from "./button";import { Calendar, type CalendarState } from "./calendar";import { PopoverContent } from "./popover";import { cn } from "@/lib/utils";export interface DatePickerProps { popover: Popover; calendar: CalendarState; placeholder?: string; class?: string;}// `PopoverTrigger` always renders its own `<button>` (no `asChild` merge) — style it directly// instead of nesting a `Button` inside it, which would produce invalid nested `<button>` elements.@Component()export class DatePicker extends StatelessComponent<DatePickerProps> { render() { const { popover, calendar, placeholder = "Pick a date", class: cls } = this.props; return ( <> <PopoverTrigger popover={popover} class={cn(buttonVariants({ variant: "outline" }), "justify-start font-normal", cls)}> <Icon name="Calendar" size={14} /> {() => calendar.formattedDate ?? <span class="text-muted-foreground">{placeholder}</span>} </PopoverTrigger> <PopoverContent popover={popover} class="w-auto p-0"> <Calendar state={calendar} class="border-none shadow-none" /> </PopoverContent> </> ); }}export interface DateRangePickerProps { popover: Popover; /** Must be created with `mode: "range"`. */ calendar: CalendarState; placeholder?: string; class?: string;}@Component()export class DateRangePicker extends StatelessComponent<DateRangePickerProps> { render() { const { popover, calendar, placeholder = "Pick a date range", class: cls } = this.props; return ( <> <PopoverTrigger popover={popover} class={cn(buttonVariants({ variant: "outline" }), "justify-start font-normal", cls)}> <Icon name="Calendar" size={14} /> {() => calendar.formattedRange ?? <span class="text-muted-foreground">{placeholder}</span>} </PopoverTrigger> <PopoverContent popover={popover} class="w-auto p-0"> <Calendar state={calendar} class="border-none shadow-none" /> </PopoverContent> </> ); }}Install the following dependencies:
npm install @morphos/overlays @morphos/icons @praxisjs/csspnpm add @morphos/overlays @morphos/icons @praxisjs/cssyarn add @morphos/overlays @morphos/icons @praxisjs/cssbun add @morphos/overlays @morphos/icons @praxisjs/cssThis component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): button, calendar, popover.
Copy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Icon } from "@morphos/icons";import { type Popover, PopoverTrigger } from "@morphos/overlays";import { ButtonStyles } from "./button";import { Calendar, type CalendarState } from "./calendar";import { PopoverContent } from "./popover";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class DatePickerStyles extends Stylesheet { $trigger = this.css({ justifyContent: "flex-start", fontWeight: 400 }); $placeholder = this.css({ color: t.mutedForeground }); $content = this.css({ width: "auto", padding: "0" }); $flush = this.css({ border: "none !important" });}export interface DatePickerProps { popover: Popover; calendar: CalendarState; placeholder?: string; class?: string;}// `PopoverTrigger` always renders its own `<button>` (no `asChild` merge) — style it directly// instead of nesting a `Button` inside it, which would produce invalid nested `<button>` elements.@Component()export class DatePicker extends StatelessComponent<DatePickerProps> { @Styled(ButtonStyles) $btn!: ButtonStyles; @Styled(DatePickerStyles) $s!: DatePickerStyles; render() { const { popover, calendar, placeholder = "Pick a date", class: cls } = this.props; return ( <> <PopoverTrigger popover={popover} class={cx(this.$btn.$root, this.$btn.$variantOutline, this.$btn.$sizeDefault, this.$s.$trigger, cls)} > <Icon name="Calendar" size={14} /> {() => calendar.formattedDate ?? <span class={this.$s.$placeholder}>{placeholder}</span>} </PopoverTrigger> <PopoverContent popover={popover} class={this.$s.$content}> <Calendar state={calendar} class={this.$s.$flush} /> </PopoverContent> </> ); }}export interface DateRangePickerProps { popover: Popover; /** Must be created with `mode: "range"`. */ calendar: CalendarState; placeholder?: string; class?: string;}@Component()export class DateRangePicker extends StatelessComponent<DateRangePickerProps> { @Styled(ButtonStyles) $btn!: ButtonStyles; @Styled(DatePickerStyles) $s!: DatePickerStyles; render() { const { popover, calendar, placeholder = "Pick a date range", class: cls } = this.props; return ( <> <PopoverTrigger popover={popover} class={cx(this.$btn.$root, this.$btn.$variantOutline, this.$btn.$sizeDefault, this.$s.$trigger, cls)} > <Icon name="Calendar" size={14} /> {() => calendar.formattedRange ?? <span class={this.$s.$placeholder}>{placeholder}</span>} </PopoverTrigger> <PopoverContent popover={popover} class={this.$s.$content}> <Calendar state={calendar} class={this.$s.$flush} /> </PopoverContent> </> ); }}Examples
Usage
import { Popover } from "@morphos/overlays";
import { CalendarState } from "@/components/ui/calendar";
import { DatePicker } from "@/components/ui/date-picker";
@Component()
class BookingForm extends StatefulComponent {
@State() popover = new Popover();
@State() calendar = new CalendarState({
onSelect: () => { this.popover.closePopover(); },
});
render() {
return <DatePicker popover={this.popover} calendar={this.calendar} />;
}
}This is a composition, not a new primitive — Popover (Morphos) +
Calendar (built from scratch). The trigger shows the
chosen date in place of the placeholder. If you need a different trigger style, compose those two
components directly instead of using DatePicker.
For a range, create the calendar with mode: "range" and use DateRangePicker — same props, and the
trigger shows from – to. To pick a time as well, see
Date Time Picker.
Props
| Prop | Type | Default |
|---|---|---|
popover | Popover | — required, a Morphos Popover instance |
calendar | CalendarState | — required |
placeholder | string | "Pick a date" |
class | string | — |