Kalendar

kalendar — views#

com.himanshoe.kalendar

The nine calendar composables, the config they share, and the four *Defaults objects every content slot falls back to.

kotlin
import com.himanshoe.kalendar.KalendarMonth

Each composable has a full narrative page under Views; this page is the signature-level reference.

The composables#

ComposableShapePaging unitState
KalendarWeekOne week rowWeekKalendarViewState
KalendarMonthMonth gridMonthKalendarViewState
KalendarYear12 month grids in a lazy columnYearKalendarViewState
KalendarTimelineContinuous month listnone — free scrollKalendarTimelineState
KalendarScheduleHourly grid, one dayDayKalendarViewState
KalendarScheduleWeekHourly grid, 7 day columnsWeekKalendarViewState
KalendarResourceViewHourly grid, one lane per resourceDayKalendarViewState
KalendarAgendaEvent list grouped by datenone — free scrollLazyListState
KalendarDatePickerCompact single-month pickerMonthKalendarViewState

Every one of them is restartable skippable — see Performance.

The event type parameter#

Every view that takes events is generic in E, the caller's own event type, and infers it from events. Callbacks and slots hand E straight back, so nothing has to be cast:

kotlin
class Booking(
    override val date: LocalDate,
    override val eventName: String,
    override val eventDescription: String? = null,
    val roomId: String,
) : KalendarEvent

KalendarMonth(
    selectedDate = today,
    events = bookings,
    onDateClick = { date, dayBookings -> show(dayBookings.map { it.roomId }) },
)

With no events there is nothing to infer from, so each view whose events is optional also has a non-generic overload that has no events parameter at all — KalendarMonth(selectedDate = today) resolves to that one. A default value could not have done this job: a Kotlin default argument is never a source of type inference, so a single generic function simply would not compile at that call site. Omitting events is also what keeps the two unambiguous — pass events and only the generic one is a candidate, pass none and only the event-free one is. Each event-free overload drops the parameters an event-free calendar could never reach (onDayEventClick, onEventDrop, onEventClick, onEventTimeChange, resourceIdOf, eventContent, allDayEventContent).

KalendarAgenda has no such overload: its events is required, so the type is always there to infer from. KalendarDatePicker is not generic at all — a picker draws no events.

KalendarMonth#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarMonth(
    selectedDate: LocalDate,
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarMonthState(selectedDate),
    events: List<E> = emptyList(),
    selectedDates: Set<LocalDate> = setOf(selectedDate),
    onDateClick: (date: LocalDate, events: List<E>) -> Unit = { _, _ -> },
    onDayEventClick: ((event: E) -> Unit)? = null,
    onDateRangeSelect: ((start: LocalDate, current: LocalDate) -> Unit)? = null,
    onDateRangeSelectEnd: (() -> Unit)? = null,
    onEventDrop: ((events: List<E>, newDate: LocalDate) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    header: @Composable (KalendarHeaderScope) -> Unit = { … },
    dayOfWeekLabel: @Composable (DayOfWeek) -> Unit = { … },
    dayContent: (@Composable (scope: KalendarDayScope<E>) -> Unit)? = null,
)

The full month grid, paged by month. The most capable date-grid view: the only one with drag-to-reschedule.

ParameterTypeDefaultDescription
selectedDateLocalDateDefault single-selection highlight, and the initially visible month when state is left at its default.
modifierModifierModifierApplied to the outermost container.
stateKalendarViewStaterememberKalendarMonthState(selectedDate)Navigation state.
eventsList<E>emptyList()Rendered as indicator dots on day cells, and as span bars for multi-day events.
selectedDatesSet<LocalDate>setOf(selectedDate)The highlighted dates. Hoist your own for multi-select or range.
onDateClick(LocalDate, List<E>) -> Unitno-opCalled when a non-disabled date is tapped, with that date's events.
onDayEventClick((E) -> Unit)?nullCalled when an individual event is tapped inside the cell's +N overflow popup.
onDateRangeSelect((LocalDate, LocalDate) -> Unit)?nullWhen non-null, press-and-hold then drag selects a range live.
onDateRangeSelectEnd(() -> Unit)?nullCalled once when such a drag ends.
onEventDrop((List<E>, LocalDate) -> Unit)?nullWhen non-null, press-and-hold a date that has events and drag to another date to reschedule.
configKalendarViewConfigKalendarViewConfig()Shared settings.
header@Composable (KalendarHeaderScope) -> UnitKalendarHeaderDefaults.HeaderReplaces the navigation header.
dayOfWeekLabel@Composable (DayOfWeek) -> UnitKalendarHeaderDefaults.DayOfWeekLabelReplaces one weekday column header's content.
dayContent(@Composable (KalendarDayScope<E>) -> Unit)?nullReplaces the built-in day cell entirely. Last, so trailing-lambda syntax lands on it.
kotlin
KalendarMonth(
    selectedDate = today,
    events = events,
    selectedDates = selected,
    onDateClick = { date, _ -> selected = setOf(date) },
)

