plass-ui 1.0.0 → 1.0.2
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 +165 -0
- package/dist/components/accordion/PlAccordion.js +1 -131
- package/dist/components/accordion/index.js +1 -1
- package/dist/components/alert/PlAlert.js +1 -109
- package/dist/components/alert/index.js +1 -1
- package/dist/components/animate-appear/PlAnimateAppear.js +1 -71
- package/dist/components/animate-appear/index.js +1 -1
- package/dist/components/animate-blink/PlAnimateBlink.js +1 -47
- package/dist/components/animate-blink/index.js +1 -1
- package/dist/components/animate-fade/PlAnimateFade.js +1 -48
- package/dist/components/animate-fade/index.js +1 -1
- package/dist/components/animate-grow/PlAnimateGrow.js +1 -49
- package/dist/components/animate-grow/index.js +1 -1
- package/dist/components/animate-headline/PlAnimateHeadline.js +1 -120
- package/dist/components/animate-headline/index.js +1 -1
- package/dist/components/animate-lighting/PlAnimateLighting.js +1 -65
- package/dist/components/animate-lighting/index.js +1 -1
- package/dist/components/animate-marquee/PlAnimateMarquee.js +1 -94
- package/dist/components/animate-marquee/index.js +1 -1
- package/dist/components/animate-rotate/PlAnimateRotate.js +1 -50
- package/dist/components/animate-rotate/index.js +1 -1
- package/dist/components/animate-slide/PlAnimateSlide.js +1 -49
- package/dist/components/animate-slide/index.js +1 -1
- package/dist/components/animate-typing/PlAnimateTyping.js +1 -177
- package/dist/components/animate-typing/index.js +1 -1
- package/dist/components/animate-zoom/PlAnimateZoom.js +1 -45
- package/dist/components/animate-zoom/index.js +1 -1
- package/dist/components/aspect-ratio/PlAspectRatio.js +1 -58
- package/dist/components/aspect-ratio/index.js +1 -1
- package/dist/components/avatar/PlAvatar.js +1 -121
- package/dist/components/avatar/index.js +1 -1
- package/dist/components/badge/PlBadge.js +1 -168
- package/dist/components/badge/index.js +1 -1
- package/dist/components/blockquote/PlBlockquote.js +1 -112
- package/dist/components/blockquote/index.js +1 -1
- package/dist/components/bottom-navigation/PlBottomNavigation.js +1 -141
- package/dist/components/bottom-navigation/index.js +1 -1
- package/dist/components/box/PlBox.js +1 -46
- package/dist/components/box/index.js +1 -1
- package/dist/components/breadcrumb/PlBreadcrumb.js +1 -201
- package/dist/components/breadcrumb/index.js +1 -1
- package/dist/components/button/PlButton.js +1 -196
- package/dist/components/button/index.js +1 -1
- package/dist/components/button-group/PlButtonGroup.js +1 -64
- package/dist/components/button-group/index.js +1 -1
- package/dist/components/card/PlCard.js +1 -102
- package/dist/components/card/index.js +1 -1
- package/dist/components/carousel/PlCarousel.js +1 -177
- package/dist/components/carousel/index.js +1 -1
- package/dist/components/chat-bubble/PlChatBubble.js +1 -206
- package/dist/components/chat-bubble/index.js +1 -1
- package/dist/components/checkbox/PlCheckbox.js +1 -100
- package/dist/components/checkbox/index.js +1 -1
- package/dist/components/chip/PlChip.js +1 -144
- package/dist/components/chip/index.js +1 -1
- package/dist/components/collapsible/PlCollapsible.js +1 -65
- package/dist/components/collapsible/index.js +1 -1
- package/dist/components/combobox/PlCombobox.js +1 -199
- package/dist/components/combobox/index.js +1 -1
- package/dist/components/container/PlContainer.js +1 -47
- package/dist/components/container/index.js +1 -1
- package/dist/components/date-picker/PlDatePicker.js +1 -87
- package/dist/components/date-picker/index.js +1 -1
- package/dist/components/date-range-picker/PlDateRangePicker.js +1 -152
- package/dist/components/date-range-picker/index.js +1 -1
- package/dist/components/date-time-picker/PlDateTimePicker.js +1 -106
- package/dist/components/date-time-picker/index.js +1 -1
- package/dist/components/divider/PlDivider.js +1 -107
- package/dist/components/divider/index.js +1 -1
- package/dist/components/drawer/PlDrawer.js +1 -190
- package/dist/components/drawer/index.js +1 -1
- package/dist/components/file-picker/PlFilePicker.js +1 -274
- package/dist/components/file-picker/index.js +1 -1
- package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.js +1 -291
- package/dist/components/floating-bottom-navigation/index.js +1 -1
- package/dist/components/grid/PlGrid.js +1 -51
- package/dist/components/grid/PlGridItem.js +1 -34
- package/dist/components/grid/index.js +1 -2
- package/dist/components/highlight/PlHighlight.js +1 -185
- package/dist/components/highlight/index.js +1 -1
- package/dist/components/hot-keys/PlHotKeys.js +1 -294
- package/dist/components/hot-keys/index.js +1 -1
- package/dist/components/icon/PlIcon.js +1 -58
- package/dist/components/icon/index.js +1 -1
- package/dist/components/icon-button/PlIconButton.js +1 -37
- package/dist/components/icon-button/index.js +1 -1
- package/dist/components/list/PlList.js +1 -160
- package/dist/components/list/index.js +1 -1
- package/dist/components/menu/PlMenu.js +1 -209
- package/dist/components/menu/index.js +1 -1
- package/dist/components/modal/PlModal.js +1 -150
- package/dist/components/modal/index.js +1 -1
- package/dist/components/number-field/PlNumberField.js +1 -111
- package/dist/components/number-field/index.js +1 -1
- package/dist/components/otp-field/PlOtpField.js +1 -109
- package/dist/components/otp-field/index.js +1 -1
- package/dist/components/overlay/PlOverlay.js +1 -77
- package/dist/components/overlay/index.js +1 -1
- package/dist/components/pagination/PlPagination.js +1 -159
- package/dist/components/pagination/index.js +1 -1
- package/dist/components/panes/PlPanes.js +1 -324
- package/dist/components/panes/index.js +1 -1
- package/dist/components/pill/PlPill.js +1 -154
- package/dist/components/pill/index.js +1 -1
- package/dist/components/popover/PlPopover.js +1 -113
- package/dist/components/popover/index.js +1 -1
- package/dist/components/progress-box/PlProgressBox.js +1 -34
- package/dist/components/progress-box/index.js +1 -1
- package/dist/components/progress-circular/PlProgressCircular.js +1 -47
- package/dist/components/progress-circular/index.js +1 -1
- package/dist/components/progress-linear/PlProgressLinear.js +1 -40
- package/dist/components/progress-linear/index.js +1 -1
- package/dist/components/radio-group/PlRadioGroup.js +1 -99
- package/dist/components/radio-group/index.js +1 -1
- package/dist/components/rating/PlRating.js +1 -101
- package/dist/components/rating/index.js +1 -1
- package/dist/components/scroll-zone/PlScrollZone.js +1 -411
- package/dist/components/scroll-zone/index.js +1 -1
- package/dist/components/segmented-button/PlSegmentedButton.js +1 -206
- package/dist/components/segmented-button/index.js +1 -1
- package/dist/components/select/PlSelect.js +1 -115
- package/dist/components/select/index.js +1 -1
- package/dist/components/skeleton/PlSkeleton.js +1 -142
- package/dist/components/skeleton/index.js +1 -1
- package/dist/components/slider/PlSlider.js +1 -132
- package/dist/components/slider/index.js +1 -1
- package/dist/components/spoiler/PlSpoiler.js +1 -74
- package/dist/components/spoiler/index.js +1 -1
- package/dist/components/switch/PlSwitch.js +1 -131
- package/dist/components/switch/index.js +1 -1
- package/dist/components/table/PlTable.js +1 -210
- package/dist/components/table/index.js +1 -1
- package/dist/components/tabs/PlTabs.js +1 -182
- package/dist/components/tabs/index.js +1 -1
- package/dist/components/text-field/PlTextField.js +1 -126
- package/dist/components/text-field/index.js +1 -1
- package/dist/components/text-link/PlTextLink.js +1 -136
- package/dist/components/text-link/index.js +1 -1
- package/dist/components/time-picker/PlTimePicker.js +1 -97
- package/dist/components/time-picker/index.js +1 -1
- package/dist/components/timeline/PlTimeline.js +1 -207
- package/dist/components/timeline/index.js +1 -1
- package/dist/components/toast/PlToast.js +1 -174
- package/dist/components/toast/index.js +1 -1
- package/dist/components/toolbar/PlToolbar.js +1 -65
- package/dist/components/toolbar/index.js +1 -1
- package/dist/components/tooltip/PlTooltip.js +1 -117
- package/dist/components/tooltip/index.js +1 -1
- package/dist/components/typography/PlTypography.js +1 -143
- package/dist/components/typography/index.js +1 -1
- package/dist/index.js +1 -75
- package/dist/internal/animate.js +1 -318
- package/dist/internal/button-group.js +2 -19
- package/dist/internal/calendar.js +1 -501
- package/dist/internal/date.js +1 -353
- package/dist/internal/format.js +1 -41
- package/dist/internal/grid.js +1 -143
- package/dist/internal/icons.js +1 -138
- package/dist/internal/menu.js +1 -18
- package/dist/internal/picker.js +1 -139
- package/dist/internal/progress.js +1 -167
- package/dist/internal/sizer.js +1 -35
- package/dist/internal/styles.js +1 -636
- package/dist/types.js +0 -8
- package/package.json +5 -3
|
@@ -1,501 +1 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { PlButton } from '../components/button/PlButton.js';
|
|
4
|
-
import { ChevronIcon } from './icons.js';
|
|
5
|
-
import { dateFormatter } from './format.js';
|
|
6
|
-
import { addDays, addMonths, addYears, calendarWeeks, compareDay, daysInMonth, isDayOutside, isMonthBeforeYear, isSameDay, isSameMonth, isValidDate, makeDate, meridiemLabels, monthLabels, startOfDay, startOfMonth, today, weekdayLabels, withTime, YEAR_PAGE_SIZE, yearPageStart } from './date.js';
|
|
7
|
-
import { controlHeightClasses, controlTextClasses, cx, gapClasses, metaTextClasses, srOnlyClasses, transitionClasses } from './styles.js';
|
|
8
|
-
export const defaultPickerLabels = {
|
|
9
|
-
previousMonth: 'Previous month',
|
|
10
|
-
nextMonth: 'Next month',
|
|
11
|
-
previousYear: 'Previous year',
|
|
12
|
-
nextYear: 'Next year',
|
|
13
|
-
previousYears: 'Previous years',
|
|
14
|
-
nextYears: 'Next years',
|
|
15
|
-
chooseMonth: 'Choose a month',
|
|
16
|
-
chooseYear: 'Choose a year',
|
|
17
|
-
today: 'Today',
|
|
18
|
-
now: 'Now',
|
|
19
|
-
clear: 'Clear',
|
|
20
|
-
done: 'Done',
|
|
21
|
-
hour: 'Hour',
|
|
22
|
-
minute: 'Minute',
|
|
23
|
-
second: 'Second',
|
|
24
|
-
meridiem: 'AM/PM',
|
|
25
|
-
start: 'Start',
|
|
26
|
-
end: 'End'
|
|
27
|
-
};
|
|
28
|
-
/** Merges a caller's overrides over the defaults, once per render. */
|
|
29
|
-
export function usePickerLabels(overrides) {
|
|
30
|
-
return React.useMemo(() => (overrides ? { ...defaultPickerLabels, ...overrides } : defaultPickerLabels), [overrides]);
|
|
31
|
-
}
|
|
32
|
-
/* ---------------------------------------------------------------------------
|
|
33
|
-
* Scale
|
|
34
|
-
* ------------------------------------------------------------------------- */
|
|
35
|
-
/**
|
|
36
|
-
* The width of one day cell, as a length rather than as a class.
|
|
37
|
-
*
|
|
38
|
-
* It goes in an inline `--p-cell` slot for the reason the design language gives
|
|
39
|
-
* for every per-instance value: Tailwind only sees class names written out
|
|
40
|
-
* literally, and the grid needs this number in places that are not a `size-*`
|
|
41
|
-
* utility — the panel's own width (`7 × cell`), the height the three views share
|
|
42
|
-
* so switching between them does not resize the popup, and the clock's columns.
|
|
43
|
-
*
|
|
44
|
-
* The numbers are `controlHeightClasses` as lengths: a `md` day cell is 40px,
|
|
45
|
-
* which is a `md` PlButton, which is a `md` PlTextField. A calendar dropped
|
|
46
|
-
* beside a form is on the form's grid.
|
|
47
|
-
*/
|
|
48
|
-
const cellSizes = {
|
|
49
|
-
xs: '1.5rem',
|
|
50
|
-
sm: '2rem',
|
|
51
|
-
md: '2.5rem',
|
|
52
|
-
lg: '3rem',
|
|
53
|
-
xl: '3.5rem'
|
|
54
|
-
};
|
|
55
|
-
/** A cell's corner, one step *down* the radius ladder from the popup it sits in. */
|
|
56
|
-
const cellRadiusClasses = {
|
|
57
|
-
xs: 'rounded-(--plass-radius-xs)',
|
|
58
|
-
sm: 'rounded-(--plass-radius-xs)',
|
|
59
|
-
md: 'rounded-(--plass-radius-sm)',
|
|
60
|
-
lg: 'rounded-(--plass-radius-md)',
|
|
61
|
-
xl: 'rounded-(--plass-radius-lg)'
|
|
62
|
-
};
|
|
63
|
-
/** The same corner on one side only, for where a range band stops. */
|
|
64
|
-
const cellRadiusStartClasses = {
|
|
65
|
-
xs: 'rounded-s-(--plass-radius-xs)',
|
|
66
|
-
sm: 'rounded-s-(--plass-radius-xs)',
|
|
67
|
-
md: 'rounded-s-(--plass-radius-sm)',
|
|
68
|
-
lg: 'rounded-s-(--plass-radius-md)',
|
|
69
|
-
xl: 'rounded-s-(--plass-radius-lg)'
|
|
70
|
-
};
|
|
71
|
-
const cellRadiusEndClasses = {
|
|
72
|
-
xs: 'rounded-e-(--plass-radius-xs)',
|
|
73
|
-
sm: 'rounded-e-(--plass-radius-xs)',
|
|
74
|
-
md: 'rounded-e-(--plass-radius-sm)',
|
|
75
|
-
lg: 'rounded-e-(--plass-radius-md)',
|
|
76
|
-
xl: 'rounded-e-(--plass-radius-lg)'
|
|
77
|
-
};
|
|
78
|
-
/**
|
|
79
|
-
* What every cell in every one of the three views is drawn on.
|
|
80
|
-
*
|
|
81
|
-
* The house transition, unchanged, and **no `transform` anywhere near it**: a
|
|
82
|
-
* grid of forty-two cells that each grew a pixel under the pointer would be the
|
|
83
|
-
* single worst place in the library to break that rule.
|
|
84
|
-
*
|
|
85
|
-
* The ring is written as the `outline` shorthand — never `outline-none`, which
|
|
86
|
-
* zeroes the variable Tailwind routes the style through — and pulled *inside*
|
|
87
|
-
* the cell rather than sitting outside it, because a ring drawn outside a cell
|
|
88
|
-
* in a gapless grid is a ring drawn on the neighbours.
|
|
89
|
-
*/
|
|
90
|
-
const cellBaseClasses = /* @__PURE__ */ [
|
|
91
|
-
'relative flex items-center justify-center tabular-nums select-none',
|
|
92
|
-
'[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]',
|
|
93
|
-
transitionClasses,
|
|
94
|
-
'focus-visible:z-10 focus-visible:[outline:2px_solid_var(--p-ring)]',
|
|
95
|
-
'focus-visible:[outline-offset:-2px]'
|
|
96
|
-
].join(' ');
|
|
97
|
-
/**
|
|
98
|
-
* One pressable square in a grid.
|
|
99
|
-
*
|
|
100
|
-
* The state branch is an if/else chain rather than a stack of Tailwind
|
|
101
|
-
* variants, which the design language asks for by name: two utilities of equal
|
|
102
|
-
* specificity resolve by their order in the generated stylesheet, and "chosen"
|
|
103
|
-
* beating "inside the range" is not something a component may leave to that.
|
|
104
|
-
*
|
|
105
|
-
* The order is the order of importance. Unavailable first — a blocked day still
|
|
106
|
-
* wearing the range's tint would be advertising that it is part of a range it
|
|
107
|
-
* cannot join. Then chosen, then inside the range, then today, then the days
|
|
108
|
-
* belonging to the month next door.
|
|
109
|
-
*
|
|
110
|
-
* A chosen cell is the family's **gradient**, which makes it the one filled
|
|
111
|
-
* token in the popup: the sheet under it is undyed glass, so what a reader is
|
|
112
|
-
* looking for in a grid of forty-two is the only coloured thing on it.
|
|
113
|
-
*/
|
|
114
|
-
function Cell({ children, label, selected, inRange = false, rangeEdge = null, current = false, muted = false, disabled = false, focused = false, size, className, onClick, onPointerEnter, onKeyDown }) {
|
|
115
|
-
const stateClasses = disabled
|
|
116
|
-
? 'cursor-not-allowed text-(--plass-muted-fg) opacity-50'
|
|
117
|
-
: selected
|
|
118
|
-
? 'cursor-pointer font-semibold text-(--p-on-solid) [background-image:var(--p-fill)] hover:brightness-105 active:brightness-95'
|
|
119
|
-
: inRange
|
|
120
|
-
? 'cursor-pointer bg-(--p-soft) text-(--plass-fg) hover:bg-(--p-soft-hover) active:bg-(--p-soft-press)'
|
|
121
|
-
: current
|
|
122
|
-
? 'cursor-pointer font-semibold text-(--p-accent) hover:bg-(--p-soft) active:bg-(--p-soft-hover)'
|
|
123
|
-
: muted
|
|
124
|
-
? 'cursor-pointer text-(--plass-muted-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)'
|
|
125
|
-
: 'cursor-pointer text-(--plass-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)';
|
|
126
|
-
// Square through the middle of a run and rounded where the run stops, so a
|
|
127
|
-
// week of banded days reads as one shape rather than as seven tokens.
|
|
128
|
-
const shapeClasses = rangeEdge === 'start'
|
|
129
|
-
? cellRadiusStartClasses[size]
|
|
130
|
-
: rangeEdge === 'end'
|
|
131
|
-
? cellRadiusEndClasses[size]
|
|
132
|
-
: rangeEdge === 'middle'
|
|
133
|
-
? 'rounded-none'
|
|
134
|
-
: cellRadiusClasses[size];
|
|
135
|
-
return (_jsxs("button", { type: "button", role: "gridcell", "aria-label": label, "aria-selected": selected, "aria-current": current ? 'date' : undefined, "aria-disabled": disabled || undefined,
|
|
136
|
-
// Not the `disabled` attribute. A disabled button leaves the tab order and
|
|
137
|
-
// the grid's arrow-key path with it, so a reader arrowing across a month
|
|
138
|
-
// would fall into a hole at every blocked day.
|
|
139
|
-
tabIndex: focused ? 0 : -1, "data-focus-target": focused ? 'true' : undefined, className: cx(cellBaseClasses, shapeClasses, stateClasses, className), onClick: () => {
|
|
140
|
-
if (!disabled) {
|
|
141
|
-
onClick();
|
|
142
|
-
}
|
|
143
|
-
}, onPointerEnter: onPointerEnter, onKeyDown: onKeyDown, children: [children, current ? (_jsx("span", { "aria-hidden": "true", className: "absolute bottom-[0.18em] size-[0.22em] rounded-full bg-current" })) : null] }));
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The two steppers and, between them, the way into the other two views.
|
|
147
|
-
*
|
|
148
|
-
* This is what the component is for. A calendar that only steps a month at a
|
|
149
|
-
* time puts a birthday thirty years back a hundred and eighty clicks away, so
|
|
150
|
-
* the month and the year are each a button that opens a grid of its own: two
|
|
151
|
-
* clicks to any month of the year on screen, three to any year at all.
|
|
152
|
-
*
|
|
153
|
-
* The two buttons are printed in the order the locale writes them — `July 2026`
|
|
154
|
-
* in English, `2026년 7월` in Korean. `Intl` is asked which part comes first
|
|
155
|
-
* rather than being guessed at, because a header in the wrong order reads as
|
|
156
|
-
* broken to exactly the readers it is wrong for.
|
|
157
|
-
*/
|
|
158
|
-
function Header({ size, color, view, month, locale, labels, showPreviousButton, showNextButton, onStep, onViewChange }) {
|
|
159
|
-
const monthName = monthLabels(locale, 'long')[month.getMonth()];
|
|
160
|
-
const yearName = String(month.getFullYear());
|
|
161
|
-
const monthFirst = isMonthBeforeYear(locale);
|
|
162
|
-
const stepLabels = view === 'day'
|
|
163
|
-
? [labels.previousMonth, labels.nextMonth]
|
|
164
|
-
: view === 'month'
|
|
165
|
-
? [labels.previousYear, labels.nextYear]
|
|
166
|
-
: [labels.previousYears, labels.nextYears];
|
|
167
|
-
const stepper = (direction, shown) => shown ? (_jsx(PlButton, { variant: "ghost", size: size, color: color, density: "compact", "aria-label": stepLabels[direction === -1 ? 0 : 1], onClick: () => onStep(direction), startIcon: _jsx("span", { className: cx('flex items-center', direction === -1 ? 'rotate-90 rtl:-rotate-90' : '-rotate-90 rtl:rotate-90'), children: _jsx(ChevronIcon, {}) }) })) : (_jsx("span", { "aria-hidden": "true", className: cx(controlHeightClasses[size], 'w-(--p-cell)') }));
|
|
168
|
-
const disclosure = (open) => (_jsx("span", { className: cx('flex items-center text-(--plass-muted-fg)', '[transition:rotate_var(--plass-duration)_var(--plass-ease)]', open && 'rotate-180'), children: _jsx(ChevronIcon, {}) }));
|
|
169
|
-
const monthButton = (_jsx(PlButton, { variant: "ghost", size: size, color: color, density: "compact", "aria-label": labels.chooseMonth, "aria-expanded": view === 'month', onClick: () => onViewChange(view === 'month' ? 'day' : 'month'), endIcon: disclosure(view === 'month'), children: monthName }, "month"));
|
|
170
|
-
const yearButton = (_jsx(PlButton, { variant: "ghost", size: size, color: color, density: "compact", className: "tabular-nums", "aria-label": labels.chooseYear, "aria-expanded": view === 'year', onClick: () => onViewChange(view === 'year' ? 'day' : 'year'), endIcon: disclosure(view === 'year'), children: yearName }, "year"));
|
|
171
|
-
const pageStart = yearPageStart(month.getFullYear());
|
|
172
|
-
return (_jsxs("div", { className: "flex items-center justify-between gap-1", children: [stepper(-1, showPreviousButton), _jsx("div", { className: cx('flex min-w-0 flex-1 items-center justify-center', gapClasses[size]), children: view === 'year' ? (_jsx("span", { className: cx('flex items-center font-semibold tabular-nums text-(--plass-fg)', controlHeightClasses[size], controlTextClasses[size]), children: `${pageStart}–${pageStart + YEAR_PAGE_SIZE - 1}` })) : view === 'month' ? (yearButton) : monthFirst ? (_jsxs(React.Fragment, { children: [monthButton, yearButton] })) : (_jsxs(React.Fragment, { children: [yearButton, monthButton] })) }), stepper(1, showNextButton)] }));
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* One month, with a way to reach every other one.
|
|
176
|
-
*
|
|
177
|
-
* Three views on the same footprint: the days of a month, the twelve months of
|
|
178
|
-
* a year, twelve years at a time. They are deliberately the same width *and*
|
|
179
|
-
* the same height — the day view is seven rows counting its header, and the
|
|
180
|
-
* other two stretch four rows and three rows across that same height — so
|
|
181
|
-
* switching view never resizes the popup under the pointer that opened it.
|
|
182
|
-
*
|
|
183
|
-
* Arrow keys move by one cell, `PageUp`/`PageDown` by a month (a year with
|
|
184
|
-
* Shift), `Home`/`End` to the ends of the week, and running off an edge steps
|
|
185
|
-
* the calendar rather than stopping. One roving tab stop, so `Tab` leaves the
|
|
186
|
-
* grid instead of walking forty-two cells — the pattern the ARIA date-picker
|
|
187
|
-
* practice describes, and the reason none of the cells is a `disabled` button.
|
|
188
|
-
*/
|
|
189
|
-
export function Calendar({ size, color, locale, weekStartsOn, month, onMonthChange, selected, rangeStart = null, rangeEnd = null, onSelect, onPreviewChange, minDate, maxDate, shouldDisableDate, showOutsideDays = true, autoFocus = false, showPreviousButton = true, showNextButton = true, labels, className }) {
|
|
190
|
-
const [view, setView] = React.useState('day');
|
|
191
|
-
const chosen = React.useMemo(() => selected.filter(isValidDate), [selected]);
|
|
192
|
-
// The one cell that carries the tab stop. It starts on the chosen day, or on
|
|
193
|
-
// today when today is on screen, or on the 1st — never nowhere, because a grid
|
|
194
|
-
// whose tab stop is nowhere cannot be reached by a keyboard at all.
|
|
195
|
-
const [focusedDate, setFocusedDate] = React.useState(() => {
|
|
196
|
-
const preferred = chosen.find((date) => isSameMonth(date, month));
|
|
197
|
-
if (preferred) {
|
|
198
|
-
return startOfDay(preferred);
|
|
199
|
-
}
|
|
200
|
-
return isSameMonth(today(), month) ? today() : startOfMonth(month);
|
|
201
|
-
});
|
|
202
|
-
// Set only by the interactions that *move* the focus — an arrow key, a view
|
|
203
|
-
// change — so the effect below never yanks focus out from under a pointer user
|
|
204
|
-
// doing something else on the page.
|
|
205
|
-
const pendingFocus = React.useRef(autoFocus);
|
|
206
|
-
const rootRef = React.useRef(null);
|
|
207
|
-
// Following the month keeps the tab stop inside the grid the reader is looking
|
|
208
|
-
// at: stepping a month and then pressing an arrow lands somewhere sensible
|
|
209
|
-
// instead of scrolling the panel back where it came from.
|
|
210
|
-
React.useEffect(() => {
|
|
211
|
-
// The tab stop follows a prop, and the updater is a no-op unless the month
|
|
212
|
-
// actually changed — so this settles in one pass rather than cascading.
|
|
213
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
214
|
-
setFocusedDate((current) => (isSameMonth(current, month) ? current : startOfMonth(month)));
|
|
215
|
-
}, [month]);
|
|
216
|
-
React.useLayoutEffect(() => {
|
|
217
|
-
if (!pendingFocus.current) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
pendingFocus.current = false;
|
|
221
|
-
// `preventScroll`, because on the very first pass this runs before the popup
|
|
222
|
-
// has been positioned — it is still at the top-left of the page, and the
|
|
223
|
-
// browser's own "scroll the focused element into view" would drag the
|
|
224
|
-
// document up there with it.
|
|
225
|
-
rootRef.current
|
|
226
|
-
?.querySelector('[data-focus-target="true"]')
|
|
227
|
-
?.focus({ preventScroll: true });
|
|
228
|
-
});
|
|
229
|
-
const isDisabled = React.useCallback((date) => isDayOutside(date, minDate, maxDate) || (shouldDisableDate?.(date) ?? false), [minDate, maxDate, shouldDisableDate]);
|
|
230
|
-
/** Moves the tab stop, pulling the month along when it lands outside. */
|
|
231
|
-
const moveFocus = (next) => {
|
|
232
|
-
pendingFocus.current = true;
|
|
233
|
-
setFocusedDate(next);
|
|
234
|
-
if (!isSameMonth(next, month)) {
|
|
235
|
-
onMonthChange(startOfMonth(next));
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
const step = (direction) => {
|
|
239
|
-
if (view === 'day') {
|
|
240
|
-
onMonthChange(addMonths(month, direction));
|
|
241
|
-
}
|
|
242
|
-
else if (view === 'month') {
|
|
243
|
-
onMonthChange(addYears(month, direction));
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
onMonthChange(addYears(month, direction * YEAR_PAGE_SIZE));
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
const changeView = (next) => {
|
|
250
|
-
pendingFocus.current = true;
|
|
251
|
-
setView(next);
|
|
252
|
-
};
|
|
253
|
-
/** In month and year view the header's `month` *is* the cursor. */
|
|
254
|
-
const moveCursor = (next) => {
|
|
255
|
-
pendingFocus.current = true;
|
|
256
|
-
onMonthChange(next);
|
|
257
|
-
};
|
|
258
|
-
return (_jsxs("div", { ref: rootRef, className: cx('flex flex-col', gapClasses[size], className), style: { '--p-cell': cellSizes[size] }, onPointerLeave: () => onPreviewChange?.(null), children: [_jsx(Header, { size: size, color: color, view: view, month: month, locale: locale, labels: labels, showPreviousButton: showPreviousButton, showNextButton: showNextButton, onStep: step, onViewChange: changeView }), _jsx("div", { className: "h-[calc(var(--p-cell)*7)] w-[calc(var(--p-cell)*7)]", children: view === 'day' ? (_jsx(DayGrid, { size: size, locale: locale, weekStartsOn: weekStartsOn, month: month, chosen: chosen, rangeStart: rangeStart, rangeEnd: rangeEnd, focusedDate: focusedDate, showOutsideDays: showOutsideDays, isDisabled: isDisabled, onSelect: (date) => {
|
|
259
|
-
setFocusedDate(date);
|
|
260
|
-
onSelect(date);
|
|
261
|
-
}, onPreviewChange: onPreviewChange, onMoveFocus: moveFocus })) : view === 'month' ? (_jsx(MonthGrid, { size: size, locale: locale, month: month, chosen: chosen, minDate: minDate, maxDate: maxDate, onMoveCursor: moveCursor, onPick: (index) => {
|
|
262
|
-
onMonthChange(makeDate(month.getFullYear(), index, 1));
|
|
263
|
-
changeView('day');
|
|
264
|
-
} })) : (_jsx(YearGrid, { size: size, month: month, chosen: chosen, minDate: minDate, maxDate: maxDate, onMoveCursor: moveCursor, onPick: (year) => {
|
|
265
|
-
onMonthChange(makeDate(year, month.getMonth(), 1));
|
|
266
|
-
changeView('month');
|
|
267
|
-
} })) })] }));
|
|
268
|
-
}
|
|
269
|
-
/* ---------------------------------------------------------------------------
|
|
270
|
-
* The three grids
|
|
271
|
-
* ------------------------------------------------------------------------- */
|
|
272
|
-
/** The two ends of a band, smallest first, whichever way round they arrived. */
|
|
273
|
-
function orderedRange(a, b) {
|
|
274
|
-
if (!isValidDate(a) || !isValidDate(b)) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
return compareDay(a, b) <= 0 ? [a, b] : [b, a];
|
|
278
|
-
}
|
|
279
|
-
function DayGrid({ size, locale, weekStartsOn, month, chosen, rangeStart, rangeEnd, focusedDate, showOutsideDays, isDisabled, onSelect, onPreviewChange, onMoveFocus }) {
|
|
280
|
-
const weeks = calendarWeeks(month, weekStartsOn);
|
|
281
|
-
const short = weekdayLabels(locale, weekStartsOn, 'short');
|
|
282
|
-
const long = weekdayLabels(locale, weekStartsOn, 'long');
|
|
283
|
-
const fullDate = dateFormatter(locale, { dateStyle: 'full' });
|
|
284
|
-
const band = orderedRange(rangeStart, rangeEnd);
|
|
285
|
-
const now = today();
|
|
286
|
-
const onKeyDown = (event, date) => {
|
|
287
|
-
const offsetInWeek = (date.getDay() - weekStartsOn + 7) % 7;
|
|
288
|
-
const moves = {
|
|
289
|
-
ArrowLeft: () => addDays(date, -1),
|
|
290
|
-
ArrowRight: () => addDays(date, 1),
|
|
291
|
-
ArrowUp: () => addDays(date, -7),
|
|
292
|
-
ArrowDown: () => addDays(date, 7),
|
|
293
|
-
Home: () => addDays(date, -offsetInWeek),
|
|
294
|
-
End: () => addDays(date, 6 - offsetInWeek),
|
|
295
|
-
PageUp: () => addMonths(date, event.shiftKey ? -12 : -1),
|
|
296
|
-
PageDown: () => addMonths(date, event.shiftKey ? 12 : 1)
|
|
297
|
-
};
|
|
298
|
-
const move = moves[event.key];
|
|
299
|
-
if (!move) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
event.preventDefault();
|
|
303
|
-
onMoveFocus(move());
|
|
304
|
-
};
|
|
305
|
-
return (_jsxs("div", { role: "grid", className: "flex h-full flex-col", children: [_jsx("div", { role: "row", className: "grid grid-cols-7", children: short.map((label, index) => (_jsx("span", { role: "columnheader", "aria-label": long[index], className: cx('flex h-(--p-cell) items-center justify-center font-semibold text-(--plass-muted-fg) select-none', metaTextClasses[size]), children: label }, index))) }), weeks.map((week, weekIndex) => (_jsx("div", { role: "row", className: "grid grid-cols-7", children: week.map((date) => {
|
|
306
|
-
const outside = !isSameMonth(date, month);
|
|
307
|
-
// A hole the size of a cell rather than a missing one: the grid has
|
|
308
|
-
// to keep its seven columns and six rows whatever month it is on.
|
|
309
|
-
if (outside && !showOutsideDays) {
|
|
310
|
-
return (_jsx("span", { role: "gridcell", "aria-hidden": "true", className: "size-(--p-cell)" }, date.getTime()));
|
|
311
|
-
}
|
|
312
|
-
const isChosen = chosen.some((entry) => isSameDay(entry, date));
|
|
313
|
-
const within = band !== null && compareDay(date, band[0]) >= 0 && compareDay(date, band[1]) <= 0;
|
|
314
|
-
const atStart = band !== null && within && isSameDay(date, band[0]);
|
|
315
|
-
const atEnd = band !== null && within && isSameDay(date, band[1]);
|
|
316
|
-
return (_jsx(Cell, { size: size, label: fullDate.format(date), selected: isChosen, inRange: within && !isChosen, rangeEdge: !within
|
|
317
|
-
? null
|
|
318
|
-
: atStart && atEnd
|
|
319
|
-
? 'both'
|
|
320
|
-
: atStart
|
|
321
|
-
? 'start'
|
|
322
|
-
: atEnd
|
|
323
|
-
? 'end'
|
|
324
|
-
: 'middle', current: isSameDay(date, now) && !isChosen, muted: outside, disabled: isDisabled(date), focused: isSameDay(date, focusedDate), className: cx('size-(--p-cell)', controlTextClasses[size]), onClick: () => onSelect(date), onPointerEnter: () => onPreviewChange?.(date), onKeyDown: (event) => onKeyDown(event, date), children: date.getDate() }, date.getTime()));
|
|
325
|
-
}) }, weekIndex)))] }));
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Twelve months, three across.
|
|
329
|
-
*
|
|
330
|
-
* The tab stop is the header's own month, so moving it *is* moving the header —
|
|
331
|
-
* arrowing right off December lands on January of the next year and the year
|
|
332
|
-
* button follows, which is one fewer thing for the reader to keep track of.
|
|
333
|
-
*/
|
|
334
|
-
function MonthGrid({ size, locale, month, chosen, minDate, maxDate, onMoveCursor, onPick }) {
|
|
335
|
-
const short = monthLabels(locale, 'short');
|
|
336
|
-
const long = monthLabels(locale, 'long');
|
|
337
|
-
const year = month.getFullYear();
|
|
338
|
-
const now = new Date();
|
|
339
|
-
const onKeyDown = (event) => {
|
|
340
|
-
const steps = {
|
|
341
|
-
ArrowLeft: -1,
|
|
342
|
-
ArrowRight: 1,
|
|
343
|
-
ArrowUp: -3,
|
|
344
|
-
ArrowDown: 3,
|
|
345
|
-
PageUp: -12,
|
|
346
|
-
PageDown: 12
|
|
347
|
-
};
|
|
348
|
-
const step = steps[event.key];
|
|
349
|
-
if (step === undefined) {
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
event.preventDefault();
|
|
353
|
-
onMoveCursor(addMonths(startOfMonth(month), step));
|
|
354
|
-
};
|
|
355
|
-
return (_jsx("div", { role: "grid", className: "flex h-full flex-col justify-evenly", children: [0, 1, 2, 3].map((row) => (_jsx("div", { role: "row", className: "grid grid-cols-3 gap-1", children: [0, 1, 2].map((column) => {
|
|
356
|
-
const index = row * 3 + column;
|
|
357
|
-
// A month is out of bounds only when every day in it is: the month a
|
|
358
|
-
// `minDate` falls in is still reachable, it just starts late.
|
|
359
|
-
const first = makeDate(year, index, 1);
|
|
360
|
-
const last = makeDate(year, index, daysInMonth(year, index));
|
|
361
|
-
return (_jsx(Cell, { size: size, label: `${long[index]} ${year}`, selected: chosen.some((entry) => entry.getFullYear() === year && entry.getMonth() === index), current: now.getFullYear() === year && now.getMonth() === index, disabled: (isValidDate(minDate) && compareDay(last, minDate) < 0) ||
|
|
362
|
-
(isValidDate(maxDate) && compareDay(first, maxDate) > 0), focused: index === month.getMonth(), className: cx('h-(--p-cell) w-full', controlTextClasses[size]), onClick: () => onPick(index), onKeyDown: onKeyDown, children: short[index] }, index));
|
|
363
|
-
}) }, row))) }));
|
|
364
|
-
}
|
|
365
|
-
/** Twelve years, four across, and the same trick with the cursor. */
|
|
366
|
-
function YearGrid({ size, month, chosen, minDate, maxDate, onMoveCursor, onPick }) {
|
|
367
|
-
const pageStart = yearPageStart(month.getFullYear());
|
|
368
|
-
const now = new Date().getFullYear();
|
|
369
|
-
const onKeyDown = (event) => {
|
|
370
|
-
const steps = {
|
|
371
|
-
ArrowLeft: -1,
|
|
372
|
-
ArrowRight: 1,
|
|
373
|
-
ArrowUp: -4,
|
|
374
|
-
ArrowDown: 4,
|
|
375
|
-
PageUp: -YEAR_PAGE_SIZE,
|
|
376
|
-
PageDown: YEAR_PAGE_SIZE
|
|
377
|
-
};
|
|
378
|
-
const step = steps[event.key];
|
|
379
|
-
if (step === undefined) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
event.preventDefault();
|
|
383
|
-
onMoveCursor(addYears(startOfMonth(month), step));
|
|
384
|
-
};
|
|
385
|
-
return (_jsx("div", { role: "grid", className: "flex h-full flex-col justify-evenly", children: [0, 1, 2].map((row) => (_jsx("div", { role: "row", className: "grid grid-cols-4 gap-1", children: [0, 1, 2, 3].map((column) => {
|
|
386
|
-
const year = pageStart + row * 4 + column;
|
|
387
|
-
return (_jsx(Cell, { size: size, label: String(year), selected: chosen.some((entry) => entry.getFullYear() === year), current: year === now, disabled: (isValidDate(minDate) && year < minDate.getFullYear()) ||
|
|
388
|
-
(isValidDate(maxDate) && year > maxDate.getFullYear()), focused: year === month.getFullYear(), className: cx('h-(--p-cell) w-full', controlTextClasses[size]), onClick: () => onPick(year), onKeyDown: onKeyDown, children: year }, year));
|
|
389
|
-
}) }, row))) }));
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Brings a row into view *inside its own column*, and nowhere else.
|
|
393
|
-
*
|
|
394
|
-
* `scrollIntoView` walks every scrollable ancestor up to the document, and the
|
|
395
|
-
* popup this runs in has not been positioned yet when the effect fires — it is
|
|
396
|
-
* still at the top-left of the page. So the browser dutifully scrolled the whole
|
|
397
|
-
* document to the top to reveal a row that was about to move anyway, which is
|
|
398
|
-
* the "opening a picker jumps the page" bug. Setting `scrollTop` on the column
|
|
399
|
-
* cannot touch anything above it.
|
|
400
|
-
*/
|
|
401
|
-
function revealInColumn(row) {
|
|
402
|
-
const column = row.parentElement;
|
|
403
|
-
if (!column) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
// Measured rather than read off `offsetTop`, which is relative to whichever
|
|
407
|
-
// ancestor happens to be positioned and not necessarily to the column.
|
|
408
|
-
const rowBox = row.getBoundingClientRect();
|
|
409
|
-
const columnBox = column.getBoundingClientRect();
|
|
410
|
-
const top = rowBox.top - columnBox.top - column.clientTop + column.scrollTop;
|
|
411
|
-
const bottom = top + rowBox.height;
|
|
412
|
-
if (top < column.scrollTop) {
|
|
413
|
-
column.scrollTop = top;
|
|
414
|
-
}
|
|
415
|
-
else if (bottom > column.scrollTop + column.clientHeight) {
|
|
416
|
-
column.scrollTop = bottom - column.clientHeight;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Hours, minutes and — when asked for — seconds, as columns you scroll rather
|
|
421
|
-
* than as a dial you drag.
|
|
422
|
-
*
|
|
423
|
-
* Columns because they are the shape that answers what a time picker is actually
|
|
424
|
-
* asked: "half past nine" is two glances, and "any time at all, on the hour" is
|
|
425
|
-
* a column you never touch. A clock face is prettier and needs a `transform` to
|
|
426
|
-
* read, which this library does not have.
|
|
427
|
-
*
|
|
428
|
-
* The chosen row in each column is scrolled into view once, on open. That is the
|
|
429
|
-
* only imperative work here and it is not optional: a column of sixty minutes
|
|
430
|
-
* that opens at `00` while the value is `45` has hidden its own answer.
|
|
431
|
-
*/
|
|
432
|
-
export function TimeGrid({ size, density, locale, value, referenceDate, onChange, hour12, showSeconds, hourStep, minuteStep, secondStep, shouldDisableTime, autoFocus = false, labels, className }) {
|
|
433
|
-
const base = value ?? referenceDate;
|
|
434
|
-
const rootRef = React.useRef(null);
|
|
435
|
-
const [am, pm] = React.useMemo(() => meridiemLabels(locale), [locale]);
|
|
436
|
-
React.useEffect(() => {
|
|
437
|
-
const root = rootRef.current;
|
|
438
|
-
root?.querySelectorAll('[data-chosen="true"]').forEach(revealInColumn);
|
|
439
|
-
if (autoFocus) {
|
|
440
|
-
const first = root?.querySelector('[role="listbox"]');
|
|
441
|
-
(first?.querySelector('[data-chosen="true"]') ??
|
|
442
|
-
first?.querySelector('[role="option"]'))?.focus({ preventScroll: true });
|
|
443
|
-
}
|
|
444
|
-
// Once, on open. Re-running it on every change would drag a column back
|
|
445
|
-
// under the pointer that is scrolling it.
|
|
446
|
-
}, [autoFocus]);
|
|
447
|
-
const hours = React.useMemo(() => {
|
|
448
|
-
const count = Math.ceil((hour12 ? 12 : 24) / hourStep);
|
|
449
|
-
const raw = Array.from({ length: count }, (_, index) => index * hourStep);
|
|
450
|
-
// 12, 1, 2 … 11 — the order a 12-hour dial is read in, not 0…11.
|
|
451
|
-
return hour12 ? raw.map((hour) => (hour === 0 ? 12 : hour)) : raw;
|
|
452
|
-
}, [hour12, hourStep]);
|
|
453
|
-
const minutes = React.useMemo(() => Array.from({ length: Math.ceil(60 / minuteStep) }, (_, index) => index * minuteStep), [minuteStep]);
|
|
454
|
-
const seconds = React.useMemo(() => Array.from({ length: Math.ceil(60 / secondStep) }, (_, index) => index * secondStep), [secondStep]);
|
|
455
|
-
/** The instant choosing this row would produce. */
|
|
456
|
-
const candidate = (unit, raw) => {
|
|
457
|
-
if (unit === 'hour') {
|
|
458
|
-
return withTime(base, {
|
|
459
|
-
hours: hour12 ? (raw % 12) + (base.getHours() >= 12 ? 12 : 0) : raw
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
if (unit === 'minute') {
|
|
463
|
-
return withTime(base, { minutes: raw });
|
|
464
|
-
}
|
|
465
|
-
if (unit === 'second') {
|
|
466
|
-
return withTime(base, { seconds: raw });
|
|
467
|
-
}
|
|
468
|
-
// `raw` is 0 for the first half of the day and 1 for the second.
|
|
469
|
-
return withTime(base, { hours: (base.getHours() % 12) + raw * 12 });
|
|
470
|
-
};
|
|
471
|
-
const pad = (raw) => String(raw).padStart(2, '0');
|
|
472
|
-
const displayHour = hour12 ? base.getHours() % 12 || 12 : base.getHours();
|
|
473
|
-
const column = (unit, name, rows, isChosen, render) => (_jsx("div", { role: "listbox", "aria-label": name, className: cx('flex flex-col gap-0.5 overflow-y-auto overscroll-contain',
|
|
474
|
-
// The same height as the calendar beside it, so a PlDateTimePicker's
|
|
475
|
-
// popup is one rectangle rather than two of different heights.
|
|
476
|
-
'h-[calc(var(--p-cell)*7)] w-[calc(var(--p-cell)*1.75)]', 'scroll-py-0.5 [scrollbar-width:thin]'), children: rows.map((raw) => {
|
|
477
|
-
const at = candidate(unit, raw);
|
|
478
|
-
const chosen = value !== null && isChosen(raw);
|
|
479
|
-
const disabled = shouldDisableTime?.(at, unit) ?? false;
|
|
480
|
-
return (_jsx("button", { type: "button", role: "option", "aria-selected": chosen, "aria-disabled": disabled || undefined, "data-chosen": chosen ? 'true' : undefined, className: cx(cellBaseClasses, cellRadiusClasses[size], controlHeightClasses[size], controlTextClasses[size], 'w-full shrink-0', disabled
|
|
481
|
-
? 'cursor-not-allowed text-(--plass-muted-fg) opacity-50'
|
|
482
|
-
: chosen
|
|
483
|
-
? 'cursor-pointer font-semibold text-(--p-on-solid) [background-image:var(--p-fill)] hover:brightness-105 active:brightness-95'
|
|
484
|
-
: 'cursor-pointer text-(--plass-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)'), onClick: () => {
|
|
485
|
-
if (!disabled) {
|
|
486
|
-
onChange(at);
|
|
487
|
-
}
|
|
488
|
-
}, children: render(raw) }, raw));
|
|
489
|
-
}) }, unit));
|
|
490
|
-
return (_jsxs("div", { ref: rootRef, className: cx('flex', density === 'compact' ? 'gap-0.5' : 'gap-1', className), style: { '--p-cell': cellSizes[size] }, children: [column('hour', labels.hour, hours, (raw) => raw === displayHour, (raw) => (hour12 ? String(raw) : pad(raw))), column('minute', labels.minute, minutes, (raw) => raw === base.getMinutes(), pad), showSeconds
|
|
491
|
-
? column('second', labels.second, seconds, (raw) => raw === base.getSeconds(), pad)
|
|
492
|
-
: null, hour12
|
|
493
|
-
? column('meridiem', labels.meridiem, [0, 1], (raw) => (base.getHours() >= 12 ? 1 : 0) === raw, (raw) => (raw === 0 ? am : pm))
|
|
494
|
-
: null, _jsx("span", { className: srOnlyClasses, "aria-live": "polite", children: value === null
|
|
495
|
-
? ''
|
|
496
|
-
: dateFormatter(locale, {
|
|
497
|
-
hour: 'numeric',
|
|
498
|
-
minute: '2-digit',
|
|
499
|
-
...(showSeconds ? { second: '2-digit' } : {})
|
|
500
|
-
}).format(value) })] }));
|
|
501
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as o}from"react/jsx-runtime";import*as t from"react";import{PlButton as r}from"../components/button/PlButton.js";import{ChevronIcon as s}from"./icons.js";import{dateFormatter as n}from"./format.js";import{addDays as l,addMonths as a,addYears as i,calendarWeeks as c,compareDay as u,daysInMonth as d,isDayOutside as m,isMonthBeforeYear as h,isSameDay as p,isSameMonth as g,isValidDate as f,makeDate as v,meridiemLabels as x,monthLabels as b,startOfDay as y,startOfMonth as w,today as D,weekdayLabels as N,withTime as C,YEAR_PAGE_SIZE as M,yearPageStart as S}from"./date.js";import{controlHeightClasses as P,controlTextClasses as k,cx as Y,gapClasses as z,metaTextClasses as A,srOnlyClasses as F,transitionClasses as j}from"./styles.js";export const defaultPickerLabels={previousMonth:"Previous month",nextMonth:"Next month",previousYear:"Previous year",nextYear:"Next year",previousYears:"Previous years",nextYears:"Next years",chooseMonth:"Choose a month",chooseYear:"Choose a year",today:"Today",now:"Now",clear:"Clear",done:"Done",hour:"Hour",minute:"Minute",second:"Second",meridiem:"AM/PM",start:"Start",end:"End"};export function usePickerLabels(e){return t.useMemo(()=>e?{...defaultPickerLabels,...e}:defaultPickerLabels,[e])}const E={xs:"1.5rem",sm:"2rem",md:"2.5rem",lg:"3rem",xl:"3.5rem"},H={xs:"rounded-(--plass-radius-xs)",sm:"rounded-(--plass-radius-xs)",md:"rounded-(--plass-radius-sm)",lg:"rounded-(--plass-radius-md)",xl:"rounded-(--plass-radius-lg)"},R={xs:"rounded-s-(--plass-radius-xs)",sm:"rounded-s-(--plass-radius-xs)",md:"rounded-s-(--plass-radius-sm)",lg:"rounded-s-(--plass-radius-md)",xl:"rounded-s-(--plass-radius-lg)"},T={xs:"rounded-e-(--plass-radius-xs)",sm:"rounded-e-(--plass-radius-xs)",md:"rounded-e-(--plass-radius-sm)",lg:"rounded-e-(--plass-radius-md)",xl:"rounded-e-(--plass-radius-lg)"},B=/* @__PURE__ */["relative flex items-center justify-center tabular-nums select-none","[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]",j,"focus-visible:z-10 focus-visible:[outline:2px_solid_var(--p-ring)]","focus-visible:[outline-offset:-2px]"].join(" ");function Cell({children:t,label:r,selected:s,inRange:n=!1,rangeEdge:l=null,current:a=!1,muted:i=!1,disabled:c=!1,focused:u=!1,size:d,className:m,onClick:h,onPointerEnter:p,onKeyDown:g}){return o("button",{type:"button",role:"gridcell","aria-label":r,"aria-selected":s,"aria-current":a?"date":void 0,"aria-disabled":c||void 0,tabIndex:u?0:-1,"data-focus-target":u?"true":void 0,className:Y(B,"start"===l?R[d]:"end"===l?T[d]:"middle"===l?"rounded-none":H[d],c?"cursor-not-allowed text-(--plass-muted-fg) opacity-50":s?"cursor-pointer font-semibold text-(--p-on-solid) [background-image:var(--p-fill)] hover:brightness-105 active:brightness-95":n?"cursor-pointer bg-(--p-soft) text-(--plass-fg) hover:bg-(--p-soft-hover) active:bg-(--p-soft-press)":a?"cursor-pointer font-semibold text-(--p-accent) hover:bg-(--p-soft) active:bg-(--p-soft-hover)":i?"cursor-pointer text-(--plass-muted-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)":"cursor-pointer text-(--plass-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)",m),onClick:()=>{c||h()},onPointerEnter:p,onKeyDown:g,children:[t,a?e("span",{"aria-hidden":"true",className:"absolute bottom-[0.18em] size-[0.22em] rounded-full bg-current"}):null]})}function Header({size:n,color:l,view:a,month:i,locale:c,labels:u,showPreviousButton:d,showNextButton:m,onStep:p,onViewChange:g}){const f=b(c,"long")[i.getMonth()],v=String(i.getFullYear()),x=h(c),y="day"===a?[u.previousMonth,u.nextMonth]:"month"===a?[u.previousYear,u.nextYear]:[u.previousYears,u.nextYears],stepper=(o,t)=>t?e(r,{variant:"ghost",size:n,color:l,density:"compact","aria-label":y[-1===o?0:1],onClick:()=>p(o),startIcon:e("span",{className:Y("flex items-center",-1===o?"rotate-90 rtl:-rotate-90":"-rotate-90 rtl:rotate-90"),children:e(s,{})})}):e("span",{"aria-hidden":"true",className:Y(P[n],"w-(--p-cell)")}),disclosure=o=>e("span",{className:Y("flex items-center text-(--plass-muted-fg)","[transition:rotate_var(--plass-duration)_var(--plass-ease)]",o&&"rotate-180"),children:e(s,{})}),w=e(r,{variant:"ghost",size:n,color:l,density:"compact","aria-label":u.chooseMonth,"aria-expanded":"month"===a,onClick:()=>g("month"===a?"day":"month"),endIcon:disclosure("month"===a),children:f},"month"),D=e(r,{variant:"ghost",size:n,color:l,density:"compact",className:"tabular-nums","aria-label":u.chooseYear,"aria-expanded":"year"===a,onClick:()=>g("year"===a?"day":"year"),endIcon:disclosure("year"===a),children:v},"year"),N=S(i.getFullYear());return o("div",{className:"flex items-center justify-between gap-1",children:[stepper(-1,d),e("div",{className:Y("flex min-w-0 flex-1 items-center justify-center",z[n]),children:"year"===a?e("span",{className:Y("flex items-center font-semibold tabular-nums text-(--plass-fg)",P[n],k[n]),children:`${N}–${N+M-1}`}):"month"===a?D:o(t.Fragment,x?{children:[w,D]}:{children:[D,w]})}),stepper(1,m)]})}export function Calendar({size:r,color:s,locale:n,weekStartsOn:l,month:c,onMonthChange:u,selected:d,rangeStart:h=null,rangeEnd:p=null,onSelect:x,onPreviewChange:b,minDate:N,maxDate:C,shouldDisableDate:S,showOutsideDays:P=!0,autoFocus:k=!1,showPreviousButton:A=!0,showNextButton:F=!0,labels:j,className:H}){const[R,T]=t.useState("day"),B=t.useMemo(()=>d.filter(f),[d]),[L,G]=t.useState(()=>{const e=B.find(e=>g(e,c));return e?y(e):g(D(),c)?D():w(c)}),K=t.useRef(k),I=t.useRef(null);t.useEffect(()=>{G(e=>g(e,c)?e:w(c))},[c]),t.useLayoutEffect(()=>{K.current&&(K.current=!1,I.current?.querySelector('[data-focus-target="true"]')?.focus({preventScroll:!0}))});const O=t.useCallback(e=>m(e,N,C)||(S?.(e)??!1),[N,C,S]),changeView=e=>{K.current=!0,T(e)},moveCursor=e=>{K.current=!0,u(e)};return o("div",{ref:I,className:Y("flex flex-col",z[r],H),style:{"--p-cell":E[r]},onPointerLeave:()=>b?.(null),children:[e(Header,{size:r,color:s,view:R,month:c,locale:n,labels:j,showPreviousButton:A,showNextButton:F,onStep:e=>{u("day"===R?a(c,e):i(c,"month"===R?e:e*M))},onViewChange:changeView}),e("div",{className:"h-[calc(var(--p-cell)*7)] w-[calc(var(--p-cell)*7)]",children:"day"===R?e(DayGrid,{size:r,locale:n,weekStartsOn:l,month:c,chosen:B,rangeStart:h,rangeEnd:p,focusedDate:L,showOutsideDays:P,isDisabled:O,onSelect:e=>{G(e),x(e)},onPreviewChange:b,onMoveFocus:e=>{K.current=!0,G(e),g(e,c)||u(w(e))}}):"month"===R?e(MonthGrid,{size:r,locale:n,month:c,chosen:B,minDate:N,maxDate:C,onMoveCursor:moveCursor,onPick:e=>{u(v(c.getFullYear(),e,1)),changeView("day")}}):e(YearGrid,{size:r,month:c,chosen:B,minDate:N,maxDate:C,onMoveCursor:moveCursor,onPick:e=>{u(v(e,c.getMonth(),1)),changeView("month")}})})]})}function DayGrid({size:t,locale:r,weekStartsOn:s,month:i,chosen:d,rangeStart:m,rangeEnd:h,focusedDate:v,showOutsideDays:x,isDisabled:b,onSelect:y,onPreviewChange:w,onMoveFocus:C}){const M=c(i,s),S=N(r,s,"short"),P=N(r,s,"long"),z=n(r,{dateStyle:"full"}),F=function orderedRange(e,o){return f(e)&&f(o)?u(e,o)<=0?[e,o]:[o,e]:null}(m,h),j=D();return o("div",{role:"grid",className:"flex h-full flex-col",children:[e("div",{role:"row",className:"grid grid-cols-7",children:S.map((o,r)=>e("span",{role:"columnheader","aria-label":P[r],className:Y("flex h-(--p-cell) items-center justify-center font-semibold text-(--plass-muted-fg) select-none",A[t]),children:o},r))}),M.map((o,r)=>e("div",{role:"row",className:"grid grid-cols-7",children:o.map(o=>{const r=!g(o,i);if(r&&!x)return e("span",{role:"gridcell","aria-hidden":"true",className:"size-(--p-cell)"},o.getTime());const n=d.some(e=>p(e,o)),c=null!==F&&u(o,F[0])>=0&&u(o,F[1])<=0,m=null!==F&&c&&p(o,F[0]),h=null!==F&&c&&p(o,F[1]);return e(Cell,{size:t,label:z.format(o),selected:n,inRange:c&&!n,rangeEdge:c?m&&h?"both":m?"start":h?"end":"middle":null,current:p(o,j)&&!n,muted:r,disabled:b(o),focused:p(o,v),className:Y("size-(--p-cell)",k[t]),onClick:()=>y(o),onPointerEnter:()=>w?.(o),onKeyDown:e=>((e,o)=>{const t=(o.getDay()-s+7)%7,r={ArrowLeft:()=>l(o,-1),ArrowRight:()=>l(o,1),ArrowUp:()=>l(o,-7),ArrowDown:()=>l(o,7),Home:()=>l(o,-t),End:()=>l(o,6-t),PageUp:()=>a(o,e.shiftKey?-12:-1),PageDown:()=>a(o,e.shiftKey?12:1)}[e.key];r&&(e.preventDefault(),C(r()))})(e,o),children:o.getDate()},o.getTime())})},r))]})}function MonthGrid({size:o,locale:t,month:r,chosen:s,minDate:n,maxDate:l,onMoveCursor:i,onPick:c}){const m=b(t,"short"),h=b(t,"long"),p=r.getFullYear(),g=new Date,onKeyDown=e=>{const o={ArrowLeft:-1,ArrowRight:1,ArrowUp:-3,ArrowDown:3,PageUp:-12,PageDown:12}[e.key];void 0!==o&&(e.preventDefault(),i(a(w(r),o)))};return e("div",{role:"grid",className:"flex h-full flex-col justify-evenly",children:[0,1,2,3].map(t=>e("div",{role:"row",className:"grid grid-cols-3 gap-1",children:[0,1,2].map(a=>{const i=3*t+a,x=v(p,i,1),b=v(p,i,d(p,i));return e(Cell,{size:o,label:`${h[i]} ${p}`,selected:s.some(e=>e.getFullYear()===p&&e.getMonth()===i),current:g.getFullYear()===p&&g.getMonth()===i,disabled:f(n)&&u(b,n)<0||f(l)&&u(x,l)>0,focused:i===r.getMonth(),className:Y("h-(--p-cell) w-full",k[o]),onClick:()=>c(i),onKeyDown,children:m[i]},i)})},t))})}function YearGrid({size:o,month:t,chosen:r,minDate:s,maxDate:n,onMoveCursor:l,onPick:a}){const c=S(t.getFullYear()),u=(new Date).getFullYear(),onKeyDown=e=>{const o={ArrowLeft:-1,ArrowRight:1,ArrowUp:-4,ArrowDown:4,PageUp:-M,PageDown:M}[e.key];void 0!==o&&(e.preventDefault(),l(i(w(t),o)))};return e("div",{role:"grid",className:"flex h-full flex-col justify-evenly",children:[0,1,2].map(l=>e("div",{role:"row",className:"grid grid-cols-4 gap-1",children:[0,1,2,3].map(i=>{const d=c+4*l+i;return e(Cell,{size:o,label:String(d),selected:r.some(e=>e.getFullYear()===d),current:d===u,disabled:f(s)&&d<s.getFullYear()||f(n)&&d>n.getFullYear(),focused:d===t.getFullYear(),className:Y("h-(--p-cell) w-full",k[o]),onClick:()=>a(d),onKeyDown,children:d},d)})},l))})}function revealInColumn(e){const o=e.parentElement;if(!o)return;const t=e.getBoundingClientRect(),r=o.getBoundingClientRect(),s=t.top-r.top-o.clientTop+o.scrollTop,n=s+t.height;s<o.scrollTop?o.scrollTop=s:n>o.scrollTop+o.clientHeight&&(o.scrollTop=n-o.clientHeight)}export function TimeGrid({size:r,density:s,locale:l,value:a,referenceDate:i,onChange:c,hour12:u,showSeconds:d,hourStep:m,minuteStep:h,secondStep:p,shouldDisableTime:g,autoFocus:f=!1,labels:v,className:b}){const y=a??i,w=t.useRef(null),[D,N]=t.useMemo(()=>x(l),[l]);t.useEffect(()=>{const e=w.current;if(e?.querySelectorAll('[data-chosen="true"]').forEach(revealInColumn),f){const o=e?.querySelector('[role="listbox"]');(o?.querySelector('[data-chosen="true"]')??o?.querySelector('[role="option"]'))?.focus({preventScroll:!0})}},[f]);const M=t.useMemo(()=>{const e=Math.ceil((u?12:24)/m),o=Array.from({length:e},(e,o)=>o*m);return u?o.map(e=>0===e?12:e):o},[u,m]),S=t.useMemo(()=>Array.from({length:Math.ceil(60/h)},(e,o)=>o*h),[h]),z=t.useMemo(()=>Array.from({length:Math.ceil(60/p)},(e,o)=>o*p),[p]),pad=e=>String(e).padStart(2,"0"),A=u?y.getHours()%12||12:y.getHours(),column=(o,t,s,n,l)=>e("div",{role:"listbox","aria-label":t,className:Y("flex flex-col gap-0.5 overflow-y-auto overscroll-contain","h-[calc(var(--p-cell)*7)] w-[calc(var(--p-cell)*1.75)]","scroll-py-0.5 [scrollbar-width:thin]"),children:s.map(t=>{const s=((e,o)=>C(y,"hour"===e?{hours:u?o%12+(y.getHours()>=12?12:0):o}:"minute"===e?{minutes:o}:"second"===e?{seconds:o}:{hours:y.getHours()%12+12*o}))(o,t),i=null!==a&&n(t),d=g?.(s,o)??!1;return e("button",{type:"button",role:"option","aria-selected":i,"aria-disabled":d||void 0,"data-chosen":i?"true":void 0,className:Y(B,H[r],P[r],k[r],"w-full shrink-0",d?"cursor-not-allowed text-(--plass-muted-fg) opacity-50":i?"cursor-pointer font-semibold text-(--p-on-solid) [background-image:var(--p-fill)] hover:brightness-105 active:brightness-95":"cursor-pointer text-(--plass-fg) hover:bg-(--p-soft) active:bg-(--p-soft-hover)"),onClick:()=>{d||c(s)},children:l(t)},t)})},o);return o("div",{ref:w,className:Y("flex","compact"===s?"gap-0.5":"gap-1",b),style:{"--p-cell":E[r]},children:[column("hour",v.hour,M,e=>e===A,e=>u?String(e):pad(e)),column("minute",v.minute,S,e=>e===y.getMinutes(),pad),d?column("second",v.second,z,e=>e===y.getSeconds(),pad):null,u?column("meridiem",v.meridiem,[0,1],e=>(y.getHours()>=12?1:0)===e,e=>0===e?D:N):null,e("span",{className:F,"aria-live":"polite",children:null===a?"":n(l,{hour:"numeric",minute:"2-digit",...d?{second:"2-digit"}:{}}).format(a)})]})}
|