panelui-native 0.25.0 → 0.30.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 +18 -1
- package/lib/module/components/accordion/index.js +8 -8
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +2 -2
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +182 -50
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/attachment/index.js +2 -1
- package/lib/module/components/attachment/index.js.map +1 -1
- package/lib/module/components/avatar/index.js +3 -3
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/badge/index.js +4 -4
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +197 -56
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +15 -10
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +6 -6
- package/lib/module/components/breadcrumb/index.js.map +1 -1
- package/lib/module/components/button/index.js +11 -6
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/calendar/index.js +792 -0
- package/lib/module/components/calendar/index.js.map +1 -0
- package/lib/module/components/carousel/index.js +680 -0
- package/lib/module/components/carousel/index.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -4
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/code-block/highlight.js +363 -0
- package/lib/module/components/code-block/highlight.js.map +1 -0
- package/lib/module/components/code-block/index.js +313 -0
- package/lib/module/components/code-block/index.js.map +1 -0
- package/lib/module/components/date-picker/index.js +165 -0
- package/lib/module/components/date-picker/index.js.map +1 -0
- package/lib/module/components/dialog/index.js +7 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/direction/index.js +2 -1
- package/lib/module/components/direction/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +2 -2
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/field/index.js +5 -5
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/index.js +3 -3
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +5 -5
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +3 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +9 -9
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/label/index.js +4 -4
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +167 -44
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +7 -7
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/marker/index.js +3 -3
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/menu/index.js +714 -0
- package/lib/module/components/menu/index.js.map +1 -0
- package/lib/module/components/message/index.js +7 -7
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +5 -4
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/plan/index.js +296 -0
- package/lib/module/components/plan/index.js.map +1 -0
- package/lib/module/components/popover/index.js +73 -10
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +15 -10
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/reasoning/index.js +244 -0
- package/lib/module/components/reasoning/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +4 -4
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/select/index.js +10 -4
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/sources/index.js +221 -0
- package/lib/module/components/sources/index.js.map +1 -0
- package/lib/module/components/steps/index.js +4 -4
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/tabs/index.js +7 -7
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/task/index.js +300 -0
- package/lib/module/components/task/index.js.map +1 -0
- package/lib/module/components/textarea/index.js +295 -0
- package/lib/module/components/textarea/index.js.map +1 -0
- package/lib/module/components/timeline/index.js +4 -4
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/toast/index.js +2 -2
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +5 -5
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tooltip/index.js +648 -0
- package/lib/module/components/tooltip/index.js.map +1 -0
- package/lib/module/components/typography/index.js +2 -2
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-back-handler.js +37 -0
- package/lib/module/hooks/use-back-handler.js.map +1 -0
- package/lib/module/icons/index.js +202 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/collapse.js +112 -0
- package/lib/module/primitives/collapse.js.map +1 -0
- package/lib/module/primitives/keyboard-avoider.js +2 -1
- package/lib/module/primitives/keyboard-avoider.js.map +1 -1
- package/lib/module/primitives/text.js +25 -1
- package/lib/module/primitives/text.js.map +1 -1
- package/lib/module/utils/date.js +386 -0
- package/lib/module/utils/date.js.map +1 -0
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +41 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +44 -2
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/calendar/index.d.ts +173 -0
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/carousel/index.d.ts +179 -0
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -1
- package/lib/typescript/src/components/code-block/highlight.d.ts +39 -0
- package/lib/typescript/src/components/code-block/highlight.d.ts.map +1 -0
- package/lib/typescript/src/components/code-block/index.d.ts +113 -0
- package/lib/typescript/src/components/code-block/index.d.ts.map +1 -0
- package/lib/typescript/src/components/date-picker/index.d.ts +97 -0
- package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts.map +1 -1
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +41 -2
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts +261 -0
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +127 -0
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +35 -1
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/reasoning/index.d.ts +97 -0
- package/lib/typescript/src/components/reasoning/index.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sources/index.d.ts +85 -0
- package/lib/typescript/src/components/sources/index.d.ts.map +1 -0
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/task/index.d.ts +103 -0
- package/lib/typescript/src/components/task/index.d.ts.map +1 -0
- package/lib/typescript/src/components/textarea/index.d.ts +175 -0
- package/lib/typescript/src/components/textarea/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tooltip/index.d.ts +180 -0
- package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-back-handler.d.ts +19 -0
- package/lib/typescript/src/hooks/use-back-handler.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +17 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +16 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/collapse.d.ts +42 -0
- package/lib/typescript/src/primitives/collapse.d.ts.map +1 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.d.ts +20 -0
- package/lib/typescript/src/primitives/text.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +116 -0
- package/lib/typescript/src/utils/date.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +8 -12
- package/src/components/alert/index.tsx +2 -2
- package/src/components/area-chart/index.tsx +208 -55
- package/src/components/attachment/index.tsx +2 -2
- package/src/components/avatar/index.tsx +3 -3
- package/src/components/badge/index.tsx +6 -6
- package/src/components/bar-chart/index.tsx +214 -54
- package/src/components/bottom-sheet/index.tsx +17 -16
- package/src/components/breadcrumb/index.tsx +6 -8
- package/src/components/button/index.tsx +12 -8
- package/src/components/calendar/index.tsx +970 -0
- package/src/components/carousel/index.tsx +788 -0
- package/src/components/chip/index.tsx +4 -6
- package/src/components/code-block/highlight.ts +278 -0
- package/src/components/code-block/index.tsx +341 -0
- package/src/components/date-picker/index.tsx +255 -0
- package/src/components/dialog/index.tsx +7 -2
- package/src/components/direction/index.tsx +2 -1
- package/src/components/empty-state/index.tsx +2 -2
- package/src/components/field/index.tsx +3 -5
- package/src/components/flow/index.tsx +3 -3
- package/src/components/frame/index.tsx +5 -7
- package/src/components/input-group/index.tsx +3 -2
- package/src/components/item/index.tsx +9 -9
- package/src/components/label/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +189 -50
- package/src/components/map/index.tsx +7 -9
- package/src/components/marker/index.tsx +3 -3
- package/src/components/menu/index.tsx +812 -0
- package/src/components/message/index.tsx +7 -7
- package/src/components/message-scroller/index.tsx +5 -4
- package/src/components/plan/index.tsx +325 -0
- package/src/components/popover/index.tsx +111 -8
- package/src/components/radio-group/index.tsx +13 -4
- package/src/components/reasoning/index.tsx +297 -0
- package/src/components/section-rail/index.tsx +4 -4
- package/src/components/select/index.tsx +10 -4
- package/src/components/sources/index.tsx +278 -0
- package/src/components/steps/index.tsx +4 -4
- package/src/components/tabs/index.tsx +7 -9
- package/src/components/task/index.tsx +329 -0
- package/src/components/textarea/index.tsx +381 -0
- package/src/components/timeline/index.tsx +4 -4
- package/src/components/toast/index.tsx +2 -2
- package/src/components/toggle-button/index.tsx +5 -7
- package/src/components/tooltip/index.tsx +793 -0
- package/src/components/typography/index.tsx +2 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-back-handler.ts +37 -0
- package/src/icons/index.tsx +147 -0
- package/src/index.ts +116 -0
- package/src/primitives/collapse.tsx +128 -0
- package/src/primitives/keyboard-avoider.tsx +2 -1
- package/src/primitives/text.tsx +28 -1
- package/src/utils/date.ts +446 -0
- package/theme.css +112 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calendar — a month of days, for picking one, several, or a range.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const [day, setDay] = useState<Date>();
|
|
8
|
+
*
|
|
9
|
+
* <Calendar mode="single" selected={day} onSelect={setDay} />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* ## The grid is always six weeks
|
|
13
|
+
*
|
|
14
|
+
* A month needs five rows or six depending on what day it starts on. Drawing
|
|
15
|
+
* only the rows a month uses makes the calendar change height as it is paged
|
|
16
|
+
* through — everything below it jumps, and the days themselves appear to shift
|
|
17
|
+
* between months. So the grid is six rows always, and the spare cells hold the
|
|
18
|
+
* neighbouring months' days.
|
|
19
|
+
*
|
|
20
|
+
* ## How a range is drawn
|
|
21
|
+
*
|
|
22
|
+
* On the web the band under a selected range is one background with the ends
|
|
23
|
+
* rounded by `:first-child` and `:last-child`. There are no pseudo-classes
|
|
24
|
+
* here, so each cell draws its own piece of the band as a view *behind* the
|
|
25
|
+
* number, and works out for itself where that piece has to stop.
|
|
26
|
+
*
|
|
27
|
+
* Two questions, and they are not the same one:
|
|
28
|
+
*
|
|
29
|
+
* - **How wide.** A day between the two ends fills its cell, so the pieces meet
|
|
30
|
+
* and the band reads as continuous. An end fills half its cell, from the
|
|
31
|
+
* centre outwards towards the middle of the range — the half that is not
|
|
32
|
+
* covered by the disc, so the disc appears to sit *on* the band rather than
|
|
33
|
+
* beside it. That is the join the old three-piece version never made, which
|
|
34
|
+
* is why a range used to read as two discs with a gap and a stripe.
|
|
35
|
+
* - **Where it is rounded.** Only where it genuinely stops: at an end of the
|
|
36
|
+
* range, or at the edge of a row, because a range running over a weekend has
|
|
37
|
+
* to close off on Saturday and open again on Sunday rather than trailing into
|
|
38
|
+
* the gap between the two.
|
|
39
|
+
*/
|
|
40
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
41
|
+
import { Pressable, ScrollView, View } from 'react-native';
|
|
42
|
+
import { tv } from 'tailwind-variants';
|
|
43
|
+
import { useCSSVariable } from 'uniwind';
|
|
44
|
+
import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "../../icons/index.js";
|
|
45
|
+
import { Text } from "../../primitives/text.js";
|
|
46
|
+
import { cn } from "../../utils/cn.js";
|
|
47
|
+
import { Popover } from "../popover/index.js";
|
|
48
|
+
import { addCalendarMonths, addDays, calendarDayNumber, calendarLongDate, calendarMonthLabel, calendarMonthNames, calendarParts, isAfter, isBefore, isSameCalendarMonth, isSameDay, isWithin, resolveCalendar, startOfCalendarMonth, startOfDay, weekdayNames } from "../../utils/date.js";
|
|
49
|
+
|
|
50
|
+
/** A range under construction: `to` is undefined between the two taps. */
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* What a `mode` selects. Written as a map so the root, the parts and the
|
|
54
|
+
* caller all read the same association rather than each casting their own way.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Which days cannot be picked. A list, a span, or a rule — a rule because
|
|
59
|
+
* "no weekends" and "not in the past" are the two most common cases and
|
|
60
|
+
* neither can be written as a list that stays right tomorrow.
|
|
61
|
+
*/
|
|
62
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
63
|
+
/**
|
|
64
|
+
* The corner radius of a day, and so of the band that runs between days.
|
|
65
|
+
*
|
|
66
|
+
* A number rather than a class because the band's four corners are set
|
|
67
|
+
* independently from the flags below, which no single `rounded-*` can express.
|
|
68
|
+
*/
|
|
69
|
+
const DAY_RADIUS = 8;
|
|
70
|
+
const calendarVariants = tv({
|
|
71
|
+
slots: {
|
|
72
|
+
root: 'gap-3',
|
|
73
|
+
header: 'h-10 flex-row items-center justify-between gap-2',
|
|
74
|
+
nav: 'h-8 w-8 items-center justify-center rounded-full',
|
|
75
|
+
caption: 'flex-1 flex-row items-center justify-center gap-1'
|
|
76
|
+
},
|
|
77
|
+
variants: {
|
|
78
|
+
/** Draw the calendar's own panel, for one standing on the page. */
|
|
79
|
+
bordered: {
|
|
80
|
+
true: {
|
|
81
|
+
root: 'rounded-3xl border border-border bg-card p-3'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
defaultVariants: {
|
|
86
|
+
bordered: true
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const dayVariants = tv({
|
|
90
|
+
slots: {
|
|
91
|
+
cell: 'h-10 flex-1 items-center justify-center',
|
|
92
|
+
/** The range band, drawn behind the number so it can run cell to cell. */
|
|
93
|
+
band: 'absolute inset-y-0.5 bg-accent',
|
|
94
|
+
/**
|
|
95
|
+
* The fill on a selected day, or the ring on today. Square-ish rather than
|
|
96
|
+
* round: a circle has one point of contact with the band beside it, so a
|
|
97
|
+
* range reads as beads on a string. A rounded rectangle meets the band
|
|
98
|
+
* along its whole edge and the two become one shape.
|
|
99
|
+
*/
|
|
100
|
+
disc: 'h-9 w-9 items-center justify-center rounded-lg',
|
|
101
|
+
label: 'text-sm'
|
|
102
|
+
},
|
|
103
|
+
variants: {
|
|
104
|
+
selected: {
|
|
105
|
+
true: {
|
|
106
|
+
disc: 'bg-primary',
|
|
107
|
+
label: 'text-primary-foreground'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
today: {
|
|
111
|
+
true: {
|
|
112
|
+
disc: 'border border-primary',
|
|
113
|
+
label: 'text-primary'
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
outside: {
|
|
117
|
+
true: {
|
|
118
|
+
label: 'text-muted-foreground/40'
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
disabled: {
|
|
122
|
+
true: {
|
|
123
|
+
label: 'text-muted-foreground/30'
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
/** A ruled-out day inside a range still sits on it, but faintly. */
|
|
127
|
+
fadedBand: {
|
|
128
|
+
true: {
|
|
129
|
+
band: 'opacity-40'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
compoundVariants: [
|
|
134
|
+
// A selected day inside a range keeps the solid fill; the band passes
|
|
135
|
+
// under it rather than the disc losing to it.
|
|
136
|
+
{
|
|
137
|
+
selected: true,
|
|
138
|
+
today: true,
|
|
139
|
+
class: {
|
|
140
|
+
label: 'text-primary-foreground'
|
|
141
|
+
}
|
|
142
|
+
}]
|
|
143
|
+
});
|
|
144
|
+
const CalendarContext = /*#__PURE__*/createContext(null);
|
|
145
|
+
function useCalendar(component) {
|
|
146
|
+
const context = useContext(CalendarContext);
|
|
147
|
+
if (!context) {
|
|
148
|
+
throw new Error(`${component} must be used within a <Calendar>`);
|
|
149
|
+
}
|
|
150
|
+
return context;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Whether a rule, a list or a span rules this day out. */
|
|
154
|
+
function isDisabled(date, disabled, minDate, maxDate) {
|
|
155
|
+
/*
|
|
156
|
+
* Both bounds are compared at local midnight, because they are about days
|
|
157
|
+
* and the caller almost always hands over a moment. `minDate={new Date()}`
|
|
158
|
+
* means "from today", and against a raw timestamp that would rule out every
|
|
159
|
+
* hour of today that has already passed — which is to say, today.
|
|
160
|
+
*/
|
|
161
|
+
if (minDate && isBefore(date, startOfDay(minDate))) return true;
|
|
162
|
+
if (maxDate && isAfter(date, startOfDay(maxDate))) return true;
|
|
163
|
+
if (!disabled) return false;
|
|
164
|
+
if (typeof disabled === 'function') return disabled(date);
|
|
165
|
+
if (Array.isArray(disabled)) return disabled.some(day => isSameDay(day, date));
|
|
166
|
+
return isWithin(date, disabled.from, disabled.to);
|
|
167
|
+
}
|
|
168
|
+
function CalendarRoot({
|
|
169
|
+
className,
|
|
170
|
+
mode = 'single',
|
|
171
|
+
selected,
|
|
172
|
+
defaultSelected,
|
|
173
|
+
onSelect,
|
|
174
|
+
month: monthProp,
|
|
175
|
+
defaultMonth,
|
|
176
|
+
onMonthChange,
|
|
177
|
+
numberOfMonths = 1,
|
|
178
|
+
captionLayout = 'label',
|
|
179
|
+
disabled,
|
|
180
|
+
minDate,
|
|
181
|
+
maxDate,
|
|
182
|
+
startMonth,
|
|
183
|
+
endMonth,
|
|
184
|
+
weekStartsOn = 0,
|
|
185
|
+
showOutsideDays = true,
|
|
186
|
+
selectOutsideDays = false,
|
|
187
|
+
bordered = true,
|
|
188
|
+
locale,
|
|
189
|
+
calendar = 'gregory',
|
|
190
|
+
...props
|
|
191
|
+
}) {
|
|
192
|
+
// Settled once here rather than at each call site, so every part of the
|
|
193
|
+
// grid is certainly reading the same calendar.
|
|
194
|
+
const system = useMemo(() => resolveCalendar(calendar, locale), [calendar, locale]);
|
|
195
|
+
const [internalSelected, setInternalSelected] = useState(defaultSelected);
|
|
196
|
+
const isControlled = selected !== undefined;
|
|
197
|
+
const value = isControlled ? selected : internalSelected;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The month to open on: whatever is selected, so a picker reopened on a date
|
|
201
|
+
* chosen last year does not land on today and make it look unselected.
|
|
202
|
+
*/
|
|
203
|
+
const initialMonth = useMemo(() => {
|
|
204
|
+
if (defaultMonth) return startOfCalendarMonth(defaultMonth, system, locale);
|
|
205
|
+
const first = value instanceof Date ? value : Array.isArray(value) ? value[0] : value && typeof value === 'object' && 'from' in value ? value.from : undefined;
|
|
206
|
+
return startOfCalendarMonth(first ?? new Date(), system, locale);
|
|
207
|
+
// Only the first render decides this; after that the month is its own state.
|
|
208
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
|
+
}, []);
|
|
210
|
+
const [internalMonth, setInternalMonth] = useState(initialMonth);
|
|
211
|
+
const month = monthProp ? startOfCalendarMonth(monthProp, system, locale) : internalMonth;
|
|
212
|
+
const setMonth = useCallback(next => {
|
|
213
|
+
const settled = startOfCalendarMonth(next, system, locale);
|
|
214
|
+
if (!monthProp) setInternalMonth(settled);
|
|
215
|
+
onMonthChange?.(settled);
|
|
216
|
+
}, [monthProp, onMonthChange, system, locale]);
|
|
217
|
+
const commit = useCallback(next => {
|
|
218
|
+
if (!isControlled) setInternalSelected(next);
|
|
219
|
+
onSelect?.(next);
|
|
220
|
+
}, [isControlled, onSelect]);
|
|
221
|
+
const select = useCallback(date => {
|
|
222
|
+
const day = startOfDay(date);
|
|
223
|
+
if (mode === 'multiple') {
|
|
224
|
+
const current = value ?? [];
|
|
225
|
+
const without = current.filter(picked => !isSameDay(picked, day));
|
|
226
|
+
const next = without.length === current.length ? [...current, day] : without;
|
|
227
|
+
commit(next);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (mode === 'range') {
|
|
231
|
+
const current = value;
|
|
232
|
+
/*
|
|
233
|
+
* Three states, not two: no range, half a range, a whole range. A tap
|
|
234
|
+
* with half a range open closes it — unless it landed before the open
|
|
235
|
+
* end, in which case it becomes the new start. Anything else would ask
|
|
236
|
+
* someone who picked the wrong month first to undo before retrying.
|
|
237
|
+
*/
|
|
238
|
+
if (!current || current.to || isBefore(day, current.from)) {
|
|
239
|
+
commit({
|
|
240
|
+
from: day
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
commit({
|
|
244
|
+
from: current.from,
|
|
245
|
+
to: day
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Single. Tapping the selected day again clears it.
|
|
252
|
+
const current = value;
|
|
253
|
+
commit(isSameDay(current, day) ? undefined : day);
|
|
254
|
+
}, [mode, value, commit]);
|
|
255
|
+
const context = useMemo(() => ({
|
|
256
|
+
month,
|
|
257
|
+
setMonth,
|
|
258
|
+
locale,
|
|
259
|
+
system,
|
|
260
|
+
minDate,
|
|
261
|
+
maxDate,
|
|
262
|
+
startMonth,
|
|
263
|
+
endMonth,
|
|
264
|
+
captionLayout
|
|
265
|
+
}), [month, setMonth, locale, system, minDate, maxDate, startMonth, endMonth, captionLayout]);
|
|
266
|
+
const {
|
|
267
|
+
root
|
|
268
|
+
} = calendarVariants({
|
|
269
|
+
bordered
|
|
270
|
+
});
|
|
271
|
+
const months = Math.max(1, numberOfMonths);
|
|
272
|
+
return /*#__PURE__*/_jsx(CalendarContext.Provider, {
|
|
273
|
+
value: context,
|
|
274
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
275
|
+
...props,
|
|
276
|
+
className: cn(root(), className),
|
|
277
|
+
children: Array.from({
|
|
278
|
+
length: months
|
|
279
|
+
}, (_unused, offset) => /*#__PURE__*/_jsxs(View, {
|
|
280
|
+
className: "gap-2",
|
|
281
|
+
children: [/*#__PURE__*/_jsx(CalendarHeader, {
|
|
282
|
+
offset: offset,
|
|
283
|
+
lastOffset: months - 1
|
|
284
|
+
}), /*#__PURE__*/_jsx(CalendarGrid, {
|
|
285
|
+
month: addCalendarMonths(month, offset, system, locale),
|
|
286
|
+
mode: mode,
|
|
287
|
+
value: value,
|
|
288
|
+
onSelect: select,
|
|
289
|
+
disabled: disabled,
|
|
290
|
+
minDate: minDate,
|
|
291
|
+
maxDate: maxDate,
|
|
292
|
+
weekStartsOn: weekStartsOn,
|
|
293
|
+
showOutsideDays: showOutsideDays,
|
|
294
|
+
selectOutsideDays: selectOutsideDays,
|
|
295
|
+
locale: locale,
|
|
296
|
+
system: system
|
|
297
|
+
})]
|
|
298
|
+
}, offset))
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
CalendarRoot.displayName = 'Calendar';
|
|
303
|
+
|
|
304
|
+
/* -------------------------------------------------------------------------- */
|
|
305
|
+
/* Caption */
|
|
306
|
+
/* -------------------------------------------------------------------------- */
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The row over a month: an arrow at each edge and the month's name between
|
|
310
|
+
* them.
|
|
311
|
+
*
|
|
312
|
+
* The arrows are pinned to the edges of the calendar rather than packed around
|
|
313
|
+
* the label, so the two targets are as far apart as the panel allows and the
|
|
314
|
+
* name sits centred over its own grid. With several months shown they are split
|
|
315
|
+
* across the run — back on the first, forward on the last — because both sets
|
|
316
|
+
* on both headers would page the whole run and give two ways to do one thing.
|
|
317
|
+
* The month that owns neither still lays out a spacer, or its name would slide
|
|
318
|
+
* off centre.
|
|
319
|
+
*/
|
|
320
|
+
function CalendarHeader({
|
|
321
|
+
offset = 0,
|
|
322
|
+
lastOffset = 0
|
|
323
|
+
}) {
|
|
324
|
+
const {
|
|
325
|
+
month,
|
|
326
|
+
setMonth,
|
|
327
|
+
locale,
|
|
328
|
+
system,
|
|
329
|
+
minDate,
|
|
330
|
+
maxDate,
|
|
331
|
+
captionLayout
|
|
332
|
+
} = useCalendar('Calendar.Header');
|
|
333
|
+
const {
|
|
334
|
+
header,
|
|
335
|
+
caption
|
|
336
|
+
} = calendarVariants();
|
|
337
|
+
const shown = addCalendarMonths(month, offset, system, locale);
|
|
338
|
+
const canGoBack = !minDate || isBefore(startOfCalendarMonth(minDate, system, locale), month);
|
|
339
|
+
const canGoForward = !maxDate || isAfter(startOfCalendarMonth(maxDate, system, locale), shown);
|
|
340
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
341
|
+
className: header(),
|
|
342
|
+
children: [offset === 0 ? /*#__PURE__*/_jsx(CalendarNav, {
|
|
343
|
+
direction: "previous",
|
|
344
|
+
disabled: !canGoBack,
|
|
345
|
+
onPress: () => setMonth(addCalendarMonths(month, -1, system, locale))
|
|
346
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
347
|
+
className: "h-8 w-8"
|
|
348
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
349
|
+
className: caption(),
|
|
350
|
+
children: captionLayout === 'dropdown' ? /*#__PURE__*/_jsx(CalendarDropdowns, {
|
|
351
|
+
month: shown,
|
|
352
|
+
offset: offset
|
|
353
|
+
}) : /*#__PURE__*/_jsx(Text, {
|
|
354
|
+
weight: "semibold",
|
|
355
|
+
className: "text-base",
|
|
356
|
+
children: calendarMonthLabel(shown, system, locale)
|
|
357
|
+
})
|
|
358
|
+
}), offset === lastOffset ? /*#__PURE__*/_jsx(CalendarNav, {
|
|
359
|
+
direction: "next",
|
|
360
|
+
disabled: !canGoForward,
|
|
361
|
+
onPress: () => setMonth(addCalendarMonths(month, 1, system, locale))
|
|
362
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
363
|
+
className: "h-8 w-8"
|
|
364
|
+
})]
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* One paging arrow.
|
|
369
|
+
*
|
|
370
|
+
* Borderless: two bordered squares at the ends of a bordered panel read as a
|
|
371
|
+
* row of boxes rather than as controls, and the chevron is legible enough on
|
|
372
|
+
* its own. The target is still 32px with another 8 of slop around it.
|
|
373
|
+
*/
|
|
374
|
+
function CalendarNav({
|
|
375
|
+
direction,
|
|
376
|
+
disabled = false,
|
|
377
|
+
onPress
|
|
378
|
+
}) {
|
|
379
|
+
const {
|
|
380
|
+
nav
|
|
381
|
+
} = calendarVariants();
|
|
382
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
383
|
+
disabled: disabled,
|
|
384
|
+
onPress: onPress,
|
|
385
|
+
hitSlop: 8,
|
|
386
|
+
accessibilityRole: "button",
|
|
387
|
+
accessibilityLabel: direction === 'next' ? 'Next month' : 'Previous month',
|
|
388
|
+
accessibilityState: {
|
|
389
|
+
disabled
|
|
390
|
+
},
|
|
391
|
+
className: cn(nav(), disabled ? 'opacity-30' : 'active:bg-accent'),
|
|
392
|
+
children: direction === 'next' ? /*#__PURE__*/_jsx(ChevronRightIcon, {
|
|
393
|
+
size: 18
|
|
394
|
+
}) : /*#__PURE__*/_jsx(ChevronLeftIcon, {
|
|
395
|
+
size: 18
|
|
396
|
+
})
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Month and year as chips that open a panel of options.
|
|
402
|
+
*
|
|
403
|
+
* The panel floats rather than opening in place, and the caption is the reason:
|
|
404
|
+
* it is a fixed-height row, so a list opened inside it has nowhere to go and
|
|
405
|
+
* spills over the grid below. A popover is laid out against the chip and
|
|
406
|
+
* clamped to the screen, which is the behaviour this needs and already has.
|
|
407
|
+
*
|
|
408
|
+
* Native pickers are still the wrong tool — a platform overlay on top of a
|
|
409
|
+
* calendar that is itself already in a popover is two floating layers deep for
|
|
410
|
+
* choosing a number, and it cannot show a Hijri year at all.
|
|
411
|
+
*/
|
|
412
|
+
function CalendarDropdowns({
|
|
413
|
+
month,
|
|
414
|
+
offset
|
|
415
|
+
}) {
|
|
416
|
+
const {
|
|
417
|
+
setMonth,
|
|
418
|
+
locale,
|
|
419
|
+
system,
|
|
420
|
+
minDate,
|
|
421
|
+
maxDate,
|
|
422
|
+
startMonth,
|
|
423
|
+
endMonth
|
|
424
|
+
} = useCalendar('Calendar.Caption');
|
|
425
|
+
|
|
426
|
+
/*
|
|
427
|
+
* Everything here counts in the calendar on screen, not in the platform's.
|
|
428
|
+
* The names come from a real year of that calendar rather than from twelve
|
|
429
|
+
* Gregorian firsts, and the year list is that calendar's years — a Hijri
|
|
430
|
+
* caption offering Gregorian years is choosing from the wrong set.
|
|
431
|
+
*/
|
|
432
|
+
const months = useMemo(() => calendarMonthNames(month, system, locale, 'short'), [month, system, locale]);
|
|
433
|
+
const current = calendarParts(month, system, locale);
|
|
434
|
+
/*
|
|
435
|
+
* `startMonth`/`endMonth` bound the lists, falling back to the selectable
|
|
436
|
+
* bounds and then to a century back and a decade on. They are separate props
|
|
437
|
+
* because the two questions are different: a birthday picker wants a hundred
|
|
438
|
+
* years on offer and no bound at all on the days inside them.
|
|
439
|
+
*/
|
|
440
|
+
const first = startMonth ?? minDate;
|
|
441
|
+
const last = endMonth ?? maxDate;
|
|
442
|
+
const bounds = {
|
|
443
|
+
from: first ? calendarParts(first, system, locale).year : current.year - 100,
|
|
444
|
+
to: last ? calendarParts(last, system, locale).year : current.year + 10
|
|
445
|
+
};
|
|
446
|
+
/*
|
|
447
|
+
* Oldest first, the way a year runs. The list is long — a century by default
|
|
448
|
+
* — so it is opened scrolled to the year already in the caption rather than
|
|
449
|
+
* to either end, which is what makes the natural order affordable. Counting
|
|
450
|
+
* backwards was a way of putting the likely years at the top of a list that
|
|
451
|
+
* always opened at the top; it is not needed once the list opens in the right
|
|
452
|
+
* place, and it made every other use of the picker read upside down.
|
|
453
|
+
*/
|
|
454
|
+
const years = Array.from({
|
|
455
|
+
length: Math.max(1, bounds.to - bounds.from + 1)
|
|
456
|
+
}, (_unused, index) => bounds.from + index);
|
|
457
|
+
|
|
458
|
+
/*
|
|
459
|
+
* Navigated by months rather than by building a date, because there is no
|
|
460
|
+
* `new Date(hijriYear, hijriMonth)`. A Hijri year is always twelve months,
|
|
461
|
+
* so a year jump is twelve of them.
|
|
462
|
+
*/
|
|
463
|
+
const choose = months_ => {
|
|
464
|
+
setMonth(addCalendarMonths(month, months_ - offset, system, locale));
|
|
465
|
+
};
|
|
466
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
467
|
+
children: [/*#__PURE__*/_jsx(CaptionDropdown, {
|
|
468
|
+
label: months[current.month - 1] ?? String(current.month),
|
|
469
|
+
accessibilityLabel: "Month",
|
|
470
|
+
options: months,
|
|
471
|
+
active: current.month - 1,
|
|
472
|
+
width: MONTH_LIST_WIDTH,
|
|
473
|
+
onSelect: index => choose(index - (current.month - 1))
|
|
474
|
+
}), /*#__PURE__*/_jsx(CaptionDropdown, {
|
|
475
|
+
label: String(current.year),
|
|
476
|
+
accessibilityLabel: "Year",
|
|
477
|
+
options: years.map(String),
|
|
478
|
+
active: years.indexOf(current.year),
|
|
479
|
+
width: YEAR_LIST_WIDTH,
|
|
480
|
+
onSelect: index => choose((years[index] - current.year) * 12)
|
|
481
|
+
})]
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Panel widths. A month name needs the room; a four-digit year does not. */
|
|
486
|
+
const MONTH_LIST_WIDTH = 176;
|
|
487
|
+
const YEAR_LIST_WIDTH = 132;
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Height of one option row, and the tallest the list is allowed to be.
|
|
491
|
+
*
|
|
492
|
+
* The row height is a constant rather than a measurement because it is what
|
|
493
|
+
* turns "the twenty-ninth option" into a scroll offset, and that has to be
|
|
494
|
+
* known before the list has laid out — the whole point is that it opens in the
|
|
495
|
+
* right place rather than scrolling there afterwards.
|
|
496
|
+
*/
|
|
497
|
+
const OPTION_HEIGHT = 40;
|
|
498
|
+
const LIST_MAX_HEIGHT = OPTION_HEIGHT * 6.5;
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* One chip and the list of options it opens.
|
|
502
|
+
*
|
|
503
|
+
* A list, not a grid of chips. Twelve months tile into a panel well enough, but
|
|
504
|
+
* the same treatment applied to a century of years is a wall of four-digit
|
|
505
|
+
* numbers with no order the eye can follow — and it opened at whichever end
|
|
506
|
+
* happened to be first. Rows in a column read as a list, take a checkmark, and
|
|
507
|
+
* can be scrolled to a particular one.
|
|
508
|
+
*/
|
|
509
|
+
function CaptionDropdown({
|
|
510
|
+
label,
|
|
511
|
+
accessibilityLabel,
|
|
512
|
+
options,
|
|
513
|
+
active,
|
|
514
|
+
width,
|
|
515
|
+
onSelect
|
|
516
|
+
}) {
|
|
517
|
+
const [open, setOpen] = useState(false);
|
|
518
|
+
const list = useRef(null);
|
|
519
|
+
/*
|
|
520
|
+
* The check is given a colour rather than left to its own fallback, which is
|
|
521
|
+
* white — right inside a filled control, invisible on this panel.
|
|
522
|
+
*/
|
|
523
|
+
const rawTick = useCSSVariable('--color-foreground');
|
|
524
|
+
const tick = typeof rawTick === 'string' ? rawTick : undefined;
|
|
525
|
+
|
|
526
|
+
/*
|
|
527
|
+
* Opened on the option already in the caption, centred in the panel rather
|
|
528
|
+
* than pinned to an edge so the years either side of it are in view too. A
|
|
529
|
+
* hundred-item list that opens at the top is showing the one year nobody came
|
|
530
|
+
* to pick, and asks for a flick through a century to reach the one they did.
|
|
531
|
+
*/
|
|
532
|
+
const scrollToActive = useCallback(() => {
|
|
533
|
+
if (active < 0) return;
|
|
534
|
+
const centred = active * OPTION_HEIGHT - LIST_MAX_HEIGHT / 2 + OPTION_HEIGHT / 2;
|
|
535
|
+
list.current?.scrollTo({
|
|
536
|
+
y: Math.max(0, centred),
|
|
537
|
+
animated: false
|
|
538
|
+
});
|
|
539
|
+
}, [active]);
|
|
540
|
+
return /*#__PURE__*/_jsxs(Popover, {
|
|
541
|
+
open: open,
|
|
542
|
+
onOpenChange: setOpen,
|
|
543
|
+
children: [/*#__PURE__*/_jsx(Popover.Trigger, {
|
|
544
|
+
children: /*#__PURE__*/_jsx(CaptionChip, {
|
|
545
|
+
label: label,
|
|
546
|
+
expanded: open,
|
|
547
|
+
accessibilityLabel: accessibilityLabel
|
|
548
|
+
})
|
|
549
|
+
}), /*#__PURE__*/_jsx(Popover.Content, {
|
|
550
|
+
align: "center",
|
|
551
|
+
width: width,
|
|
552
|
+
className: "p-1.5",
|
|
553
|
+
children: /*#__PURE__*/_jsx(ScrollView, {
|
|
554
|
+
ref: list,
|
|
555
|
+
bounces: false,
|
|
556
|
+
showsVerticalScrollIndicator: false,
|
|
557
|
+
style: {
|
|
558
|
+
maxHeight: LIST_MAX_HEIGHT
|
|
559
|
+
}
|
|
560
|
+
// Fires once the rows exist, which is the first moment there is
|
|
561
|
+
// anything to scroll. Doing it on mount would scroll an empty list.
|
|
562
|
+
,
|
|
563
|
+
onContentSizeChange: scrollToActive,
|
|
564
|
+
children: options.map((option, index) => {
|
|
565
|
+
const selected = index === active;
|
|
566
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
567
|
+
onPress: () => {
|
|
568
|
+
onSelect(index);
|
|
569
|
+
setOpen(false);
|
|
570
|
+
},
|
|
571
|
+
accessibilityRole: "button",
|
|
572
|
+
accessibilityState: {
|
|
573
|
+
selected
|
|
574
|
+
},
|
|
575
|
+
style: {
|
|
576
|
+
height: OPTION_HEIGHT
|
|
577
|
+
},
|
|
578
|
+
className: cn('flex-row items-center justify-between gap-2 rounded-xl px-3', selected ? 'bg-accent' : 'active:bg-accent'),
|
|
579
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
580
|
+
size: "sm",
|
|
581
|
+
weight: selected ? 'semibold' : 'normal',
|
|
582
|
+
children: option
|
|
583
|
+
}), selected ? /*#__PURE__*/_jsx(CheckIcon, {
|
|
584
|
+
size: 16,
|
|
585
|
+
color: tick
|
|
586
|
+
}) : null]
|
|
587
|
+
}, option);
|
|
588
|
+
})
|
|
589
|
+
})
|
|
590
|
+
})]
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function CaptionChip({
|
|
594
|
+
label,
|
|
595
|
+
expanded,
|
|
596
|
+
accessibilityLabel,
|
|
597
|
+
onPress
|
|
598
|
+
}) {
|
|
599
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
600
|
+
onPress: onPress,
|
|
601
|
+
accessibilityRole: "button",
|
|
602
|
+
accessibilityLabel: accessibilityLabel,
|
|
603
|
+
accessibilityState: {
|
|
604
|
+
expanded
|
|
605
|
+
},
|
|
606
|
+
className: "flex-row items-center gap-1 rounded-lg px-2 py-1.5 active:bg-accent",
|
|
607
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
608
|
+
weight: "semibold",
|
|
609
|
+
children: label
|
|
610
|
+
}), /*#__PURE__*/_jsx(ChevronDownIcon, {
|
|
611
|
+
size: 14
|
|
612
|
+
})]
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* -------------------------------------------------------------------------- */
|
|
617
|
+
/* Grid */
|
|
618
|
+
/* -------------------------------------------------------------------------- */
|
|
619
|
+
|
|
620
|
+
function CalendarGrid({
|
|
621
|
+
month,
|
|
622
|
+
mode,
|
|
623
|
+
value,
|
|
624
|
+
onSelect,
|
|
625
|
+
disabled,
|
|
626
|
+
minDate,
|
|
627
|
+
maxDate,
|
|
628
|
+
weekStartsOn,
|
|
629
|
+
showOutsideDays,
|
|
630
|
+
selectOutsideDays = false,
|
|
631
|
+
locale,
|
|
632
|
+
system
|
|
633
|
+
}) {
|
|
634
|
+
/*
|
|
635
|
+
* Six rows of consecutive days, anchored on the first of *this* calendar's
|
|
636
|
+
* month. Weekdays are shared between the two calendars — only the month
|
|
637
|
+
* boundaries and the day numbers differ — so the walk itself is the same
|
|
638
|
+
* either way and only where it starts moves.
|
|
639
|
+
*/
|
|
640
|
+
const weeks = useMemo(() => {
|
|
641
|
+
const first = startOfCalendarMonth(month, system, locale);
|
|
642
|
+
const lead = (first.getDay() - weekStartsOn + 7) % 7;
|
|
643
|
+
const start = addDays(first, -lead);
|
|
644
|
+
return Array.from({
|
|
645
|
+
length: 6
|
|
646
|
+
}, (_unusedWeek, week) => Array.from({
|
|
647
|
+
length: 7
|
|
648
|
+
}, (_unusedDay, day) => addDays(start, week * 7 + day)));
|
|
649
|
+
}, [month, weekStartsOn, system, locale]);
|
|
650
|
+
const headings = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
|
|
651
|
+
const today = startOfDay(new Date());
|
|
652
|
+
const range = mode === 'range' ? value : undefined;
|
|
653
|
+
const multiple = mode === 'multiple' ? value ?? [] : [];
|
|
654
|
+
const single = mode === 'single' ? value : undefined;
|
|
655
|
+
return (
|
|
656
|
+
/*#__PURE__*/
|
|
657
|
+
// React Native has no `grid` role, so the month announces itself as a list
|
|
658
|
+
// of days and each day carries its own full date as its label.
|
|
659
|
+
_jsxs(View, {
|
|
660
|
+
accessibilityRole: "list",
|
|
661
|
+
className: "gap-1",
|
|
662
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
663
|
+
className: "flex-row",
|
|
664
|
+
children: headings.map(heading => /*#__PURE__*/_jsx(Text, {
|
|
665
|
+
size: "xs",
|
|
666
|
+
muted: true,
|
|
667
|
+
className: "flex-1 text-center",
|
|
668
|
+
children: heading
|
|
669
|
+
}, heading))
|
|
670
|
+
}), weeks.map((week, weekIndex) => /*#__PURE__*/_jsx(View, {
|
|
671
|
+
className: "flex-row",
|
|
672
|
+
children: week.map((date, dayIndex) => {
|
|
673
|
+
const outside = !isSameCalendarMonth(date, month, system, locale);
|
|
674
|
+
if (outside && !showOutsideDays) {
|
|
675
|
+
// A spacer, not nothing: the row has to stay seven wide or the
|
|
676
|
+
// columns stop lining up with their headings.
|
|
677
|
+
return /*#__PURE__*/_jsx(View, {
|
|
678
|
+
className: "h-10 flex-1"
|
|
679
|
+
}, dayIndex);
|
|
680
|
+
}
|
|
681
|
+
const isStart = mode === 'range' && isSameDay(date, range?.from);
|
|
682
|
+
const isEnd = mode === 'range' && isSameDay(date, range?.to);
|
|
683
|
+
const selected = mode === 'range' ? isStart || isEnd : mode === 'multiple' ? multiple.some(picked => isSameDay(picked, date)) : isSameDay(date, single);
|
|
684
|
+
|
|
685
|
+
/*
|
|
686
|
+
* Inclusive of both ends, unlike the old middles-only rule. An end
|
|
687
|
+
* has to carry its half of the band or there is a gap between the
|
|
688
|
+
* disc and the run beside it — which is precisely what made a range
|
|
689
|
+
* read as two discs with a stripe floating between them.
|
|
690
|
+
*/
|
|
691
|
+
const inRange = !!range?.to && isWithin(date, range.from, range.to);
|
|
692
|
+
|
|
693
|
+
/*
|
|
694
|
+
* Whether the band carries on past this cell's edge into the one
|
|
695
|
+
* beside it. It stops at an end of the range, and at the edge of a
|
|
696
|
+
* row — a range over a weekend closes on Saturday and opens again
|
|
697
|
+
* on Sunday rather than trailing into the gap between the rows.
|
|
698
|
+
*/
|
|
699
|
+
const openStart = inRange && !isStart && dayIndex > 0;
|
|
700
|
+
const openEnd = inRange && !isEnd && dayIndex < 6;
|
|
701
|
+
|
|
702
|
+
// A neighbouring month's day is drawn, not offered. Reaching into
|
|
703
|
+
// July from June is what the arrows are for.
|
|
704
|
+
const ruledOut = isDisabled(date, disabled, minDate, maxDate) || outside && !selectOutsideDays;
|
|
705
|
+
return /*#__PURE__*/_jsx(CalendarDay, {
|
|
706
|
+
date: date,
|
|
707
|
+
outside: outside,
|
|
708
|
+
selected: selected,
|
|
709
|
+
inRange: inRange,
|
|
710
|
+
rangeStart: isStart,
|
|
711
|
+
rangeEnd: isEnd,
|
|
712
|
+
today: isSameDay(date, today),
|
|
713
|
+
disabled: ruledOut,
|
|
714
|
+
openStart: openStart,
|
|
715
|
+
openEnd: openEnd,
|
|
716
|
+
locale: locale,
|
|
717
|
+
system: system,
|
|
718
|
+
onPress: () => onSelect(date)
|
|
719
|
+
}, dayIndex);
|
|
720
|
+
})
|
|
721
|
+
}, weekIndex))]
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
function CalendarDay({
|
|
726
|
+
date,
|
|
727
|
+
outside,
|
|
728
|
+
selected,
|
|
729
|
+
inRange,
|
|
730
|
+
rangeStart = false,
|
|
731
|
+
rangeEnd = false,
|
|
732
|
+
today,
|
|
733
|
+
disabled,
|
|
734
|
+
openStart,
|
|
735
|
+
openEnd,
|
|
736
|
+
locale,
|
|
737
|
+
system,
|
|
738
|
+
onPress
|
|
739
|
+
}) {
|
|
740
|
+
const styles = dayVariants({
|
|
741
|
+
selected,
|
|
742
|
+
today: today && !selected,
|
|
743
|
+
outside,
|
|
744
|
+
disabled,
|
|
745
|
+
fadedBand: disabled && inRange
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
/*
|
|
749
|
+
* An end of the range fills half its cell, from the centre outwards towards
|
|
750
|
+
* the rest of the range; every other day in it fills the whole cell. Half a
|
|
751
|
+
* cell is what puts the band underneath the disc rather than beside it — the
|
|
752
|
+
* disc is centred, so a band starting at the centre emerges from behind it
|
|
753
|
+
* and the two read as one shape. A range of one day gets both halves and so
|
|
754
|
+
* no band at all, which is right: there is nothing to span.
|
|
755
|
+
*/
|
|
756
|
+
const bandStyle = inRange ? {
|
|
757
|
+
left: rangeStart ? '50%' : 0,
|
|
758
|
+
right: rangeEnd ? '50%' : 0,
|
|
759
|
+
borderTopLeftRadius: openStart ? 0 : DAY_RADIUS,
|
|
760
|
+
borderBottomLeftRadius: openStart ? 0 : DAY_RADIUS,
|
|
761
|
+
borderTopRightRadius: openEnd ? 0 : DAY_RADIUS,
|
|
762
|
+
borderBottomRightRadius: openEnd ? 0 : DAY_RADIUS
|
|
763
|
+
} : null;
|
|
764
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
765
|
+
disabled: disabled,
|
|
766
|
+
onPress: onPress,
|
|
767
|
+
accessibilityRole: "button",
|
|
768
|
+
accessibilityLabel: calendarLongDate(date, system, locale),
|
|
769
|
+
accessibilityState: {
|
|
770
|
+
selected,
|
|
771
|
+
disabled
|
|
772
|
+
},
|
|
773
|
+
className: styles.cell(),
|
|
774
|
+
children: [bandStyle ? /*#__PURE__*/_jsx(View, {
|
|
775
|
+
className: styles.band(),
|
|
776
|
+
style: bandStyle
|
|
777
|
+
}) : null, /*#__PURE__*/_jsx(View, {
|
|
778
|
+
className: styles.disc(),
|
|
779
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
780
|
+
className: styles.label(),
|
|
781
|
+
children: calendarDayNumber(date, system, locale)
|
|
782
|
+
})
|
|
783
|
+
})]
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
export const Calendar = Object.assign(CalendarRoot, {
|
|
787
|
+
Header: CalendarHeader,
|
|
788
|
+
Nav: CalendarNav,
|
|
789
|
+
Grid: CalendarGrid,
|
|
790
|
+
Day: CalendarDay
|
|
791
|
+
});
|
|
792
|
+
//# sourceMappingURL=index.js.map
|