KalendarWeek#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarWeek(
    selectedDate: LocalDate,
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarWeekState(selectedDate),
    events: List<E> = emptyList(),
    selectedDates: Set<LocalDate> = setOf(selectedDate),
    onDateClick: (date: LocalDate, events: List<E>) -> Unit = { _, _ -> },
    onDayEventClick: ((event: E) -> Unit)? = null,
    onDateRangeSelect: ((start: LocalDate, current: LocalDate) -> Unit)? = null,
    onDateRangeSelectEnd: (() -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    header: @Composable (KalendarHeaderScope) -> Unit = { … },
    dayOfWeekLabel: @Composable (DayOfWeek) -> Unit = { … },
    dayContent: (@Composable (scope: KalendarDayScope<E>) -> Unit)? = null,
)

A single-week row, paged by week. Every parameter means what it does on KalendarMonth; it has no onEventDrop, because one row is not a drag surface.

kotlin
KalendarWeek(selectedDate = today, state = rememberKalendarWeekState(today))

KalendarYear#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarYear(
    selectedDate: LocalDate,
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarYearState(selectedDate),
    events: List<E> = emptyList(),
    selectedDates: Set<LocalDate> = setOf(selectedDate),
    onDateClick: (date: LocalDate, events: List<E>) -> Unit = { _, _ -> },
    onDayEventClick: ((event: E) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    header: @Composable (KalendarHeaderScope) -> Unit = { … },
    dayOfWeekLabel: @Composable (DayOfWeek) -> Unit = { … },
    dayContent: (@Composable (scope: KalendarDayScope<E>) -> Unit)? = null,
)

Twelve month grids in one lazily composed scrolling column, paged by year. No drag gestures and no sliding selection indicator: twelve grids are on screen at once, so there is no single indicator to slide and no unambiguous drag surface.

kotlin
KalendarYear(selectedDate = today, onDateClick = { date, _ -> jumpTo(date) })

KalendarTimeline#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarTimeline(
    selectedDate: LocalDate,
    modifier: Modifier = Modifier,
    state: KalendarTimelineState = rememberKalendarTimelineState(selectedDate),
    events: List<E> = emptyList(),
    selectedDates: Set<LocalDate> = setOf(selectedDate),
    onDateClick: (date: LocalDate, events: List<E>) -> Unit = { _, _ -> },
    onDayEventClick: ((event: E) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    header: @Composable (KalendarHeaderScope) -> Unit = { … },
    dayOfWeekLabel: @Composable (DayOfWeek) -> Unit = { … },
    dayContent: (@Composable (scope: KalendarDayScope<E>) -> Unit)? = null,
)

Every month in one uninterrupted vertical scroll. Note the different state type (KalendarTimelineState) and the different header default (KalendarHeaderDefaults.TimelineHeader) — a sticky overlay, not a page header. KalendarViewConfig.showNavigationArrows and showJumpPicker have no effect here.

kotlin
KalendarTimeline(selectedDate = today, events = events)

