@ahrowe/ui 0.39.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -14
- package/dist/cjs-types/common/ahroweProvider/ahroweProvider.d.ts +4 -0
- package/dist/cjs-types/common/ahroweProvider/ahroweProvider.types.d.ts +12 -0
- package/dist/cjs-types/common/ahroweProvider/index.d.ts +2 -0
- package/dist/cjs-types/common/bodyEnd/portalHostContext.d.ts +6 -0
- package/dist/cjs-types/common/calendar/calendar.d.ts +4 -0
- package/dist/cjs-types/common/calendar/calendar.types.d.ts +141 -0
- package/dist/cjs-types/common/calendar/components/agendaList/agendaList.d.ts +5 -0
- package/dist/cjs-types/common/calendar/components/agendaList/agendaList.types.d.ts +23 -0
- package/dist/cjs-types/common/calendar/components/agendaList/index.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/calendarHeader/calendarHeader.d.ts +4 -0
- package/dist/cjs-types/common/calendar/components/calendarHeader/calendarHeader.types.d.ts +17 -0
- package/dist/cjs-types/common/calendar/components/calendarHeader/index.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/compactMonth/compactMonth.d.ts +9 -0
- package/dist/cjs-types/common/calendar/components/compactMonth/compactMonth.types.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/compactMonth/index.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/monthView/index.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/monthView/monthView.d.ts +5 -0
- package/dist/cjs-types/common/calendar/components/monthView/monthView.types.d.ts +10 -0
- package/dist/cjs-types/common/calendar/components/timeGrid/index.d.ts +1 -0
- package/dist/cjs-types/common/calendar/components/timeGrid/timeGrid.d.ts +5 -0
- package/dist/cjs-types/common/calendar/components/timeGrid/timeGrid.types.d.ts +40 -0
- package/dist/cjs-types/common/calendar/dates.d.ts +50 -0
- package/dist/cjs-types/common/calendar/format.d.ts +32 -0
- package/dist/cjs-types/common/calendar/gesture.d.ts +32 -0
- package/dist/cjs-types/common/calendar/index.d.ts +2 -0
- package/dist/cjs-types/common/calendar/layout.d.ts +74 -0
- package/dist/cjs-types/common/calendar/useGestures.d.ts +37 -0
- package/dist/cjs-types/common/calendar/usePager.d.ts +20 -0
- package/dist/cjs-types/common/calendar/view.types.d.ts +26 -0
- package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +3 -0
- package/dist/cjs-types/common/confirmModal/confirmModal.types.d.ts +1 -1
- package/dist/cjs-types/common/datePicker/index.d.ts +1 -1
- package/dist/cjs-types/common/errorBoundary/errorBoundary.types.d.ts +2 -2
- package/dist/cjs-types/common/modal/modal.types.d.ts +4 -3
- package/dist/cjs-types/common/themeProvider/index.d.ts +1 -1
- package/dist/cjs-types/common/themeProvider/theme.types.d.ts +16 -0
- package/dist/cjs-types/common/themeProvider/themeProvider.d.ts +7 -9
- package/dist/cjs-types/common/themeProvider/themeVariablesContext.d.ts +2 -2
- package/dist/cjs-types/common/toast/index.d.ts +1 -2
- package/dist/cjs-types/common/toast/toast.types.d.ts +4 -4
- package/dist/cjs-types/common/toast/toastHost.d.ts +4 -0
- package/dist/cjs-types/common/toast/toastService.d.ts +1 -1
- package/dist/cjs-types/common/utils/day.d.ts +8 -0
- package/dist/cjs-types/common/virtualList/useColumns.d.ts +3 -1
- package/dist/cjs-types/index.d.ts +4 -1
- package/dist/esm/common/ahroweProvider/ahroweProvider.mjs +2 -0
- package/dist/esm/common/ahroweProvider/ahroweProvider.mjs.map +1 -0
- package/dist/esm/common/bodyEnd/bodyEnd.mjs +1 -1
- package/dist/esm/common/bodyEnd/bodyEnd.mjs.map +1 -1
- package/dist/esm/common/bodyEnd/portalHostContext.mjs +2 -0
- package/dist/esm/common/bodyEnd/portalHostContext.mjs.map +1 -0
- package/dist/esm/common/calendar/calendar.mjs +2 -0
- package/dist/esm/common/calendar/calendar.mjs.map +1 -0
- package/dist/esm/common/calendar/calendar.module.mjs +2 -0
- package/dist/esm/common/calendar/calendar.module.mjs.map +1 -0
- package/dist/esm/common/calendar/calendar.types.mjs +2 -0
- package/dist/esm/common/calendar/calendar.types.mjs.map +1 -0
- package/dist/esm/common/calendar/components/agendaList/agendaList.mjs +2 -0
- package/dist/esm/common/calendar/components/agendaList/agendaList.mjs.map +1 -0
- package/dist/esm/common/calendar/components/agendaList/agendaList.module.mjs +2 -0
- package/dist/esm/common/calendar/components/agendaList/agendaList.module.mjs.map +1 -0
- package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.mjs +2 -0
- package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.mjs.map +1 -0
- package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.module.mjs +2 -0
- package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.module.mjs.map +1 -0
- package/dist/esm/common/calendar/components/compactMonth/compactMonth.mjs +2 -0
- package/dist/esm/common/calendar/components/compactMonth/compactMonth.mjs.map +1 -0
- package/dist/esm/common/calendar/components/compactMonth/compactMonth.module.mjs +2 -0
- package/dist/esm/common/calendar/components/compactMonth/compactMonth.module.mjs.map +1 -0
- package/dist/esm/common/calendar/components/monthView/monthView.mjs +2 -0
- package/dist/esm/common/calendar/components/monthView/monthView.mjs.map +1 -0
- package/dist/esm/common/calendar/components/monthView/monthView.module.mjs +2 -0
- package/dist/esm/common/calendar/components/monthView/monthView.module.mjs.map +1 -0
- package/dist/esm/common/calendar/components/timeGrid/timeGrid.mjs +2 -0
- package/dist/esm/common/calendar/components/timeGrid/timeGrid.mjs.map +1 -0
- package/dist/esm/common/calendar/components/timeGrid/timeGrid.module.mjs +2 -0
- package/dist/esm/common/calendar/components/timeGrid/timeGrid.module.mjs.map +1 -0
- package/dist/esm/common/calendar/dates.mjs +2 -0
- package/dist/esm/common/calendar/dates.mjs.map +1 -0
- package/dist/esm/common/calendar/format.mjs +2 -0
- package/dist/esm/common/calendar/format.mjs.map +1 -0
- package/dist/esm/common/calendar/gesture.mjs +2 -0
- package/dist/esm/common/calendar/gesture.mjs.map +1 -0
- package/dist/esm/common/calendar/layout.mjs +2 -0
- package/dist/esm/common/calendar/layout.mjs.map +1 -0
- package/dist/esm/common/calendar/pager.module.mjs +2 -0
- package/dist/esm/common/calendar/pager.module.mjs.map +1 -0
- package/dist/esm/common/calendar/useGestures.mjs +2 -0
- package/dist/esm/common/calendar/useGestures.mjs.map +1 -0
- package/dist/esm/common/calendar/usePager.mjs +2 -0
- package/dist/esm/common/calendar/usePager.mjs.map +1 -0
- package/dist/esm/common/carousel/carousel.module.mjs.map +1 -1
- package/dist/esm/common/confirmModal/confirmModal.mjs +1 -1
- package/dist/esm/common/confirmModal/confirmModal.mjs.map +1 -1
- package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs +1 -1
- package/dist/esm/common/datePicker/dateFunctions.mjs +1 -1
- package/dist/esm/common/datePicker/datePicker.mjs +1 -1
- package/dist/esm/common/errorBoundary/errorBoundary.mjs +2 -2
- package/dist/esm/common/hooks/useAnchorTracking.mjs.map +1 -1
- package/dist/esm/common/input/input.mjs +1 -1
- package/dist/esm/common/input/input.mjs.map +1 -1
- package/dist/esm/common/modal/modal.mjs +1 -1
- package/dist/esm/common/modal/modal.mjs.map +1 -1
- package/dist/esm/common/popover/popover.mjs +1 -1
- package/dist/esm/common/popover/popover.mjs.map +1 -1
- package/dist/esm/common/scrollFade/scrollFade.mjs +1 -1
- package/dist/esm/common/scrollbarProvider/scrollbarProvider.module.mjs.map +1 -1
- package/dist/esm/common/tabHeader/tabHeader.module.mjs.map +1 -1
- package/dist/esm/common/themeProvider/themeProvider.mjs +1 -1
- package/dist/esm/common/themeProvider/themeProvider.mjs.map +1 -1
- package/dist/esm/common/themeProvider/themeVariablesContext.mjs.map +1 -1
- package/dist/esm/common/toast/toastHost.mjs +2 -0
- package/dist/esm/common/toast/toastHost.mjs.map +1 -0
- package/dist/esm/common/toast/toastService.mjs +1 -1
- package/dist/esm/common/toast/toastService.mjs.map +1 -1
- package/dist/esm/common/tooltip/tooltip.module.mjs.map +1 -1
- package/dist/esm/common/utils/day.mjs +1 -1
- package/dist/esm/common/utils/day.mjs.map +1 -1
- package/dist/esm/common/virtualList/useColumns.mjs +1 -1
- package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/mcp.js +2 -2
- package/dist/style.css +1 -1
- package/dist/types/common/ahroweProvider/ahroweProvider.d.ts +4 -0
- package/dist/types/common/ahroweProvider/ahroweProvider.types.d.ts +12 -0
- package/dist/types/common/ahroweProvider/index.d.ts +2 -0
- package/dist/types/common/bodyEnd/portalHostContext.d.ts +6 -0
- package/dist/types/common/calendar/calendar.d.ts +4 -0
- package/dist/types/common/calendar/calendar.types.d.ts +141 -0
- package/dist/types/common/calendar/components/agendaList/agendaList.d.ts +5 -0
- package/dist/types/common/calendar/components/agendaList/agendaList.types.d.ts +23 -0
- package/dist/types/common/calendar/components/agendaList/index.d.ts +1 -0
- package/dist/types/common/calendar/components/calendarHeader/calendarHeader.d.ts +4 -0
- package/dist/types/common/calendar/components/calendarHeader/calendarHeader.types.d.ts +17 -0
- package/dist/types/common/calendar/components/calendarHeader/index.d.ts +1 -0
- package/dist/types/common/calendar/components/compactMonth/compactMonth.d.ts +9 -0
- package/dist/types/common/calendar/components/compactMonth/compactMonth.types.d.ts +1 -0
- package/dist/types/common/calendar/components/compactMonth/index.d.ts +1 -0
- package/dist/types/common/calendar/components/monthView/index.d.ts +1 -0
- package/dist/types/common/calendar/components/monthView/monthView.d.ts +5 -0
- package/dist/types/common/calendar/components/monthView/monthView.types.d.ts +10 -0
- package/dist/types/common/calendar/components/timeGrid/index.d.ts +1 -0
- package/dist/types/common/calendar/components/timeGrid/timeGrid.d.ts +5 -0
- package/dist/types/common/calendar/components/timeGrid/timeGrid.types.d.ts +40 -0
- package/dist/types/common/calendar/dates.d.ts +50 -0
- package/dist/types/common/calendar/format.d.ts +32 -0
- package/dist/types/common/calendar/gesture.d.ts +32 -0
- package/dist/types/common/calendar/index.d.ts +2 -0
- package/dist/types/common/calendar/layout.d.ts +74 -0
- package/dist/types/common/calendar/useGestures.d.ts +37 -0
- package/dist/types/common/calendar/usePager.d.ts +20 -0
- package/dist/types/common/calendar/view.types.d.ts +26 -0
- package/dist/types/common/configProvider/configProvider.types.d.ts +3 -0
- package/dist/types/common/confirmModal/confirmModal.types.d.ts +1 -1
- package/dist/types/common/datePicker/index.d.ts +1 -1
- package/dist/types/common/errorBoundary/errorBoundary.types.d.ts +2 -2
- package/dist/types/common/modal/modal.types.d.ts +4 -3
- package/dist/types/common/themeProvider/index.d.ts +1 -1
- package/dist/types/common/themeProvider/theme.types.d.ts +16 -0
- package/dist/types/common/themeProvider/themeProvider.d.ts +7 -9
- package/dist/types/common/themeProvider/themeVariablesContext.d.ts +2 -2
- package/dist/types/common/toast/index.d.ts +1 -2
- package/dist/types/common/toast/toast.types.d.ts +4 -4
- package/dist/types/common/toast/toastHost.d.ts +4 -0
- package/dist/types/common/toast/toastService.d.ts +1 -1
- package/dist/types/common/utils/day.d.ts +8 -0
- package/dist/types/common/virtualList/useColumns.d.ts +3 -1
- package/dist/types/index.d.ts +4 -1
- package/docs/AhroweProvider.md +90 -0
- package/docs/BodyEnd.md +5 -12
- package/docs/Breadcrumb.md +0 -2
- package/docs/CLAUDE.md +21 -16
- package/docs/Calendar.md +184 -0
- package/docs/CommandPalette.md +0 -2
- package/docs/ConfigProvider.md +12 -8
- package/docs/ConfirmModal.md +1 -3
- package/docs/DatePicker.md +9 -2
- package/docs/ErrorBoundary.md +5 -3
- package/docs/FormValidatorGroup.md +1 -1
- package/docs/KlipyPicker.md +1 -1
- package/docs/Menu.md +0 -2
- package/docs/Modal.md +2 -4
- package/docs/MultiSelect.md +0 -2
- package/docs/OtpInput.md +0 -2
- package/docs/Popover.md +0 -2
- package/docs/RadioGroup.md +1 -1
- package/docs/ScrollbarProvider.md +4 -0
- package/docs/SplitButton.md +0 -2
- package/docs/ThemeProvider.md +13 -10
- package/docs/TimeInput.md +2 -0
- package/docs/Toast.md +18 -17
- package/docs/Tooltip.md +1 -1
- package/docs/VirtualList.md +0 -2
- package/package.json +1 -1
- package/dist/cjs-types/common/toast/toastProvider.d.ts +0 -8
- package/dist/esm/common/toast/toastProvider.mjs +0 -2
- package/dist/esm/common/toast/toastProvider.mjs.map +0 -1
- package/dist/types/common/toast/toastProvider.d.ts +0 -8
package/docs/Calendar.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
**When to use:** Showing and editing your own events over time, in month, week, day and agenda views: bookings, shifts, appointments, a team schedule. To let a user pick one date, use [DatePicker](DatePicker.md); for a sequence of past steps, use [Timeline](Timeline.md).
|
|
4
|
+
|
|
5
|
+
**Keywords:** schedule, scheduler, planner, appointments, booking, meetings, week view, month view, drag and drop, events
|
|
6
|
+
|
|
7
|
+
**Import:** `import { Calendar, CalendarView } from '@ahrowe/ui'`
|
|
8
|
+
**Types:** `import type { CalendarProps, CalendarEvent, CalendarRange, CalendarEventChange, CalendarEventContext, CalendarSlot, CalendarLabels, CalendarWeekday, CalendarWeekStart, CalendarSlots, CalendarDefaults } from '@ahrowe/ui'`
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { Calendar, CalendarView, Popover, PopoverPlacement, Presentation } from '@ahrowe/ui';
|
|
13
|
+
import type { CalendarEvent, CalendarSlot } from '@ahrowe/ui';
|
|
14
|
+
|
|
15
|
+
// Your own event type: the calendar hands the same objects back in every callback.
|
|
16
|
+
interface Booking extends CalendarEvent {
|
|
17
|
+
room: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [bookings, setBookings] = useState<Booking[]>(initial);
|
|
21
|
+
// A clicked event, or a new slot that has no id yet.
|
|
22
|
+
const [editing, setEditing] = useState<{ booking: Booking | CalendarSlot; anchor: HTMLElement } | null>(null);
|
|
23
|
+
|
|
24
|
+
<Calendar<Booking>
|
|
25
|
+
events={bookings}
|
|
26
|
+
defaultView={CalendarView.Week}
|
|
27
|
+
// Setting a callback is what switches the interaction on.
|
|
28
|
+
onEventClick={(booking, anchor) => setEditing({ booking, anchor })}
|
|
29
|
+
onSlotCreate={(slot, anchor) => setEditing({ booking: slot, anchor })}
|
|
30
|
+
onEventChange={({ event, start, end }) =>
|
|
31
|
+
setBookings((all) => all.map((b) => (b.id === event.id ? { ...b, start, end } : b)))
|
|
32
|
+
}
|
|
33
|
+
// One line in a short event or a month row; a second line where there is room for it.
|
|
34
|
+
renderEvent={(booking, { singleLine }) =>
|
|
35
|
+
singleLine ? booking.title : <>{booking.title}<small className='room'>{booking.room}</small></>
|
|
36
|
+
}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
// The editor is yours: next to the event on a desktop, a bottom sheet on a phone.
|
|
40
|
+
<Popover
|
|
41
|
+
anchor={editing?.anchor ?? null}
|
|
42
|
+
isOpen={editing !== null}
|
|
43
|
+
onOpenChange={(isOpen) => !isOpen && setEditing(null)}
|
|
44
|
+
placement={PopoverPlacement.Right}
|
|
45
|
+
presentation={Presentation.Auto}
|
|
46
|
+
content={editing && <BookingEditor booking={editing.booking} onDone={() => setEditing(null)} />}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
// Read-only month and agenda
|
|
50
|
+
<Calendar events={events} views={[CalendarView.Month, CalendarView.Agenda]} />
|
|
51
|
+
|
|
52
|
+
// A working week that opens at 06:00
|
|
53
|
+
<Calendar events={events} defaultView={CalendarView.Week} hiddenDays={[0, 6]} scrollToHour={6} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Enums:** `CalendarView`: `Month`, `Week`, `Day`, `Agenda`
|
|
57
|
+
|
|
58
|
+
**Days and moments:** the selected day (`date`) is a `'YYYY-MM-DD'` string, like DatePicker's, and is the same day wherever the calendar is opened. Events are moments, so their `start` and `end` are `Date`s, shown in the browser's time zone. `dayTimeToDate` and `dateToTime` turn one into the other; see "When a day stands for a moment" in [DatePicker.md](DatePicker.md), also for a zone other than the browser's.
|
|
59
|
+
|
|
60
|
+
**Events** are plain objects: `{ id, title, start, end, allDay?, color?, readOnly? }`.
|
|
61
|
+
|
|
62
|
+
- `end` is **exclusive**, for timed and all-day events alike. A one-day all-day event on the 26th runs from midnight on the 26th to midnight on the 27th. A timed event ending at midnight does not show on the next day.
|
|
63
|
+
- Timed events of 24 hours or more are drawn in the all-day row, not stretched down the time grid.
|
|
64
|
+
- A timed event is drawn exactly as long as it lasts. One under 15 minutes has no room for its title, so it is a bar of its real length with the title on a line beneath it, and the next event in its column leaves that line free. Zoomed in far enough, the bar holds its title like any other event.
|
|
65
|
+
- `color` is any CSS colour, `var(--success-color)` included. The event is tinted from it against the page background, so any colour reads in both light and dark themes.
|
|
66
|
+
- `readOnly` events can still be clicked, but not moved or resized.
|
|
67
|
+
|
|
68
|
+
**Loading events and recurring series.** `onRangeChange` fires on mount and whenever the shown period changes, with that period **and one either side of it**, because swiping reveals the neighbours before it pages. Fetch events for that range, and expand recurring series into it yourself: the calendar has no recurrence rules of its own.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
|
72
|
+
|
|
73
|
+
<Calendar
|
|
74
|
+
events={events}
|
|
75
|
+
onRangeChange={async ({ start, end }) => setEvents(await api.events(start, end))}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Editing:**
|
|
80
|
+
|
|
81
|
+
- With a **mouse**, drag on empty time to create, drag an event to move it, and drag its bottom edge to resize it. A plain click on empty time creates an hour (or `slotMinutes`, if longer); in the month view it creates an all-day slot.
|
|
82
|
+
- With **touch**, press and hold for 400ms to pick up, then drag. A tap does what a click does. On a coarse pointer the resize grip is always visible.
|
|
83
|
+
- Moving and resizing snap to `slotMinutes`. A live time label follows the drag, and `Escape` cancels it.
|
|
84
|
+
- Update `events` in the same render as `onEventChange`, or **return a promise**: the event then stays where it was dropped until the promise settles, and is drawn from `events` again after that. So update `events` before it resolves, and reject to put the event back:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
onEventChange={async ({ event, start, end }) => {
|
|
88
|
+
await api.save(event.id, { start, end }); // rejects when saving fails: the event goes back
|
|
89
|
+
setBookings((all) => all.map((b) => (b.id === event.id ? { ...b, start, end } : b)));
|
|
90
|
+
}}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**An event editor.** The calendar has none of its own, because what an event holds and how it is saved are yours. The Editing example on the [demo page](https://ahrowe.com/packages/ui/demo/#calendar) is one to copy, built from `Input`, `DatePicker`, `TimeInput`, `Checkbox` and `ActionButtons`; its **Code** tab has all of it. The part that is easy to get wrong is the conversion, because the fields hold days and times and the event holds moments:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { dateToDay, dateToTime, dayTimeToDate, dayToDate } from '@ahrowe/ui';
|
|
97
|
+
import type { CalendarEvent, CalendarSlot } from '@ahrowe/ui';
|
|
98
|
+
|
|
99
|
+
// What the editor's DatePickers and TimeInputs hold.
|
|
100
|
+
interface Draft { startDay: string; startTime: string; endDay: string; endTime: string; allDay: boolean }
|
|
101
|
+
|
|
102
|
+
function toDraft({ start, end, allDay = false }: Pick<CalendarEvent, 'start' | 'end' | 'allDay'>): Draft {
|
|
103
|
+
// `end` is exclusive, so an all-day event ends at midnight after its last day. Show the last day.
|
|
104
|
+
const lastDay = allDay ? new Date(end.getTime() - 1) : end;
|
|
105
|
+
return { allDay, startDay: dateToDay(start), startTime: dateToTime(start), endDay: dateToDay(lastDay), endTime: dateToTime(end) };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function fromDraft({ startDay, startTime, endDay, endTime, allDay }: Draft): CalendarSlot {
|
|
109
|
+
if (!allDay) return { allDay, start: dayTimeToDate(startDay, startTime), end: dayTimeToDate(endDay, endTime) };
|
|
110
|
+
const end = dayToDate(endDay);
|
|
111
|
+
end.setDate(end.getDate() + 1);
|
|
112
|
+
return { allDay, start: dayToDate(startDay), end };
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The example also:
|
|
117
|
+
|
|
118
|
+
- **keeps the length when the start changes**, as calendar apps do, counting whole days for an all-day event, since a day the clocks change on is 23 or 25 hours long;
|
|
119
|
+
- **shows a placeholder while a new event is written**: it adds the draft to `events` as a `readOnly` event, and Cancel drops it;
|
|
120
|
+
- **disables Save** until the title is filled in and the end is after the start.
|
|
121
|
+
|
|
122
|
+
**Hiding days:** `hiddenDays` leaves weekdays out of every view, `[0, 6]` being the weekend. The week and month grids close up without them, the day view and its arrows step over them, and a selected day that is hidden shows as the next one after it. An event on hidden days only is not drawn; one that runs into them is drawn on its shown days.
|
|
123
|
+
|
|
124
|
+
**Navigating:** the header's arrows, **Today** and the view switcher. In the month, week and day views a horizontal swipe on touch pages to the next or previous period, with its events already in view while you swipe; the agenda is a list and pages with the arrows only. Pinch the week or day view, or hold Ctrl and use the wheel or trackpad, to zoom the hours; the zoom is kept when switching views. Clicking a day number, or a weekday in the week view, opens that day.
|
|
125
|
+
|
|
126
|
+
**On a narrow calendar** (under 600px of its own width, not the viewport's) the month view becomes a grid with coloured dots above the selected day's events. Tapping a day selects it rather than opening it, and with `onSlotCreate` set, a **New event** button above the list creates an all-day slot on the selected day. Scrolling the list folds the month up into the selected week; swiping then pages by week instead of by month. Under `prefers-reduced-motion: reduce` the month folds in one step, once the list has scrolled the whole way, instead of following the scroll.
|
|
127
|
+
|
|
128
|
+
**Key props:**
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Description |
|
|
131
|
+
|------|------|-------------|
|
|
132
|
+
| `events` | `E[]` | The events to show, `E extends CalendarEvent` |
|
|
133
|
+
| `date` / `defaultDate` / `onDateChange` | `string` | Selected day, `'YYYY-MM-DD'`, controlled or uncontrolled: the same value a [DatePicker](DatePicker.md) holds, so the two can share state |
|
|
134
|
+
| `view` / `defaultView` / `onViewChange` | `CalendarView` | Shown view, controlled or uncontrolled (default `Month`) |
|
|
135
|
+
| `views` | `CalendarView[]` | Views in the switcher, in order; hidden when there is one (default all four) |
|
|
136
|
+
| `locale` | `string` | BCP 47 tag for dates, times and the week start (default the browser's) |
|
|
137
|
+
| `weekStartsOn` | `0` to `6` | First day of the week, `0` being Sunday (default the locale's, else Monday) |
|
|
138
|
+
| `slotMinutes` | `number` | What creating, moving and resizing snap to (default `15`) |
|
|
139
|
+
| `hiddenDays` | `CalendarWeekday[]` | Weekdays left out of every view, `0` being Sunday: `[0, 6]` hides the weekend |
|
|
140
|
+
| `scrollToHour` | `number` | Hour the week and day views open at, such as `6` (default an hour before now while today is shown, otherwise `7`) |
|
|
141
|
+
| `onRangeChange` | `(range: CalendarRange) => void` | The shown period plus one on either side |
|
|
142
|
+
| `onEventClick` | `(event: E, anchor: HTMLElement) => void` | An event was clicked; `anchor` is its element, ready for `Popover`'s `anchor` |
|
|
143
|
+
| `onEventChange` | `(change: CalendarEventChange<E>) => void \| Promise<unknown>` | Moved or resized. Setting it makes events draggable. A returned promise keeps the event in place until it settles |
|
|
144
|
+
| `onSlotCreate` | `(slot: CalendarSlot, anchor: HTMLElement) => void` | Empty time clicked or dragged over. Setting it enables creating. `anchor` is the day's cell in the month view (the **New event** button in a narrow one) and an invisible box over the new slot in the week and day views, ready for `Popover`'s `anchor` |
|
|
145
|
+
| `renderEvent` | `(event: E, context: CalendarEventContext) => ReactNode` | Content of an event in place of its title. No interactive elements. `context.singleLine` is set where it has one line: an event under 45 minutes, an all-day bar, a month row. Keep to the title there |
|
|
146
|
+
| `ref` | `Ref<HTMLDivElement>` | The root element |
|
|
147
|
+
|
|
148
|
+
Give the calendar a height. It is `40em` tall by default and scrolls inside itself; `style={{ height: '100dvh' }}` or a class sets another.
|
|
149
|
+
|
|
150
|
+
**Keyboard:** in the month view the days are a grid with one tab stop on the selected day: `←`/`→` move a day, `↑`/`↓` a week, `Home`/`End` go to the start and end of the week, `PageUp`/`PageDown` a month. `Enter` or `Space` opens a list of that day's events, with a **New event** button when `onSlotCreate` is set. In the week and day views every event is a button, in time order.
|
|
151
|
+
|
|
152
|
+
**Accessibility:**
|
|
153
|
+
|
|
154
|
+
- The period title is a polite live region, so paging is announced.
|
|
155
|
+
- Days carry their full date and event count as their name, and today has `aria-current="date"`.
|
|
156
|
+
- Each day column of the time grid is a group named by its date.
|
|
157
|
+
- **Dragging needs a single-pointer alternative (WCAG 2.5.7).** Creating has one, a click. For moving and resizing it has to be your `onEventClick` dialog, so let it edit the start and end.
|
|
158
|
+
|
|
159
|
+
**Theming:** these are theme variables (typed on `ThemeVariables`). Set them theme-wide via `ThemeProvider`'s `variables`, or per instance via `style`:
|
|
160
|
+
|
|
161
|
+
| Variable | Falls back to |
|
|
162
|
+
|----------|---------------|
|
|
163
|
+
| `--calendar-hour-height` | `3em` (the height of one hour; pinching zooms from it) |
|
|
164
|
+
| `--calendar-today-color` | `var(--primary-color)` |
|
|
165
|
+
| `--calendar-now-color` | `var(--error-color)` |
|
|
166
|
+
| `--calendar-event-radius` | `6px` |
|
|
167
|
+
|
|
168
|
+
**Global defaults:** `ConfigProvider` sets `labels`, `locale`, `weekStartsOn`, `slotMinutes`, `views`, `hiddenDays` and `scrollToHour` for every calendar, e.g. `defaultProps={{ Calendar: { locale: 'de-AT', weekStartsOn: 1 } }}`. The other props depend on your event type, so they are set per instance. See [ConfigProvider.md](ConfigProvider.md).
|
|
169
|
+
|
|
170
|
+
**Slots:** `root` `header` `dayHeader` `cell` `event`
|
|
171
|
+
|
|
172
|
+
**Labels:** `previous` `next` `today` `viewSwitcher` `month` `week` `day` `agenda` `allDay` `more` `eventCount` `noEvents` `newEvent`: the text this component renders of its own. `more` and `eventCount` are functions of a count. Dates and times come from `locale`, not from here. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; keys merge individually. Type: `CalendarLabels`.
|
|
173
|
+
|
|
174
|
+
**Limits:**
|
|
175
|
+
|
|
176
|
+
- **One time zone, the browser's.** A `timeZone` prop would need `Temporal`, which iOS Safari does not ship yet.
|
|
177
|
+
- **No recurrence rules.** Expand series for the range `onRangeChange` reports.
|
|
178
|
+
- **Moving stays within the shown period**, and between days or times of the same kind: an event cannot be dragged from the time grid into the all-day row.
|
|
179
|
+
- **No right-to-left layout.**
|
|
180
|
+
- **Safari on macOS** sends a trackpad pinch as a gesture event rather than a Ctrl+wheel, so there it zooms the page as usual; pinching on a touch screen zooms the hours in every browser.
|
|
181
|
+
- **A controlled `date` must update in the same render as `onDateChange`.** After a swipe, the calendar shows the new period's panes straight away; a parent that updates later makes it jump back and forth.
|
|
182
|
+
- **A tap outside your own popover also reaches the calendar.** `Popover` closes on pointer release and lets the click through, so a tap on empty time closes it and creates a slot: an editor opened from `onSlotCreate` moves to the new slot.
|
|
183
|
+
|
|
184
|
+
HTML attributes such as `id`, `aria-label` and `data-*` are passed to the root element.
|
package/docs/CommandPalette.md
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
**Import:** `import { CommandPalette } from '@ahrowe/ui'`
|
|
8
8
|
**Types:** `import type { CommandPaletteProps, CommandPaletteLabels } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
**Requires:** `<div id="bodyEnd"></div>` in your app: the palette is a [Modal](Modal.md) and portals there.
|
|
11
|
-
|
|
12
10
|
```tsx
|
|
13
11
|
import { CommandPalette, menuHotkeys, useHotkeys } from '@ahrowe/ui';
|
|
14
12
|
import type { MenuEntry } from '@ahrowe/ui';
|
package/docs/ConfigProvider.md
CHANGED
|
@@ -7,16 +7,19 @@
|
|
|
7
7
|
**Import:** `import { ConfigProvider } from '@ahrowe/ui'`
|
|
8
8
|
**Types:** `import type { ComponentDefaults, ConfigProviderProps } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
At the app root, pass `defaultProps` and `presentation` to [AhroweProvider](AhroweProvider.md), which includes a ConfigProvider. Use ConfigProvider on its own for a section with defaults of its own:
|
|
11
11
|
|
|
12
12
|
```tsx
|
|
13
|
-
import {
|
|
13
|
+
import { AhroweProvider, ConfigProvider } from '@ahrowe/ui';
|
|
14
14
|
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<AhroweProvider defaultProps={{ Input: { alwaysFloatLabel: true } }}>
|
|
16
|
+
<App />
|
|
17
|
+
</AhroweProvider>
|
|
18
|
+
|
|
19
|
+
// Inside App: one form that wants different defaults
|
|
20
|
+
<ConfigProvider defaultProps={{ Input: { alwaysFloatLabel: false } }}>
|
|
21
|
+
<SignupForm />
|
|
22
|
+
</ConfigProvider>
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
## How defaults resolve
|
|
@@ -95,6 +98,7 @@ Each entry is a `Partial<...Props>`, so any of that component's props can be def
|
|
|
95
98
|
- **Display:** `Button` · `CopyButton` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer` · `WeatherIcon`
|
|
96
99
|
- **Overlays:** `ConfirmModal` · `Modal` · `CommandPalette` · `Popover`
|
|
97
100
|
- **Editors:** `RoomDrawer`, `RoomViewer`
|
|
101
|
+
- **Scheduling:** `Calendar`, for `labels`, `locale`, `weekStartsOn`, `slotMinutes`, `views`, `hiddenDays` and `scrollToHour` only (see below)
|
|
98
102
|
|
|
99
103
|
```tsx
|
|
100
104
|
<ConfigProvider
|
|
@@ -134,6 +138,6 @@ Every string a component renders of its own is set through its `labels` prop, an
|
|
|
134
138
|
|
|
135
139
|
**Labels merge per key**, unlike every other default here. An instance that overrides one string keeps the provider's translations for the rest, so `<Alert labels={{ dismiss: 'Weg damit' }} />` under the provider above does not lose anything else Alert might have been given. Anything the component does not produce itself, a `title`, a column `label`, a button's children, is already yours and never appears in `labels`.
|
|
136
140
|
|
|
137
|
-
`ColorPicker` and `VirtualList` appear in `ComponentDefaults` with their labels only: the first is configured elsewhere, the second has generic props that a per-key default map cannot express. `Toast` is not there at all, because `
|
|
141
|
+
`ColorPicker` and `VirtualList` appear in `ComponentDefaults` with their labels only: the first is configured elsewhere, the second has generic props that a per-key default map cannot express. `Calendar` is generic too, so its entry is the props that do not depend on the event type: `labels`, `locale`, `weekStartsOn`, `slotMinutes`, `views`, `hiddenDays` and `scrollToHour`. `Toast` is not there at all, because `AhroweProvider`'s `toast` prop is already its app-wide configuration and carries `labels` itself.
|
|
138
142
|
|
|
139
143
|
Each component's doc lists its own keys under **Labels:**.
|
package/docs/ConfirmModal.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
**Import:** `import { ConfirmModal } from '@ahrowe/ui'`
|
|
8
8
|
|
|
9
|
-
**Requires:** `<div id="bodyEnd"></div>` in your HTML (renders via portal).
|
|
10
|
-
|
|
11
9
|
```tsx
|
|
12
10
|
import { ConfirmModal } from '@ahrowe/ui';
|
|
13
11
|
|
|
@@ -34,7 +32,7 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
34
32
|
| Prop | Type | Description |
|
|
35
33
|
|------|------|-------------|
|
|
36
34
|
| `isOpen` | `boolean` | Controls visibility |
|
|
37
|
-
| `title` | `
|
|
35
|
+
| `title` | `ReactNode` | Dialog heading |
|
|
38
36
|
| `content` | `ReactNode` | Dialog body |
|
|
39
37
|
| `onConfirm` | `() => void \| Promise<void>` | Called when user confirms |
|
|
40
38
|
| `onClose` | `(didSubmit: boolean) => void` | Called on both confirm and cancel |
|
package/docs/DatePicker.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Keyboard:** the day grid uses a roving tab stop (the selected day, or today, or the 1st — one Tab reaches it, not every day individually); arrow keys move by day (`←`/`→`) or by week (`↑`/`↓`), crossing into the adjacent month automatically when they run past the edge, and Enter/Space selects the focused day.
|
|
8
8
|
|
|
9
|
-
**Import:** `import { DatePicker, dateToDay, dayToDate } from '@ahrowe/ui'`
|
|
9
|
+
**Import:** `import { DatePicker, dateToDay, dayToDate, dateToTime, dayTimeToDate } from '@ahrowe/ui'`
|
|
10
10
|
**Types:** `import type { DatePickerProps } from '@ahrowe/ui'`
|
|
11
11
|
|
|
12
12
|
```tsx
|
|
@@ -93,7 +93,14 @@ const closesAt = DateTime.fromISO(day, { zone: 'Europe/Vienna' }).endOf('day').t
|
|
|
93
93
|
const shown = DateTime.fromJSDate(closesAt, { zone: 'Europe/Vienna' }).toISODate();
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
With a [TimeInput](TimeInput.md) next to it, which holds `'HH:MM'`, the two make
|
|
96
|
+
With a [TimeInput](TimeInput.md) next to it, which holds `'HH:MM'`, the two make one moment. In the user's own zone:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const start = dayTimeToDate(day, time); // an Invalid Date unless both are complete
|
|
100
|
+
const [shownDay, shownTime] = [dateToDay(start), dateToTime(start)]; // and back, for the two inputs
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
On the day the clocks go forward a time can be missing (02:30 in Vienna on the last Sunday of March); `dayTimeToDate` moves it on by the gap, to 03:30. On the day they go back a time happens twice, and it takes the first. In a given zone the same comes from Luxon: ``DateTime.fromISO(`${day}T${time}`, { zone })``.
|
|
97
104
|
|
|
98
105
|
## Migration from Date values
|
|
99
106
|
|
package/docs/ErrorBoundary.md
CHANGED
|
@@ -65,7 +65,9 @@ import { ErrorBoundary } from '@ahrowe/ui';
|
|
|
65
65
|
</ErrorBoundary>
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a `CopyButton` that also fires a `Toast` confirmation
|
|
68
|
+
The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a `CopyButton` that also fires a `Toast` confirmation when the boundary is inside `AhroweProvider`), and a `Button` for "Try again".
|
|
69
|
+
|
|
70
|
+
Put an app-wide boundary inside `AhroweProvider`, not around it. Around it, the error has unmounted the provider by the time the fallback shows, so the fallback renders unthemed and its copy confirmation never appears.
|
|
69
71
|
|
|
70
72
|
**Key props:**
|
|
71
73
|
|
|
@@ -89,7 +91,7 @@ The default fallback is a centered card with a tinted icon badge, an `Accordion`
|
|
|
89
91
|
| `copy` | `"Copy"` | Copy button tooltip and accessible name before copying |
|
|
90
92
|
| `copied` | `"Copied"` | Copy button tooltip and accessible name right after a successful copy |
|
|
91
93
|
| `reset` | `"Try again"` | Reset button label |
|
|
92
|
-
| `copySuccessToast` | `"Stack trace copied to clipboard"` | Toast shown after a successful copy
|
|
93
|
-
| `copyErrorToast` | `"Could not copy stack trace"` | Toast shown if the copy fails |
|
|
94
|
+
| `copySuccessToast` | `"Stack trace copied to clipboard"` | Toast shown after a successful copy, inside `AhroweProvider` only |
|
|
95
|
+
| `copyErrorToast` | `"Could not copy stack trace"` | Toast shown if the copy fails, inside `AhroweProvider` only |
|
|
94
96
|
|
|
95
97
|
**Slots:** `root` `header` `badge` `icon` `title` `description` `stack` `stackHeader` `stackLabel` `stackTrace` `copyButton` `actions` `resetButton`
|
|
@@ -71,7 +71,7 @@ const editForm = useFormValidatorGroup(fields, { initialValues: user, onSubmit:
|
|
|
71
71
|
5. Otherwise calls `onSubmit(values, group)`. If it throws, the error is passed to `parseError(err, true)` to surface field-level or generic errors.
|
|
72
72
|
6. Sets `isSubmitting` false.
|
|
73
73
|
|
|
74
|
-
By default `onSubmitError` shows the first error as an error **toast** (
|
|
74
|
+
By default `onSubmitError` shows the first error as an error **toast** (see `Toast`). Override it via `options.onSubmitError`.
|
|
75
75
|
|
|
76
76
|
## Options
|
|
77
77
|
|
package/docs/KlipyPicker.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Import:** `import { KlipyPicker } from '@ahrowe/ui'`
|
|
8
8
|
|
|
9
|
-
**Requires:**
|
|
9
|
+
**Requires:** a Klipy API token, passed as the `token` prop. There is no built-in key: without one the picker renders but every request fails.
|
|
10
10
|
|
|
11
11
|
```tsx
|
|
12
12
|
import { KlipyPicker } from '@ahrowe/ui';
|
package/docs/Menu.md
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
**Import:** `import { Menu, menuHotkeys } from '@ahrowe/ui'`
|
|
8
8
|
**Types:** `import type { MenuProps, MenuEntry, MenuAction } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
**Requires:** `<div id="bodyEnd"></div>` in your app: the menu is portalled there, like every other anchored panel in the library.
|
|
11
|
-
|
|
12
10
|
```tsx
|
|
13
11
|
import { Menu } from '@ahrowe/ui';
|
|
14
12
|
import type { MenuEntry } from '@ahrowe/ui';
|
package/docs/Modal.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
**Import:** `import { Modal } from '@ahrowe/ui'`
|
|
8
8
|
|
|
9
|
-
**Requires:** `<div id="bodyEnd"></div>` in your HTML (renders via portal).
|
|
10
|
-
|
|
11
9
|
The panel itself never scrolls: the header is fixed and the children scroll inside the `body` slot, so the title and close button stay visible no matter how long the content is. Pass `footer` for anything that should stay put below the content, typically the action buttons.
|
|
12
10
|
|
|
13
11
|
While open, the modal traps focus (focus moves into the panel and returns to whatever was focused before on close), locks scrolling on the page behind it, and closes on Escape. Modals nest correctly: Escape closes only the topmost one, and the page stays locked until the last one closes. The same applies to anything opened from inside a modal — a `Menu`, `Dropdown` or `Popover` counts as the layer above it, so Escape closes that first, and the modal's focus trap leaves focus alone while it's open (all three render through the same portal, so their DOM sits beside the modal's panel rather than inside it).
|
|
@@ -48,7 +46,7 @@ const [isOpen, setIsOpen] = useState(false);
|
|
|
48
46
|
| Prop | Type | Description |
|
|
49
47
|
|------|------|-------------|
|
|
50
48
|
| `isOpen` | `boolean` | Controls visibility |
|
|
51
|
-
| `title` | `
|
|
49
|
+
| `title` | `ReactNode` | Header title, also the dialog's accessible name |
|
|
52
50
|
| `onClose` | `() => void` | Called when the backdrop is clicked, the close button is pressed, or Escape is pressed |
|
|
53
51
|
| `footer` | `ReactNode` | Pinned below the scrolling body, e.g. action buttons. Nothing renders when omitted |
|
|
54
52
|
| `hideHeader` | `boolean` | Removes the header bar |
|
|
@@ -92,7 +90,7 @@ To switch it off and go back to a hard edge, set `--modal-scroll-fade: 0px`, per
|
|
|
92
90
|
|
|
93
91
|
**Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Modal: { presentation: Presentation.Auto } }}` — which also covers `DatePicker`, `TimeInput` and `KlipyPicker` when they open in a Modal via their own `isModal`. `presentation={Presentation.Auto}` on the provider itself sets every sheet-capable component at once. See [ConfigProvider.md](ConfigProvider.md).
|
|
94
92
|
|
|
95
|
-
The panel also takes the full native `div` attribute set (`id`, `role`, `aria-*`, `data-*`, …). Pass `aria-label` when using `hideHeader`,
|
|
93
|
+
The panel also takes the full native `div` attribute set (`id`, `role`, `aria-*`, `data-*`, …). The title's text names the dialog for screen readers. Pass `aria-label` when using `hideHeader`, or when the title has no text of its own (an icon alone): it then takes over from the title.
|
|
96
94
|
|
|
97
95
|
**Slots:** `container` `backdrop` `modal` `header` `title` `closeButton` `body` `footer`
|
|
98
96
|
|
package/docs/MultiSelect.md
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
**Import:** `import { MultiSelect } from '@ahrowe/ui'`
|
|
8
8
|
**Types:** `import type { MultiSelectProps, MultiSelectItem, MultiSelectValue } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
**Requires:** `<div id="bodyEnd"></div>` in your app — the panel renders through a portal.
|
|
11
|
-
|
|
12
10
|
```tsx
|
|
13
11
|
import { useState } from 'react';
|
|
14
12
|
import { MultiSelect } from '@ahrowe/ui';
|
package/docs/OtpInput.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
**Import:** `import { OtpInput, OtpInputCharset } from '@ahrowe/ui'`
|
|
8
8
|
|
|
9
|
-
**Requires:** `<div id="bodyEnd"></div>` in your HTML (the error message renders via `Tooltip`, which portals).
|
|
10
|
-
|
|
11
9
|
**Charset:** `OtpInputCharset.Numeric` (default) | `OtpInputCharset.Alphanumeric`
|
|
12
10
|
|
|
13
11
|
```tsx
|
package/docs/Popover.md
CHANGED
|
@@ -88,8 +88,6 @@ Only a `Click` trigger presents as a sheet. `Hover` and `Focus` open something s
|
|
|
88
88
|
|
|
89
89
|
**Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ Popover: { presentation: Presentation.Auto } }}`, or `presentation={Presentation.Auto}` on the provider itself to set every sheet-capable component at once. See [ConfigProvider.md](ConfigProvider.md).
|
|
90
90
|
|
|
91
|
-
Requires a `<div id="bodyEnd"></div>` at the app root — the panel is portaled there, same as `Tooltip` and `Menu`.
|
|
92
|
-
|
|
93
91
|
**Scrolling:** where the browser supports CSS anchor positioning, including the trigger's transforms, the panel is anchored to the trigger in CSS, so it moves with a scrolling trigger in the same frame instead of catching up a frame later. Firefox resolves anchors without transforms, so it keeps the pixel positioning. While it is open the trigger carries an inline `anchor-name`, added to any inline one it already has; an `anchor-name` your stylesheet gives the trigger is overridden for that time.
|
|
94
92
|
|
|
95
93
|
**Keyboard & focus (click trigger):** the panel is portaled to the end of the DOM, so Tab can't reach it naturally. When the panel has focusable content and the keyboard opened it (Enter or Space on the trigger, or ArrowDown/ArrowUp), focus moves to the first focusable element and the panel gets `role="dialog"`; a pointer click leaves the focus where it already is. `focusOnOpen` forces either. Tabbing past the last element closes the popover and moves focus to the element after the trigger; Shift+Tab past the first element, and Escape, close it and return focus to the trigger. This is non-modal: focus is not trapped, and hover/focus triggers don't steal focus. Escape closes only the innermost open layer, so a popover inside a `Modal` closes on the first press and leaves the modal open. Popovers with only static content don't manage focus or claim the dialog role.
|
package/docs/RadioGroup.md
CHANGED
|
@@ -71,7 +71,7 @@ interface RadioOption {
|
|
|
71
71
|
| `errorMessage` | `string` | Manual error message (when not using a `formValidator`) |
|
|
72
72
|
| `isValid` | `boolean` | Manual valid state (default `true`) |
|
|
73
73
|
|
|
74
|
-
**Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces only after the field is *touched* (focus leaves the group), then on focus or hover
|
|
74
|
+
**Validation:** like `Input`, an errored group shows an error `Tooltip` and turns its unselected circles red. With a `formValidator` the error surfaces only after the field is *touched* (focus leaves the group), then on focus or hover.
|
|
75
75
|
|
|
76
76
|
**Global defaults:** adopts `ConfigProvider` — e.g. `defaultProps={{ RadioGroup: { orientation: RadioGroupOrientation.Horizontal } }}`. See [ConfigProvider.md](ConfigProvider.md).
|
|
77
77
|
|
|
@@ -20,3 +20,7 @@ import { ScrollbarProvider } from '@ahrowe/ui';
|
|
|
20
20
|
|------|------|-------------|
|
|
21
21
|
| `className` | `string` | Additional class on the wrapper |
|
|
22
22
|
| `children` | `ReactNode` | Scrollable content |
|
|
23
|
+
|
|
24
|
+
**Your own rules win.** Its rules have zero specificity, so any scrollbar rule of a component's or of yours, on an element inside it, takes precedence.
|
|
25
|
+
|
|
26
|
+
**The whole app:** `AhroweProvider` already styles every scrollbar in the app, portalled panels included, unless it is given `scrollbars={false}`. Use `ScrollbarProvider` for one region in an app that turned that off.
|
package/docs/SplitButton.md
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
**Types:** `import type { SplitButtonAction, SplitButtonProps } from '@ahrowe/ui'`
|
|
9
9
|
**Enums:** `import { PopoverAlign } from '@ahrowe/ui'` for `align`.
|
|
10
10
|
|
|
11
|
-
**Requires:** `<div id="bodyEnd"></div>` in your HTML (the actions menu is a `Menu`, which portals).
|
|
12
|
-
|
|
13
11
|
```tsx
|
|
14
12
|
import { SplitButton } from '@ahrowe/ui';
|
|
15
13
|
import type { SplitButtonAction } from '@ahrowe/ui';
|
package/docs/ThemeProvider.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# ThemeProvider
|
|
2
2
|
|
|
3
|
-
**When to use:**
|
|
3
|
+
**When to use:** Applies a theme to its subtree, injecting its CSS custom properties as inline styles so every component inside can use them. [AhroweProvider](AhroweProvider.md) includes one and takes the same `themes` and `currentThemeId`: use that at the app root, and ThemeProvider on its own for a section with a different theme.
|
|
4
4
|
|
|
5
5
|
**Keywords:** theming, dark mode, gradient
|
|
6
6
|
|
|
7
7
|
**Import:** `import { ThemeProvider } from '@ahrowe/ui'`
|
|
8
|
-
**Types:** `import type { Theme, ThemeVariables } from '@ahrowe/ui'`
|
|
8
|
+
**Types:** `import type { Theme, ThemeVariables, ThemeProviderProps } from '@ahrowe/ui'`
|
|
9
9
|
**Built-in themes:** `import { defaultTheme, lightTheme } from '@ahrowe/ui'`
|
|
10
10
|
|
|
11
11
|
```tsx
|
|
@@ -58,18 +58,21 @@ no `themes` at all. `lightTheme` is its light sibling: same brand colour, invert
|
|
|
58
58
|
both to let the user choose.
|
|
59
59
|
|
|
60
60
|
```tsx
|
|
61
|
-
import {
|
|
61
|
+
import { AhroweProvider, defaultTheme, lightTheme, useTheme } from '@ahrowe/ui';
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
// A new array on every render reads as a theme change, so keep it at module scope.
|
|
64
|
+
const THEMES = [defaultTheme, lightTheme];
|
|
65
|
+
|
|
66
|
+
function Root() {
|
|
64
67
|
const { currentThemeId, setCurrentThemeId } = useTheme(defaultTheme.id);
|
|
65
68
|
|
|
66
69
|
return (
|
|
67
|
-
<
|
|
70
|
+
<AhroweProvider themes={THEMES} currentThemeId={currentThemeId}>
|
|
68
71
|
<button onClick={() => setCurrentThemeId(currentThemeId === 'light' ? 'default' : 'light')}>
|
|
69
72
|
Toggle theme
|
|
70
73
|
</button>
|
|
71
74
|
<App />
|
|
72
|
-
</
|
|
75
|
+
</AhroweProvider>
|
|
73
76
|
);
|
|
74
77
|
}
|
|
75
78
|
```
|
|
@@ -116,9 +119,9 @@ interface Theme {
|
|
|
116
119
|
| `themes` | `Theme[]` | Theme definitions — defaults to built-in default theme |
|
|
117
120
|
| `currentThemeId` | `string` | Active theme id — must match one of the `themes[].id` values |
|
|
118
121
|
|
|
119
|
-
**Note:** Without `ThemeProvider
|
|
122
|
+
**Note:** Without a `ThemeProvider` or `AhroweProvider` above them, all CSS variables are undefined and components will be unstyled.
|
|
120
123
|
|
|
121
|
-
**Base typography:** The wrapper also sets `font-family`, `font-size`, `font-weight`, and `color` from the theme's `--default-font`, `--default-font-size`, `--default-font-weight`, and `--text-color`, so everything inside — including
|
|
124
|
+
**Base typography:** The wrapper also sets `font-family`, `font-size`, `font-weight`, and `color` from the theme's `--default-font`, `--default-font-size`, `--default-font-weight`, and `--text-color`, so everything inside — including what `AhroweProvider` portals — inherits the theme's base text style without each component having to set it.
|
|
122
125
|
|
|
123
126
|
## Gradient primary
|
|
124
127
|
|
|
@@ -202,7 +205,7 @@ through its own variable (`--slider-range-color`, `--pagination-active-backgroun
|
|
|
202
205
|
|
|
203
206
|
## Nested providers and portals
|
|
204
207
|
|
|
205
|
-
Providers nest: an inner one overrides the variables it declares and inherits the rest, so a differently-themed panel inside a themed app works without restating the whole theme.
|
|
208
|
+
Providers nest: an inner one overrides the variables it declares and inherits the rest, so a differently-themed panel inside a themed app works without restating the whole theme. Only the outermost one sets the page's `<meta name="theme-color">` to its `--primary-color`; an inner one themes its section, not the browser.
|
|
206
209
|
|
|
207
|
-
A portal breaks that by default, because theme variables travel by CSS inheritance and a portal escapes the subtree. Anything rendered through `BodyEnd` — `Tooltip`, `Dropdown`, `InputDropdown`, `ColorPicker`, `Menu`, `Popover`, `Modal`, `Toast` — therefore carries the nearest provider's variables with it, so an open panel matches the panel that opened it rather than whatever wraps
|
|
210
|
+
A portal breaks that by default, because theme variables travel by CSS inheritance and a portal escapes the subtree. Anything rendered through `BodyEnd` — `Tooltip`, `Dropdown`, `InputDropdown`, `ColorPicker`, `Menu`, `Popover`, `Modal`, `Toast` — therefore carries the nearest provider's variables with it, so an open panel matches the panel that opened it rather than whatever wraps the element it portals into.
|
|
208
211
|
|
package/docs/TimeInput.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
The value is a `"HH:MM"` 24-hour string (e.g. `"09:30"`) everywhere — also when `use24Hour={false}` is set (that flag only changes the dial UI, not the stored value).
|
|
10
10
|
|
|
11
|
+
With a [DatePicker](DatePicker.md) beside it, `dayTimeToDate(day, time)` makes the two one `Date`, and `dateToTime(date)` gives a `Date`'s time back for the field. See "When a day stands for a moment" there.
|
|
12
|
+
|
|
11
13
|
```tsx
|
|
12
14
|
import { TimeInput, FormValidator } from '@ahrowe/ui';
|
|
13
15
|
|
package/docs/Toast.md
CHANGED
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
**Keywords:** snackbar, notification popup
|
|
6
6
|
|
|
7
|
-
**Import:** `import {
|
|
8
|
-
**Types:** `import type { ToastOptions, ToastType, ToastPosition } from '@ahrowe/ui'`
|
|
7
|
+
**Import:** `import { showToast } from '@ahrowe/ui'`
|
|
8
|
+
**Types:** `import type { ToastOptions, ToastContextConfig, ToastLabels, ToastType, ToastPosition } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
**Setup:**
|
|
10
|
+
**Setup:** [AhroweProvider](AhroweProvider.md) shows the toasts. Its `toast` prop sets the defaults for all of them:
|
|
11
11
|
|
|
12
12
|
```tsx
|
|
13
|
-
import {
|
|
13
|
+
import { AhroweProvider } from '@ahrowe/ui';
|
|
14
14
|
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
</ToastProvider>
|
|
19
|
-
</ThemeProvider>
|
|
15
|
+
<AhroweProvider toast={{ position: 'bottom-right', duration: 3000 }}>
|
|
16
|
+
<App />
|
|
17
|
+
</AhroweProvider>
|
|
20
18
|
```
|
|
21
19
|
|
|
20
|
+
`ToastProvider` was removed in 0.41. It took the same settings as props: move them into `toast`.
|
|
21
|
+
|
|
22
22
|
**Usage:** Call `showToast` anywhere — no component hierarchy required:
|
|
23
23
|
|
|
24
24
|
```tsx
|
|
@@ -56,16 +56,17 @@ showToast('Done', {
|
|
|
56
56
|
});
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
**
|
|
59
|
+
**ToastContextConfig**, the `toast` prop. Every key but `allowStacking` can also be passed to a single `showToast`:
|
|
60
60
|
|
|
61
|
-
|
|
|
62
|
-
|
|
63
|
-
| `position` | `ToastPosition` | `'
|
|
64
|
-
| `duration` | `number` | `
|
|
65
|
-
| `showRemainingTime` | `boolean` | `
|
|
66
|
-
| `showCloseButton` | `boolean` | `
|
|
61
|
+
| Key | Type | Default | Description |
|
|
62
|
+
|-----|------|---------|-------------|
|
|
63
|
+
| `position` | `ToastPosition` | `'top-right'` | Screen position |
|
|
64
|
+
| `duration` | `number` | `4000` | Auto-dismiss delay in ms; `0` = persistent |
|
|
65
|
+
| `showRemainingTime` | `boolean` | `true` | Show a progress bar draining to zero |
|
|
66
|
+
| `showCloseButton` | `boolean` | `true` | Show × button |
|
|
67
67
|
| `allowStacking` | `boolean` | `true` | Allow multiple toasts simultaneously |
|
|
68
68
|
| `pauseOnHover` | `boolean` | `true` | Pause the auto-dismiss timer while the toast is hovered or focused |
|
|
69
|
+
| `labels` | `ToastLabels` | | See **Labels** |
|
|
69
70
|
|
|
70
71
|
**Swipe to dismiss:** on touch devices, every toast can be dragged away to dismiss it, on top of the close button and auto-dismiss timer. A fast flick dismisses even without dragging far; dragging pauses the auto-dismiss timer for the duration of the gesture (regardless of `pauseOnHover`) and a vertical drag is left alone so it doesn't fight the page's own scrolling. It's gated to coarse (touch) pointers only — on mouse/trackpad devices the close button and auto-dismiss already cover it, and a drag there is more likely to be a text selection than an intentional dismiss.
|
|
71
72
|
|
|
@@ -75,7 +76,7 @@ showToast('Done', {
|
|
|
75
76
|
|
|
76
77
|
**Slots:** `root` `icon` `content` `closeButton` `progressBar`
|
|
77
78
|
|
|
78
|
-
**Labels:** `close
|
|
79
|
+
**Labels:** `close`, the dismiss button's screen-reader name. Set it for the whole app in `AhroweProvider`'s `toast={{ labels: { close: 'Schließen' } }}`, or per toast with `showToast('Saved', { labels: { close: 'Schließen' } })`. Type: `ToastLabels`.
|
|
79
80
|
|
|
80
81
|
## Sizing
|
|
81
82
|
|
package/docs/Tooltip.md
CHANGED
|
@@ -77,4 +77,4 @@ The default bubble is an **inverse of the page** rather than a fixed colour: it
|
|
|
77
77
|
|
|
78
78
|
The bubble, its arrow, the `details` badge and the error variant all scale with the theme's text. The bubble's `max-width` is `em`, so it bounds a line measure rather than a pixel count as the text grows.
|
|
79
79
|
|
|
80
|
-
The badge derives its size from `--default-font-size` directly rather than from `em`. It renders inline wherever you put it, so an `em` would measure the surrounding text instead of the theme; the bubble can use `em` because it is
|
|
80
|
+
The badge derives its size from `--default-font-size` directly rather than from `em`. It renders inline wherever you put it, so an `em` would measure the surrounding text instead of the theme; the bubble can use `em` because it is portalled to the end of the app and always lands on the theme's own size.
|
package/docs/VirtualList.md
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
**Import:** `import { VirtualList } from '@ahrowe/ui'`
|
|
8
8
|
**Types:** `import type { VirtualListProps, VirtualListColumn, VirtualListHandle, VirtualListSort } from '@ahrowe/ui'`
|
|
9
9
|
|
|
10
|
-
**Requires:** `<div id="bodyEnd"></div>` in your app **only if** you use the column-visibility toggle (the gear menu) — its popover is a `Popover` that renders via portal. Lists without columns, or with `showColumnToggle={false}`, don't need it.
|
|
11
|
-
|
|
12
10
|
```tsx
|
|
13
11
|
import { VirtualList } from '@ahrowe/ui';
|
|
14
12
|
import type { VirtualListColumn } from '@ahrowe/ui';
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { default as React, ReactNode } from 'react';
|
|
2
|
-
import { ToastContextConfig } from './toast.types.js';
|
|
3
|
-
interface ToastProviderProps extends ToastContextConfig {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
}
|
|
6
|
-
declare function ToastProvider({ children, position, duration, showRemainingTime, showCloseButton, allowStacking, pauseOnHover, labels, }: ToastProviderProps): React.JSX.Element;
|
|
7
|
-
export default ToastProvider;
|
|
8
|
-
export type { ToastProviderProps };
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use client";import e from"../bodyEnd/bodyEnd.mjs";import t from"./toast.module.mjs";import n from"./toast.mjs";import{_registerToastHandler as r,_unregisterToastHandler as i}from"./toastService.mjs";import{useCallback as a,useEffect as o,useRef as s,useState as c}from"react";import l from"classnames";import{Fragment as u,jsx as d,jsxs as f}from"react/jsx-runtime";var p={position:`top-right`,duration:4e3,showRemainingTime:!0,showCloseButton:!0,allowStacking:!0,pauseOnHover:!0,labels:{close:`Close`}},m=220;function h(e){return t[`container-${e}`]}function g({children:g,position:_=p.position,duration:v=p.duration,showRemainingTime:y=p.showRemainingTime,showCloseButton:b=p.showCloseButton,allowStacking:x=p.allowStacking,pauseOnHover:S=p.pauseOnHover,labels:C}){let[w,T]=c([]),E=s(0),D=a(e=>{T(t=>t.map(t=>t.id===e?{...t,dismissing:!0}:t)),setTimeout(()=>{T(t=>t.filter(t=>t.id!==e))},m)},[]),O=a((e,t)=>{let n={position:t?.position??_,duration:t?.duration??v,showRemainingTime:t?.showRemainingTime??y,showCloseButton:t?.showCloseButton??b,pauseOnHover:t?.pauseOnHover??S,type:t?.type??`default`,classNames:t?.classNames,styles:t?.styles};E.current+=1;let r={id:`toast-${E.current}`,content:e,options:n,dismissing:!1};T(e=>!x&&e.length>0?(setTimeout(()=>{T([r])},m),e.map(e=>({...e,dismissing:!0}))):[...e,r])},[_,v,y,b,x,S]);o(()=>(r(O),()=>i()),[O]);let k=w.reduce((e,t)=>{let n=t.options.position;return e[n]||(e[n]=[]),e[n].push(t),e},{});return f(u,{children:[g,d(e,{children:Object.entries(k).map(([e,r])=>d(`div`,{className:l(t.container,h(e)),children:r.map(t=>d(n,{item:t,isBottom:e.startsWith(`bottom`),onDismiss:D,labels:{...p.labels,...C,...t.labels}},t.id))},e))})]})}export{g as default};
|
|
2
|
-
//# sourceMappingURL=toastProvider.mjs.map
|