panelui-native 0.28.1 → 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/calendar/index.js +350 -115
- package/lib/module/components/calendar/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 +8 -1
- package/lib/module/components/date-picker/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/plan/index.js +296 -0
- package/lib/module/components/plan/index.js.map +1 -0
- package/lib/module/components/popover/index.js +35 -4
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +13 -8
- 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/sources/index.js +221 -0
- package/lib/module/components/sources/index.js.map +1 -0
- 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/tooltip/index.js +179 -23
- package/lib/module/components/tooltip/index.js.map +1 -1
- package/lib/module/icons/index.js +139 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +8 -0
- 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/typescript/src/components/calendar/index.d.ts +68 -3
- package/lib/typescript/src/components/calendar/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 +7 -1
- package/lib/typescript/src/components/date-picker/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/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/sources/index.d.ts +85 -0
- package/lib/typescript/src/components/sources/index.d.ts.map +1 -0
- 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/tooltip/index.d.ts +63 -8
- package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts +14 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +10 -2
- 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/package.json +1 -1
- package/src/components/calendar/index.tsx +389 -122
- 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 +19 -1
- package/src/components/menu/index.tsx +812 -0
- package/src/components/plan/index.tsx +325 -0
- package/src/components/popover/index.tsx +69 -2
- package/src/components/radio-group/index.tsx +11 -2
- package/src/components/reasoning/index.tsx +297 -0
- package/src/components/sources/index.tsx +278 -0
- package/src/components/task/index.tsx +329 -0
- package/src/components/textarea/index.tsx +381 -0
- package/src/components/tooltip/index.tsx +203 -20
- package/src/icons/index.tsx +102 -0
- package/src/index.ts +71 -0
- package/src/primitives/collapse.tsx +128 -0
- package/theme.css +112 -0
|
@@ -17,24 +17,34 @@
|
|
|
17
17
|
* between months. So the grid is six rows always, and the spare cells hold the
|
|
18
18
|
* neighbouring months' days.
|
|
19
19
|
*
|
|
20
|
-
* ##
|
|
20
|
+
* ## How a range is drawn
|
|
21
21
|
*
|
|
22
22
|
* On the web the band under a selected range is one background with the ends
|
|
23
23
|
* rounded by `:first-child` and `:last-child`. There are no pseudo-classes
|
|
24
|
-
* here, so each
|
|
25
|
-
*
|
|
26
|
-
* range that runs over a weekend has to close off on Saturday and open again
|
|
27
|
-
* on Sunday rather than trailing into the gap.
|
|
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.
|
|
28
26
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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.
|
|
31
39
|
*/
|
|
32
|
-
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
33
|
-
import { Pressable, View } from 'react-native';
|
|
40
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
41
|
+
import { Pressable, ScrollView, View } from 'react-native';
|
|
34
42
|
import { tv } from 'tailwind-variants';
|
|
35
|
-
import {
|
|
43
|
+
import { useCSSVariable } from 'uniwind';
|
|
44
|
+
import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "../../icons/index.js";
|
|
36
45
|
import { Text } from "../../primitives/text.js";
|
|
37
46
|
import { cn } from "../../utils/cn.js";
|
|
47
|
+
import { Popover } from "../popover/index.js";
|
|
38
48
|
import { addCalendarMonths, addDays, calendarDayNumber, calendarLongDate, calendarMonthLabel, calendarMonthNames, calendarParts, isAfter, isBefore, isSameCalendarMonth, isSameDay, isWithin, resolveCalendar, startOfCalendarMonth, startOfDay, weekdayNames } from "../../utils/date.js";
|
|
39
49
|
|
|
40
50
|
/** A range under construction: `to` is undefined between the two taps. */
|
|
@@ -49,15 +59,46 @@ import { addCalendarMonths, addDays, calendarDayNumber, calendarLongDate, calend
|
|
|
49
59
|
* "no weekends" and "not in the past" are the two most common cases and
|
|
50
60
|
* neither can be written as a list that stays right tomorrow.
|
|
51
61
|
*/
|
|
52
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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
|
+
});
|
|
53
89
|
const dayVariants = tv({
|
|
54
90
|
slots: {
|
|
55
|
-
cell: 'flex-1 items-center justify-center',
|
|
91
|
+
cell: 'h-10 flex-1 items-center justify-center',
|
|
56
92
|
/** The range band, drawn behind the number so it can run cell to cell. */
|
|
57
|
-
band: 'absolute inset-y-
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
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'
|
|
61
102
|
},
|
|
62
103
|
variants: {
|
|
63
104
|
selected: {
|
|
@@ -82,14 +123,15 @@ const dayVariants = tv({
|
|
|
82
123
|
label: 'text-muted-foreground/30'
|
|
83
124
|
}
|
|
84
125
|
},
|
|
85
|
-
|
|
126
|
+
/** A ruled-out day inside a range still sits on it, but faintly. */
|
|
127
|
+
fadedBand: {
|
|
86
128
|
true: {
|
|
87
|
-
band: '
|
|
129
|
+
band: 'opacity-40'
|
|
88
130
|
}
|
|
89
131
|
}
|
|
90
132
|
},
|
|
91
133
|
compoundVariants: [
|
|
92
|
-
// A selected day inside a range keeps the solid
|
|
134
|
+
// A selected day inside a range keeps the solid fill; the band passes
|
|
93
135
|
// under it rather than the disc losing to it.
|
|
94
136
|
{
|
|
95
137
|
selected: true,
|
|
@@ -110,8 +152,14 @@ function useCalendar(component) {
|
|
|
110
152
|
|
|
111
153
|
/** Whether a rule, a list or a span rules this day out. */
|
|
112
154
|
function isDisabled(date, disabled, minDate, maxDate) {
|
|
113
|
-
|
|
114
|
-
|
|
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;
|
|
115
163
|
if (!disabled) return false;
|
|
116
164
|
if (typeof disabled === 'function') return disabled(date);
|
|
117
165
|
if (Array.isArray(disabled)) return disabled.some(day => isSameDay(day, date));
|
|
@@ -131,8 +179,12 @@ function CalendarRoot({
|
|
|
131
179
|
disabled,
|
|
132
180
|
minDate,
|
|
133
181
|
maxDate,
|
|
182
|
+
startMonth,
|
|
183
|
+
endMonth,
|
|
134
184
|
weekStartsOn = 0,
|
|
135
185
|
showOutsideDays = true,
|
|
186
|
+
selectOutsideDays = false,
|
|
187
|
+
bordered = true,
|
|
136
188
|
locale,
|
|
137
189
|
calendar = 'gregory',
|
|
138
190
|
...props
|
|
@@ -207,20 +259,28 @@ function CalendarRoot({
|
|
|
207
259
|
system,
|
|
208
260
|
minDate,
|
|
209
261
|
maxDate,
|
|
262
|
+
startMonth,
|
|
263
|
+
endMonth,
|
|
210
264
|
captionLayout
|
|
211
|
-
}), [month, setMonth, locale, system, minDate, maxDate, 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);
|
|
212
272
|
return /*#__PURE__*/_jsx(CalendarContext.Provider, {
|
|
213
273
|
value: context,
|
|
214
274
|
children: /*#__PURE__*/_jsx(View, {
|
|
215
275
|
...props,
|
|
216
|
-
className: cn(
|
|
276
|
+
className: cn(root(), className),
|
|
217
277
|
children: Array.from({
|
|
218
|
-
length:
|
|
278
|
+
length: months
|
|
219
279
|
}, (_unused, offset) => /*#__PURE__*/_jsxs(View, {
|
|
220
280
|
className: "gap-2",
|
|
221
|
-
children: [/*#__PURE__*/_jsx(
|
|
281
|
+
children: [/*#__PURE__*/_jsx(CalendarHeader, {
|
|
222
282
|
offset: offset,
|
|
223
|
-
lastOffset:
|
|
283
|
+
lastOffset: months - 1
|
|
224
284
|
}), /*#__PURE__*/_jsx(CalendarGrid, {
|
|
225
285
|
month: addCalendarMonths(month, offset, system, locale),
|
|
226
286
|
mode: mode,
|
|
@@ -231,6 +291,7 @@ function CalendarRoot({
|
|
|
231
291
|
maxDate: maxDate,
|
|
232
292
|
weekStartsOn: weekStartsOn,
|
|
233
293
|
showOutsideDays: showOutsideDays,
|
|
294
|
+
selectOutsideDays: selectOutsideDays,
|
|
234
295
|
locale: locale,
|
|
235
296
|
system: system
|
|
236
297
|
})]
|
|
@@ -245,15 +306,20 @@ CalendarRoot.displayName = 'Calendar';
|
|
|
245
306
|
/* -------------------------------------------------------------------------- */
|
|
246
307
|
|
|
247
308
|
/**
|
|
248
|
-
* The month
|
|
309
|
+
* The row over a month: an arrow at each edge and the month's name between
|
|
310
|
+
* them.
|
|
249
311
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
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.
|
|
253
319
|
*/
|
|
254
|
-
function
|
|
255
|
-
offset,
|
|
256
|
-
lastOffset
|
|
320
|
+
function CalendarHeader({
|
|
321
|
+
offset = 0,
|
|
322
|
+
lastOffset = 0
|
|
257
323
|
}) {
|
|
258
324
|
const {
|
|
259
325
|
month,
|
|
@@ -263,38 +329,56 @@ function CalendarCaption({
|
|
|
263
329
|
minDate,
|
|
264
330
|
maxDate,
|
|
265
331
|
captionLayout
|
|
266
|
-
} = useCalendar('Calendar.
|
|
332
|
+
} = useCalendar('Calendar.Header');
|
|
333
|
+
const {
|
|
334
|
+
header,
|
|
335
|
+
caption
|
|
336
|
+
} = calendarVariants();
|
|
267
337
|
const shown = addCalendarMonths(month, offset, system, locale);
|
|
268
338
|
const canGoBack = !minDate || isBefore(startOfCalendarMonth(minDate, system, locale), month);
|
|
269
339
|
const canGoForward = !maxDate || isAfter(startOfCalendarMonth(maxDate, system, locale), shown);
|
|
270
340
|
return /*#__PURE__*/_jsxs(View, {
|
|
271
|
-
className:
|
|
341
|
+
className: header(),
|
|
272
342
|
children: [offset === 0 ? /*#__PURE__*/_jsx(CalendarNav, {
|
|
273
343
|
direction: "previous",
|
|
274
344
|
disabled: !canGoBack,
|
|
275
345
|
onPress: () => setMonth(addCalendarMonths(month, -1, system, locale))
|
|
276
346
|
}) : /*#__PURE__*/_jsx(View, {
|
|
277
|
-
className: "h-
|
|
278
|
-
}),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
})
|
|
284
358
|
}), offset === lastOffset ? /*#__PURE__*/_jsx(CalendarNav, {
|
|
285
359
|
direction: "next",
|
|
286
360
|
disabled: !canGoForward,
|
|
287
361
|
onPress: () => setMonth(addCalendarMonths(month, 1, system, locale))
|
|
288
362
|
}) : /*#__PURE__*/_jsx(View, {
|
|
289
|
-
className: "h-
|
|
363
|
+
className: "h-8 w-8"
|
|
290
364
|
})]
|
|
291
365
|
});
|
|
292
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
|
+
*/
|
|
293
374
|
function CalendarNav({
|
|
294
375
|
direction,
|
|
295
|
-
disabled,
|
|
376
|
+
disabled = false,
|
|
296
377
|
onPress
|
|
297
378
|
}) {
|
|
379
|
+
const {
|
|
380
|
+
nav
|
|
381
|
+
} = calendarVariants();
|
|
298
382
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
299
383
|
disabled: disabled,
|
|
300
384
|
onPress: onPress,
|
|
@@ -304,21 +388,26 @@ function CalendarNav({
|
|
|
304
388
|
accessibilityState: {
|
|
305
389
|
disabled
|
|
306
390
|
},
|
|
307
|
-
className: cn(
|
|
391
|
+
className: cn(nav(), disabled ? 'opacity-30' : 'active:bg-accent'),
|
|
308
392
|
children: direction === 'next' ? /*#__PURE__*/_jsx(ChevronRightIcon, {
|
|
309
|
-
size:
|
|
393
|
+
size: 18
|
|
310
394
|
}) : /*#__PURE__*/_jsx(ChevronLeftIcon, {
|
|
311
|
-
size:
|
|
395
|
+
size: 18
|
|
312
396
|
})
|
|
313
397
|
});
|
|
314
398
|
}
|
|
315
399
|
|
|
316
400
|
/**
|
|
317
|
-
* Month and year as
|
|
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.
|
|
318
407
|
*
|
|
319
|
-
*
|
|
320
|
-
* itself already
|
|
321
|
-
*
|
|
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.
|
|
322
411
|
*/
|
|
323
412
|
function CalendarDropdowns({
|
|
324
413
|
month,
|
|
@@ -329,9 +418,10 @@ function CalendarDropdowns({
|
|
|
329
418
|
locale,
|
|
330
419
|
system,
|
|
331
420
|
minDate,
|
|
332
|
-
maxDate
|
|
421
|
+
maxDate,
|
|
422
|
+
startMonth,
|
|
423
|
+
endMonth
|
|
333
424
|
} = useCalendar('Calendar.Caption');
|
|
334
|
-
const [open, setOpen] = useState(null);
|
|
335
425
|
|
|
336
426
|
/*
|
|
337
427
|
* Everything here counts in the calendar on screen, not in the platform's.
|
|
@@ -341,10 +431,26 @@ function CalendarDropdowns({
|
|
|
341
431
|
*/
|
|
342
432
|
const months = useMemo(() => calendarMonthNames(month, system, locale, 'short'), [month, system, locale]);
|
|
343
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;
|
|
344
442
|
const bounds = {
|
|
345
|
-
from:
|
|
346
|
-
to:
|
|
443
|
+
from: first ? calendarParts(first, system, locale).year : current.year - 100,
|
|
444
|
+
to: last ? calendarParts(last, system, locale).year : current.year + 10
|
|
347
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
|
+
*/
|
|
348
454
|
const years = Array.from({
|
|
349
455
|
length: Math.max(1, bounds.to - bounds.from + 1)
|
|
350
456
|
}, (_unused, index) => bounds.from + index);
|
|
@@ -356,58 +462,154 @@ function CalendarDropdowns({
|
|
|
356
462
|
*/
|
|
357
463
|
const choose = months_ => {
|
|
358
464
|
setMonth(addCalendarMonths(month, months_ - offset, system, locale));
|
|
359
|
-
setOpen(null);
|
|
360
465
|
};
|
|
361
|
-
return /*#__PURE__*/_jsxs(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
+
})
|
|
391
589
|
})
|
|
392
|
-
})
|
|
590
|
+
})]
|
|
393
591
|
});
|
|
394
592
|
}
|
|
395
593
|
function CaptionChip({
|
|
396
594
|
label,
|
|
397
595
|
expanded,
|
|
596
|
+
accessibilityLabel,
|
|
398
597
|
onPress
|
|
399
598
|
}) {
|
|
400
|
-
return /*#__PURE__*/
|
|
599
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
401
600
|
onPress: onPress,
|
|
402
601
|
accessibilityRole: "button",
|
|
602
|
+
accessibilityLabel: accessibilityLabel,
|
|
403
603
|
accessibilityState: {
|
|
404
604
|
expanded
|
|
405
605
|
},
|
|
406
|
-
className: "flex-row items-center gap-1 rounded-
|
|
407
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
606
|
+
className: "flex-row items-center gap-1 rounded-lg px-2 py-1.5 active:bg-accent",
|
|
607
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
408
608
|
weight: "semibold",
|
|
409
609
|
children: label
|
|
410
|
-
})
|
|
610
|
+
}), /*#__PURE__*/_jsx(ChevronDownIcon, {
|
|
611
|
+
size: 14
|
|
612
|
+
})]
|
|
411
613
|
});
|
|
412
614
|
}
|
|
413
615
|
|
|
@@ -425,6 +627,7 @@ function CalendarGrid({
|
|
|
425
627
|
maxDate,
|
|
426
628
|
weekStartsOn,
|
|
427
629
|
showOutsideDays,
|
|
630
|
+
selectOutsideDays = false,
|
|
428
631
|
locale,
|
|
429
632
|
system
|
|
430
633
|
}) {
|
|
@@ -475,22 +678,41 @@ function CalendarGrid({
|
|
|
475
678
|
className: "h-10 flex-1"
|
|
476
679
|
}, dayIndex);
|
|
477
680
|
}
|
|
478
|
-
const
|
|
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;
|
|
479
701
|
|
|
480
|
-
//
|
|
481
|
-
|
|
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;
|
|
482
705
|
return /*#__PURE__*/_jsx(CalendarDay, {
|
|
483
706
|
date: date,
|
|
484
707
|
outside: outside,
|
|
485
708
|
selected: selected,
|
|
486
709
|
inRange: inRange,
|
|
710
|
+
rangeStart: isStart,
|
|
711
|
+
rangeEnd: isEnd,
|
|
487
712
|
today: isSameDay(date, today),
|
|
488
|
-
disabled:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
,
|
|
492
|
-
openStart: inRange && dayIndex > 0,
|
|
493
|
-
openEnd: inRange && dayIndex < 6,
|
|
713
|
+
disabled: ruledOut,
|
|
714
|
+
openStart: openStart,
|
|
715
|
+
openEnd: openEnd,
|
|
494
716
|
locale: locale,
|
|
495
717
|
system: system,
|
|
496
718
|
onPress: () => onSelect(date)
|
|
@@ -505,6 +727,8 @@ function CalendarDay({
|
|
|
505
727
|
outside,
|
|
506
728
|
selected,
|
|
507
729
|
inRange,
|
|
730
|
+
rangeStart = false,
|
|
731
|
+
rangeEnd = false,
|
|
508
732
|
today,
|
|
509
733
|
disabled,
|
|
510
734
|
openStart,
|
|
@@ -518,8 +742,25 @@ function CalendarDay({
|
|
|
518
742
|
today: today && !selected,
|
|
519
743
|
outside,
|
|
520
744
|
disabled,
|
|
521
|
-
inRange
|
|
745
|
+
fadedBand: disabled && inRange
|
|
522
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;
|
|
523
764
|
return /*#__PURE__*/_jsxs(Pressable, {
|
|
524
765
|
disabled: disabled,
|
|
525
766
|
onPress: onPress,
|
|
@@ -529,28 +770,22 @@ function CalendarDay({
|
|
|
529
770
|
selected,
|
|
530
771
|
disabled
|
|
531
772
|
},
|
|
532
|
-
className:
|
|
533
|
-
children: [
|
|
534
|
-
className:
|
|
535
|
-
style:
|
|
536
|
-
left: openStart ? 0 : '10%',
|
|
537
|
-
right: openEnd ? 0 : '10%',
|
|
538
|
-
borderTopLeftRadius: openStart ? 0 : 999,
|
|
539
|
-
borderBottomLeftRadius: openStart ? 0 : 999,
|
|
540
|
-
borderTopRightRadius: openEnd ? 0 : 999,
|
|
541
|
-
borderBottomRightRadius: openEnd ? 0 : 999
|
|
542
|
-
}
|
|
773
|
+
className: styles.cell(),
|
|
774
|
+
children: [bandStyle ? /*#__PURE__*/_jsx(View, {
|
|
775
|
+
className: styles.band(),
|
|
776
|
+
style: bandStyle
|
|
543
777
|
}) : null, /*#__PURE__*/_jsx(View, {
|
|
544
|
-
className:
|
|
778
|
+
className: styles.disc(),
|
|
545
779
|
children: /*#__PURE__*/_jsx(Text, {
|
|
546
|
-
|
|
547
|
-
className: cn(styles.label()),
|
|
780
|
+
className: styles.label(),
|
|
548
781
|
children: calendarDayNumber(date, system, locale)
|
|
549
782
|
})
|
|
550
783
|
})]
|
|
551
784
|
});
|
|
552
785
|
}
|
|
553
786
|
export const Calendar = Object.assign(CalendarRoot, {
|
|
787
|
+
Header: CalendarHeader,
|
|
788
|
+
Nav: CalendarNav,
|
|
554
789
|
Grid: CalendarGrid,
|
|
555
790
|
Day: CalendarDay
|
|
556
791
|
});
|