KalendarSchedule#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarSchedule(
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarScheduleState(),
    events: List<E> = emptyList(),
    onEventClick: (E) -> Unit = {},
    onEventTimeChange: ((event: E, newStart: LocalDateTime, newEnd: LocalDateTime) -> Unit)? = null,
    onEventCreate: ((start: LocalDateTime, end: LocalDateTime) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    hourHeight: Dp = KalendarTheme.dimensions.hourHeight,
    nowIndicatorTick: Duration = 1.minutes,
    allDayEventContent: @Composable (E) -> Unit = { … },
    hourLabel: @Composable (Int) -> Unit = { … },
    nowIndicator: @Composable () -> Unit = { … },
    eventContent: @Composable (KalendarScheduleEventScope<E>) -> Unit = { … },
)

An hourly time grid for a single day. There is no selectedDate parameter — the Schedule views have no date selection, so the visible day is the state's business alone.

ParameterTypeDefaultDescription
modifierModifierModifierApplied to the outermost container.
stateKalendarViewStaterememberKalendarScheduleState()Pages by single day.
eventsList<E>emptyList()Only those on the visible day are rendered.
onEventClick(E) -> Unitno-opCalled when a block or all-day chip is tapped.
onEventTimeChange((E, LocalDateTime, LocalDateTime) -> Unit)?nullWhen non-null, blocks become draggable and resizable from either edge.
onEventCreate((LocalDateTime, LocalDateTime) -> Unit)?nullWhen non-null, a press-and-drag on empty grid sweeps out a new time range and reports it on release.
configKalendarViewConfigKalendarViewConfig()scheduleVisibleHours, scheduleInitialScrollHour and scheduleDragSnapMinutes all apply here.
hourHeightDpKalendarTheme.dimensions.hourHeightVertical space for one hour; block heights scale with it.
nowIndicatorTickDuration1.minutesHow often the now-indicator re-reads the clock.
allDayEventContent@Composable (E) -> UnitKalendarScheduleDefaults.AllDayChipOne chip in the all-day row.
hourLabel@Composable (Int) -> UnitKalendarScheduleDefaults.HourLabelOne gutter label, given the absolute hour.
nowIndicator@Composable () -> UnitKalendarScheduleDefaults.NowIndicatorThe current-time marker. The grid owns its offset.
eventContent@Composable (KalendarScheduleEventScope<E>) -> UnitKalendarScheduleDefaults.EventBlockOne timed block. Last, so trailing-lambda syntax lands on the primary slot.
kotlin
KalendarSchedule(
    state = rememberKalendarScheduleState(initialDate = today),
    events = events,
    onEventClick = { openDetails(it) },
    onEventCreate = { start, end -> newEvent(start, end) },
)

KalendarScheduleWeek#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarScheduleWeek(
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarWeekState(),
    events: List<E> = emptyList(),
    onEventClick: (E) -> Unit = {},
    onEventTimeChange: ((event: E, newStart: LocalDateTime, newEnd: LocalDateTime) -> Unit)? = null,
    onEventCreate: ((start: LocalDateTime, end: LocalDateTime) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    hourHeight: Dp = KalendarTheme.dimensions.hourHeight,
    nowIndicatorTick: Duration = 1.minutes,
    allDayEventContent: @Composable (E) -> Unit = { … },
    hourLabel: @Composable (Int) -> Unit = { … },
    nowIndicator: @Composable () -> Unit = { … },
    dayHeader: @Composable (date: LocalDate, isToday: Boolean) -> Unit = { … },
    eventContent: @Composable (KalendarScheduleEventScope<E>) -> Unit = { … },
)

KalendarSchedule's week variant: seven day columns sharing one hour gutter, paged by week. Note it takes the week state factory, so it can share a hoisted state with KalendarWeek.

Two differences beyond the shape: a horizontal drag also moves an event across day columns, and it adds a dayHeader slot for one column's header. KalendarViewConfig.visibleDaysOfWeek narrows the column count.

kotlin
KalendarScheduleWeek(state = rememberKalendarWeekState(today), events = events)

