AgendaCalendar
One week, and what is on it.
Overview
One week, and what is on it.
First example
import { AgendaCalendar } from '@xaui/native/agenda-calendar'export function Example() {return <AgendaCalendar />}
Anatomy
<AgendaCalendar value={day} onValueChange={setDay} events={dates}>
<AgendaCalendar.Header>
<AgendaCalendar.Title />
<AgendaCalendar.Nav>
<AgendaCalendar.PreviousButton accessibilityLabel="Semaine précédente" />
<AgendaCalendar.TodayButton>Today</AgendaCalendar.TodayButton>
<AgendaCalendar.NextButton accessibilityLabel="Semaine suivante" />
</AgendaCalendar.Nav>
</AgendaCalendar.Header>
<AgendaCalendar.Weekdays />
<AgendaCalendar.Week />
</AgendaCalendar>
| slot | what it is |
|---|---|
AgendaCalendar | The card, the state, the resolved style |
AgendaCalendar.Header | The row above the strip |
AgendaCalendar.Title | The month the week is in |
AgendaCalendar.Nav | The cluster on the trailing end |
AgendaCalendar.PreviousButton | A week back. Dead at the bounds |
AgendaCalendar.NextButton | A week forward |
AgendaCalendar.TodayButton | Back to this week. It does not choose today |
AgendaCalendar.Weekdays | The seven column headings |
AgendaCalendar.Week | The seven days |
AgendaCalendar.Day | One day, and the mark under it |
AgendaCalendar.MonthPicker | The twelve months, in a row where the days were |
AgendaCalendar.YearPicker | The years, in a row where the days were |
Unlike the Calendar, this one draws its own card: a surface ground, a corner and its
padding. A strip is a thing you put on a screen; a month grid is a thing you put in a sheet.
Usage
import { AgendaCalendar } from '@xaui/native/agenda-calendar'
Root props
AgendaCalendarProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| variant | CalendarVariant | undefined | No | — |
| size | AgendaCalendarSize | undefined | No | The cell's box and the type around it. The strip spans its parent either way. |
| radius | RadiusKey | undefined | No | The chosen day's corner, which is a circle by default. |
| color | string | undefined | No | A raw tint (`'#7c3aed'`), never a token (R7). It lands on the chosen day. |
| value | Date | undefined | No | The chosen day. Controlled — leave it out and the strip holds its own. |
| defaultValue | Date | undefined | No | — |
| onValueChange | ((value: Date) => void) | undefined | No | — |
| week | Date | undefined | No | Any day in the week on screen. Controlled separately from the value, for the `Calendar`'s reason: scrolling through weeks is not choosing a day. |
| defaultWeek | Date | undefined | No | — |
| onWeekChange | ((week: Date) => void) | undefined | No | — |
| view | AgendaCalendarView | undefined | No | Which strip is on screen — the days, the months, or the years. Controlled separately again: the picker components step it, and a title button of the caller's opens it. |
| defaultView | AgendaCalendarView | undefined | No | The strip shown at first mount. |
| onViewChange | ((view: AgendaCalendarView) => void) | undefined | No | — |
| events | readonly Date[] | undefined | No | The days that have something on them. Each one gets a mark under its number, which is the whole of what makes this an *agenda* rather than a week of numbers. A list rather than a predicate because that is what a caller has: a month of events mapped to their dates. It is read by day, so the time each one carries is ignored. |
| minValue | Date | undefined | No | — |
| maxValue | Date | undefined | No | — |
| firstDayOfWeek | WeekDay | undefined | No | — |
| locale | string | undefined | No | Names the month and the weekdays. |
| isDisabled | boolean | undefined | No | — |
| style | StyleProp<ViewStyle> | No | — |
| children | ReactNode | No | — |
Slots
AgendaCalendarDayProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| date | Date | Yes | — |
| children | ReactNode | No | — |
| isDisabled | boolean | undefined | No | — |
| style | StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>) | No | — |
AgendaCalendarMonthPickerProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| format | "long" | "short" | undefined | No | How the month names read. `'long'` is "septembre", `'short'` is "sept." — a row that scrolls sideways, so a long name is a wider pill rather than a wrapped one. |
| children | ReactNode | No | — |
AgendaCalendarTextSlotProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
AgendaCalendarViewSlotProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | No | — |
AgendaCalendarWeekProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | ((date: Date) => ReactNode) | No | The `Calendar.Grid`'s escape hatch, for the same reason: seven cells from a date. |
| swipeable | boolean | undefined | No | A horizontal drag on the strip pages the week — left for the next, right for the previous — so the chevrons are a shortcut rather than the only way. Set `false` to turn it off. It runs on `react-native-gesture-handler`, an **optional** peer of this package: turning this on is what pulls it in, so a `<GestureHandlerRootView>` has to be above the strip. |
AgendaCalendarYearPickerProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| firstYear | number | undefined | No | First year in the row. Defaults to fifty back — from `minValue`'s year when bounded. |
| lastYear | number | undefined | No | Last year in the row. Defaults to fifty on — from `maxValue`'s year when bounded. |
| children | ReactNode | No | — |
Variants, sizes and colour
Sizes
size | Cell | Nav button | Pill |
|---|---|---|---|
sm | 36 | 32 | 12/16 |
md | 40 | 36 | 14/20 |
lg | 44 | 40 | 16/24 |
The cell is the Calendar's at the same size, so a strip and a month at md line up.
Variants and colour
The Calendar's four, on the same tokens, because the chosen day is the same day. The
Today pill answers the same variant: the variant is the card's one level of emphasis, and
a strip whose chosen day is a soft wash under a pill that kept a hard accent border is two
variants on one component.
variant | Chosen day | Today pill |
|---|---|---|
primary | Accent disc | Bordered, the word in the accent |
secondary | Soft accent disc | Soft accent fill |
tertiary | Neutral disc | Neutral fill |
ghost | Inverted disc | The word alone |
The emphasis runs the other way round from a Button's, and on purpose: primary outlines
rather than fills, because the pill sits between two bare chevrons and a filled accent there
would read as the primary action of the whole card. The accent goes on the word instead.
color is a raw value (R7) and reaches the disc, the mark on it, and whatever the variant
gave the pill — the border and the word on primary, the fill on secondary and
tertiary, the word on ghost.
Accessibility
- Each day is a
buttonlabelled with its full date, and a marked day says so in its label: a dot is invisible to a screen reader otherwise. The word is one language, which is the honest limit — a caller with two passesaccessibilityLabel, and that wins. - The weekday row is hidden from the accessibility tree; each day announces its own date.
- The chevrons need an
accessibilityLabelfrom the caller, for theCalendar's reason.
Migration from legacy
Implementation notes
It is a `Calendar` folded down to a week
And the row of marks is the whole difference. A strip of seven numbers is a date picker. A strip of seven numbers with marks under some of them is an agenda.
<AgendaCalendar events={[new Date(2026, 8, 7), new Date(2026, 8, 9)]} />
events is a list rather than a predicate, because a list is what a caller has: a month of
events mapped to their dates. It is read by day, so the time each one carries is ignored,
and it is turned into a set once per change rather than scanned per cell.
The cells are the Calendar's own style, resolved through calendarRecipe rather than a
second table. A strip and a month showing two different discs for the same chosen day is
what that sharing exists to prevent — and the two sit one above the other the moment a caller
expands one into the other, which is what the chevron beside the title is usually for.
Why it is a component rather than a prop
It steps by weeks. That is a different unit from the Calendar's month, so the state
under it is different too: a layout="week" prop would have had to mean "and now month is
a week", which is a prop that changes what another prop means.
Everything genuinely shared — the cell, the disc, the mark, the column headings, the date arithmetic — is shared. The API is not, because the two do not do the same thing.
No day is ever "outside"
All seven are on screen and all seven are choosable. A strip that greyed out the two days belonging to next month would be greying out days it is showing.
Only minValue / maxValue make a day inert. The chevrons go dead when the week they would
reach has no selectable day in it — the Calendar's rule, one unit down.
Swipe to page the week
A horizontal drag on AgendaCalendar.Week turns the week — left for the next, right for
the previous — so the chevrons are a shortcut rather than the only way. The strip follows
the finger a little and springs back; past ~48pt, or a fast flick, the week changes. A drag
that would land on a week with no selectable day does nothing, exactly like the dead chevron.
A tap on a day never starts a drag, and a vertical drag is handed straight back to whatever
scrolls the page. swipeable={false} turns the gesture off — and with it the
react-native-gesture-handler requirement.
The week and the day are two pieces of state
<AgendaCalendar
value={day}
onValueChange={setDay}
week={week}
onWeekChange={setWeek}
/>
Paging weeks is not choosing a day, for the reason calendar.md gives about months.
Today, in one press
AgendaCalendar.TodayButton brings today's week on screen, closes any month or year row
that is open, and chooses today — the same three things legacy's dialog did. A button
that dropped you on the right week and left you to find the day would be half a button.
select still honours the bounds, so an out-of-range today moves the strip without choosing.
It goes dead, and reads dead, only when there is nothing left for it to do: today's week is showing, the day strip is the view, and today is the chosen day. Page away, open a picker, or choose another day and it comes back to life.
The word is the caller's — "Today", "Aujourd'hui", "Hoy" — and R3 wraps a text child into the label.
Changing the month and the year
const [view, setView] = useState<AgendaCalendarView>('week')
<AgendaCalendar value={day} onValueChange={setDay} view={view} onViewChange={setView}>
<AgendaCalendar.Header>
<PressableFeedback onPress={() => setView(v => (v === 'week' ? 'year' : 'week'))}>
<AgendaCalendar.Title />
</PressableFeedback>
<AgendaCalendar.Nav>
<AgendaCalendar.PreviousButton accessibilityLabel="Précédent" />
<AgendaCalendar.TodayButton>Today</AgendaCalendar.TodayButton>
<AgendaCalendar.NextButton accessibilityLabel="Suivant" />
</AgendaCalendar.Nav>
</AgendaCalendar.Header>
{view === 'year' ? (
<AgendaCalendar.YearPicker />
) : view === 'month' ? (
<AgendaCalendar.MonthPicker />
) : (
<>
<AgendaCalendar.Weekdays />
<AgendaCalendar.Week />
</>
)}
</AgendaCalendar>
view is a third piece of state, controlled the way value and week are. The pickers are
rows in the strip's place, not a grid over it — they scroll sideways where the days
were, the way the strip itself does. The walk is the Calendar's, one unit down: the title
opens the years, a year opens that year's months, a month lands back on the week that
holds it.
One pair of chevrons pages all three. PreviousButton / NextButton step whatever
view is showing — a week, a month, or a year — and go dead when the unit they would reach
has no selectable day in it. The caller reads view to render one of the three; the pickers
only ever step it.
The title names the middle day's month
A week can straddle two. The fourth of seven is always in the majority month, and it is the only rule that does not call a week with six September days in it "August".
Cells of your own
AgendaCalendar.Week takes a function, exactly as Calendar.Grid does and for the same
reason: seven cells are generated from a date rather than written.
<AgendaCalendar.Week>
{date => (
<AgendaCalendar.Day key={date.getTime()} date={date}>
<Text>{date.getDate()}</Text>
</AgendaCalendar.Day>
)}
</AgendaCalendar.Week>
Extending it
useAgendaCalendar() is exported (R10) and carries the seven days, the chosen one,
hasEvent, view / setView, and the moves — goByWeeks, goByMonths, goByYears,
page (whichever the view shows), goToYear, goToMonthInYear, goToToday and select —
plus isOnToday and isTodayResolved.
Enough to write a strip of your own: names under the numbers, a count instead of a dot, a
second mark in another colour. Outside an <AgendaCalendar> it throws by name.