KalendarResourceView#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarResourceView(
    resources: List<KalendarResource>,
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarScheduleState(),
    events: List<E> = emptyList(),
    resourceIdOf: (E) -> String? = { it.calendarId },
    onEventClick: (E) -> Unit = {},
    onEventChange: ((
        event: E,
        newStart: LocalDateTime,
        newEnd: LocalDateTime,
        newResourceId: String,
    ) -> Unit)? = null,
    config: KalendarViewConfig = KalendarViewConfig(),
    hourHeight: Dp = KalendarTheme.dimensions.hourHeight,
    minResourceColumnWidth: Dp = KalendarTheme.dimensions.resourceColumnMinWidth,
    nowIndicatorTick: Duration = 1.minutes,
    resourceHeader: @Composable (resource: KalendarResource) -> Unit = { … },
    hourLabel: @Composable (Int) -> Unit = { … },
    nowIndicator: @Composable () -> Unit = { … },
    emptyState: @Composable () -> Unit = { … },
    eventContent: @Composable (KalendarResourceEventScope<E>) -> Unit = { … },
)

One day, one column per resource — a room board, a chair schedule, a vehicle roster.

ParameterTypeDefaultDescription
resourcesList<KalendarResource>The lanes, left to right by KalendarResource.order. Empty renders emptyState.
modifierModifierModifierApplied to the outermost container.
stateKalendarViewStaterememberKalendarScheduleState()Pages by single day. It does not swipe between pages — a horizontal swipe belongs to the lanes; the header's arrows, today button and jump picker move between days.
eventsList<E>emptyList()Only timed events on the visible day, in a known lane, are drawn.
resourceIdOf(E) -> String?KalendarEvent.calendarIdMaps an event to its lane's id, or null for none. An accessor rather than an interface member, because KalendarEvent is shipped and cannot grow one.
onEventClick(E) -> Unitno-opCalled when a block is tapped.
onEventChange4-arg lambda, nullablenullWhen non-null, blocks drag in time and across lanes, and resize from either edge. Reports the event, its new start/end, and the id of the lane it landed in.
configKalendarViewConfigKalendarViewConfig()disabledDates and showAdjacentMonthDates have no effect.
hourHeightDpKalendarTheme.dimensions.hourHeightVertical space for one hour.
minResourceColumnWidthDpKalendarTheme.dimensions.resourceColumnMinWidthNarrowest a lane may become before the lanes stop dividing the width evenly and start scrolling horizontally.
nowIndicatorTickDuration1.minutesOne timer serves every lane.
resourceHeader@Composable (KalendarResource) -> UnitKalendarResourceDefaults.ResourceHeaderOne lane's column header.
hourLabel@Composable (Int) -> UnitKalendarResourceDefaults.HourLabelOne gutter label.
nowIndicator@Composable () -> UnitKalendarResourceDefaults.NowIndicatorThe current-time marker.
emptyState@Composable () -> UnitKalendarResourceDefaults.EmptyStateShown in place of the grid when resources is empty.
eventContent@Composable (KalendarResourceEventScope<E>) -> UnitKalendarResourceDefaults.EventBlockOne block. Last — the primary slot.
kotlin
KalendarResourceView(
    resources = listOf(
        KalendarResource(id = "room-a", title = "Room A"),
        KalendarResource(id = "room-b", title = "Room B"),
    ),
    events = bookings,
    resourceIdOf = { event -> (event as Booking).roomId },
    onEventChange = { event, start, end, resourceId -> move(event, start, end, resourceId) },
)

Note: untimed events are not drawn — a lane grid has no all-day row to pin them to.

KalendarAgenda#

kotlin
@Composable
public fun <E : KalendarEvent> KalendarAgenda(
    events: List<E>,
    modifier: Modifier = Modifier,
    onEventClick: (E) -> Unit = {},
    config: KalendarViewConfig = KalendarViewConfig(),
    state: LazyListState = rememberLazyListState(),
    dateHeader: @Composable (date: LocalDate) -> Unit = { … },
    emptyState: @Composable () -> Unit = { … },
    eventContent: @Composable (event: E) -> Unit = { … },
)

Events grouped by date, with no grid and no pager. Dates with no events are skipped entirely.

ParameterTypeDefaultDescription
eventsList<E>The events to list.
modifierModifierModifierApplied to the outermost container.
onEventClick(E) -> Unit{}Called when a row is tapped.
configKalendarViewConfigKalendarViewConfig()Only the month-name formatter and the background apply here.
stateLazyListStaterememberLazyListState()Scroll state. A list of events has nothing to page and nothing to bound, so there is no KalendarViewState.
dateHeader@Composable (LocalDate) -> UnitKalendarAgendaDefaults.DateHeaderOne date's header.
emptyState@Composable () -> UnitKalendarAgendaDefaults.EmptyStateShown centred when events is empty.
eventContent@Composable (E) -> UnitKalendarAgendaDefaults.EventRowOne event row. Last — the primary slot. Takes the event alone, because a list row has no position to describe.
kotlin
KalendarAgenda(events = events, onEventClick = { openDetails(it) })

Rows are keyed by KalendarEvent.id when supplied, which is what keeps per-row state attached to the right event across edits.

KalendarDatePicker#

kotlin
@Composable
public fun KalendarDatePicker(
    selectedDates: Set<LocalDate>,
    onDateClick: (date: LocalDate) -> Unit,
    modifier: Modifier = Modifier,
    state: KalendarViewState = rememberKalendarMonthState(),
    selectionMode: KalendarSelectionMode = KalendarSelectionMode.Single,
    config: KalendarViewConfig = KalendarViewConfig(),
    yearRange: IntRange? = null,
    onClear: (() -> Unit)? = null,
    onTodayClick: ((date: LocalDate) -> Unit)? = null,
    header: @Composable (KalendarHeaderScope) -> Unit = { … },
    dayOfWeekLabel: @Composable (DayOfWeek) -> Unit = { … },
    dayContent: @Composable (scope: KalendarDatePickerDayScope) -> Unit = { … },
    actions: @Composable (scope: KalendarDatePickerActionScope) -> Unit = { … },
)

A compact single-month picker — the calendar that drops out of a form field, rather than the calendar an app is built around. It trades events, drag-to-reschedule and full-bleed sizing for a header you can jump years with and a rail of commit actions.

ParameterTypeDefaultDescription
selectedDatesSet<LocalDate>The highlighted dates. Hoisted, like every other view's selection.
onDateClick(LocalDate) -> UnitCalled when a selectable date is tapped or committed from the keyboard.
modifierModifierModifierThe picker imposes no size; give it one.
stateKalendarViewStaterememberKalendarMonthState()Navigation state — and where minDate/maxDate live, so one bound both disables out-of-range cells and stops the header's arrows at the same edge.
selectionModeKalendarSelectionModeSingleHow the selection is drawn, not how a tap behaves: Range fills the span between the earliest and latest selected dates as one band. Match it to your KalendarSelectionState's mode.
configKalendarViewConfigKalendarViewConfig()disabledDates, showAdjacentMonthDates, showNavigationArrows, visibleDaysOfWeek, the formatters and background. Schedule-only and event-only options are ignored.
yearRangeIntRange?nullThe years the header's year control offers. null derives it from state's bounds where they exist and a century either side of the visible year where they do not.
onClear(() -> Unit)?nullnull hides the Clear action entirely, independently of onTodayClick.
onTodayClick((LocalDate) -> Unit)?nullCalled with today's date after the picker has scrolled to today's month, and only when today is itself selectable. null hides the action.
header@Composable (KalendarHeaderScope) -> UnitKalendarDatePickerDefaults.HeaderMonth and year controls, arrows demoted to the trailing corner.
dayOfWeekLabel@Composable (DayOfWeek) -> UnitKalendarHeaderDefaults.DayOfWeekLabelShared with the grid views — a column initial is a column initial.
dayContent@Composable (KalendarDatePickerDayScope) -> UnitKalendarDatePickerDefaults.DayCellNon-nullable here, unlike the grid views.
actions@Composable (KalendarDatePickerActionScope) -> UnitKalendarDatePickerDefaults.ActionRowOnly composed when at least one of onClear/onTodayClick was supplied.
kotlin
val selection = rememberKalendarSelectionState(mode = KalendarSelectionMode.Range)
val state = rememberKalendarMonthState(maxDate = today)

KalendarDatePicker(
    selectedDates = selection.selectedDates,
    onDateClick = selection::onDateClick,
    modifier = Modifier.width(KalendarDatePickerDefaults.Width),
    state = state,
    selectionMode = KalendarSelectionMode.Range,
    onClear = selection::clear,
    onTodayClick = selection::onDateClick,
)

Adjacent-month dates are dimmed but, unlike in KalendarMonth, remain selectable: a picker that showed the 1st of next month and then refused it would be asking the user to page for no reason.

KalendarViewConfig#

kotlin
@Immutable
public class KalendarViewConfig(
    public val disabledDates: (LocalDate) -> Boolean = { false },
    public val showNavigationArrows: Boolean = true,
    public val showAdjacentMonthDates: Boolean = true,
    public val showTodayButton: Boolean = true,
    public val showJumpPicker: Boolean = true,
    public val showSelectionIndicator: Boolean = true,
    public val monthNameFormatter: (Month) -> String = KalendarFormatters.monthName,
    public val shortMonthNameFormatter: (Month) -> String = KalendarFormatters.shortMonthName,
    public val dayOfWeekNameFormatter: (DayOfWeek) -> String = KalendarFormatters.dayOfWeekName,
    public val dayOfWeekLabelFormatter: (DayOfWeek) -> String = KalendarFormatters.dayOfWeekLabel,
    public val hourLabelFormatter: (Int) -> String = KalendarFormatters.hourLabel,
    public val visibleDaysOfWeek: Set<DayOfWeek> = ALL_DAYS_OF_WEEK,
    public val scheduleVisibleHours: KalendarHourWindow = KalendarHourWindow.FullDay,
    public val scheduleInitialScrollHour: Int = 7,
    public val scheduleDragSnapMinutes: Int = 15,
    public val eventIndicatorCap: Int = 3,
    public val background: Brush? = null,
)

Behaviour and formatting shared by every view: what appears, what is interactive, and how dates are turned into text. Colours, sizes and words live on the theme instead; dates, bounds and the clock live on the state factories.

The full option-by-option table, with which views honour each, is on Configuration.

MemberReturnsDescription
copy(…)KalendarViewConfigA duplicate with only the options passed here replaced. Revalidated.
kotlin
val base = KalendarViewConfig(monthNameFormatter = ::localizedMonthName)

KalendarMonth(selectedDate = today, config = base)
KalendarSchedule(config = base.copy(scheduleInitialScrollHour = 9))

Warning: four options are validated in the constructor and throw IllegalArgumentException on construction, not at render time: visibleDaysOfWeek must be non-empty, scheduleInitialScrollHour must be in 0..23, scheduleDragSnapMinutes at least 1, and eventIndicatorCap at least 1.

Scope types#

KalendarDatePickerDayScope#

kotlin
@Immutable
public class KalendarDatePickerDayScope(
    public val day: KalendarDayScope<KalendarEvent>,
    public val isInRange: Boolean = false,
    public val isRangeStart: Boolean = false,
    public val isRangeEnd: Boolean = false,
    public val isAdjacentMonth: Boolean = false,
)

What KalendarDatePicker's dayContent slot is given: the ordinary KalendarDayScope plus where the date sits in a range band.

PropertyTypeDefaultDescription
dayKalendarDayScope<KalendarEvent>The date, and whether it is selected, today, or disabled.
isInRangeBooleanfalseWhether the date lies inside the selected span.
isRangeStartBooleanfalseWhether it is the span's first date.
isRangeEndBooleanfalseWhether it is the span's last. Both ends are true on the same date for a half-committed range, i.e. one tap in.
isAdjacentMonthBooleanfalseWhether the date belongs to the previous or next month and is only on screen to complete a week. Dimmed, but still selectable.
copy(…)KalendarDatePickerDayScopeA duplicate with only the fields passed here replaced.
kotlin
dayContent = { scope ->
    MyPickerCell(
        date = scope.day.date,
        banded = scope.isInRange,
        muted = scope.isAdjacentMonth,
    )
}

KalendarDatePickerActionScope#

kotlin
@Stable
public interface KalendarDatePickerActionScope

What KalendarDatePicker's actions slot is given: which actions the picker was configured to offer, and what they do. An action is offered only when the picker was handed a callback for it, so a replacement row can honour that rather than hard-coding two buttons. Calling an action the picker is not offering is harmless — it does nothing.

MemberTypeDescription
todayLocalDateToday's date in the picker's time zone, re-read when the day rolls over.
showClearActionBooleanWhether an onClear handler was supplied.
showTodayActionBooleanWhether an onTodayClick handler was supplied.
isSelectionEmptyBooleanWhether nothing is currently selected. The built-in row leaves Clear live either way — a control that greys out mid-interaction is worse than one that does nothing.
clear()UnitEmpties the selection through the picker's onClear handler.
selectToday()UnitScrolls to today's month and reports it through onTodayClick — but only when today is actually selectable.
kotlin
actions = { scope ->
    Row {
        if (scope.showTodayAction) MyTextButton("Today", onClick = scope::selectToday)
        if (scope.showClearAction) MyTextButton("Clear", onClick = scope::clear)
    }
}

The *Defaults objects#

Every content slot defaults to one of these. They are public so a custom slot can fall back to the built-in rendering for the cases it does not handle specially — which is the intended pattern, and what keeps a partly-customised calendar consistent with the rest of itself.

KalendarAgendaDefaults#

FunctionSignature
DateHeader(date: LocalDate, monthNameFormatter: (Month) -> String, modifier: Modifier = Modifier)
EventRow(event: KalendarEvent, modifier: Modifier = Modifier)
EmptyState(modifier: Modifier = Modifier)
kotlin
eventContent = { event ->
    if (event.calendarId == "work") MyWorkRow(event) else KalendarAgendaDefaults.EventRow(event = event)
}

KalendarScheduleDefaults#

FunctionSignature
EventBlock(scope: KalendarScheduleEventScope<*>, modifier: Modifier = Modifier)
AllDayChip(event: KalendarEvent, modifier: Modifier = Modifier, prefix: String? = null)
HourLabel(hour: Int, formatter: (Int) -> String, modifier: Modifier = Modifier)
WeekDayHeader(date: LocalDate, isToday: Boolean, dayOfWeekLabelFormatter: (DayOfWeek) -> String, modifier: Modifier = Modifier)
NowIndicator(modifier: Modifier = Modifier)

AllDayChip's prefix is how KalendarScheduleWeek's shared all-day row labels which day each chip belongs to. A replacement slot does not get it automatically — pass it yourself.

kotlin
allDayEventContent = { event ->
    KalendarScheduleDefaults.AllDayChip(event = event, prefix = event.date.day.toString())
}

KalendarResourceDefaults#

FunctionSignature
ResourceHeader(resource: KalendarResource, modifier: Modifier = Modifier)
EventBlock(scope: KalendarResourceEventScope<*>, modifier: Modifier = Modifier)
HourLabel(hour: Int, formatter: (Int) -> String, modifier: Modifier = Modifier)
NowIndicator(modifier: Modifier = Modifier)
EmptyState(modifier: Modifier = Modifier)
kotlin
resourceHeader = { resource -> KalendarResourceDefaults.ResourceHeader(resource = resource) }

KalendarDatePickerDefaults#

MemberSignature
WidthDp312.dp, the width the picker is drawn for. It imposes no size of its own.
Header(scope: KalendarHeaderScope, modifier: Modifier = Modifier, yearRange: IntRange = …, showNavigationArrows: Boolean = true, showMonthPicker: Boolean = true, showYearPicker: Boolean = true, monthNameFormatter: (Month) -> String = …, shortMonthNameFormatter: (Month) -> String = …, previousIcon: @Composable () -> Unit = …, nextIcon: @Composable () -> Unit = …, dropdownIcon: @Composable () -> Unit = …)
DayCell(scope: KalendarDatePickerDayScope, onClick: () -> Unit, modifier: Modifier = Modifier, showSelectionBackground: Boolean = true, showTodayMarker: Boolean = true, monthNameFormatter: (Month) -> String = …)
ActionRow(scope: KalendarDatePickerActionScope, modifier: Modifier = Modifier, clearLabel: String = "Clear", todayLabel: String = KalendarTheme.strings.today)
kotlin
KalendarDatePicker(
    selectedDates = selected,
    onDateClick = ::select,
    modifier = Modifier.width(KalendarDatePickerDefaults.Width),
    header = { scope -> KalendarDatePickerDefaults.Header(scope = scope, showYearPicker = false) },
)