@nepdatejs/react 0.0.0-bootstrap → 0.1.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/LICENSE +21 -0
- package/README.md +73 -1
- package/dist/date-text-BT82cZ7i.cjs +84 -0
- package/dist/date-text-BT82cZ7i.cjs.map +1 -0
- package/dist/date-text-Df1gFksR.js +61 -0
- package/dist/date-text-Df1gFksR.js.map +1 -0
- package/dist/date-text-Dipetpbp.d.cts +37 -0
- package/dist/date-text-Dipetpbp.d.ts +37 -0
- package/dist/headless-6BuUOtOR.d.cts +194 -0
- package/dist/headless-BK0XRHKb.js +513 -0
- package/dist/headless-BK0XRHKb.js.map +1 -0
- package/dist/headless-CFZ06zLn.d.ts +194 -0
- package/dist/headless-HxTkvgbz.cjs +572 -0
- package/dist/headless-HxTkvgbz.cjs.map +1 -0
- package/dist/headless.cjs +11 -0
- package/dist/headless.d.cts +2 -0
- package/dist/headless.d.ts +2 -0
- package/dist/headless.js +2 -0
- package/dist/index.cjs +891 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +245 -0
- package/dist/index.d.ts +245 -0
- package/dist/index.js +871 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +6 -0
- package/dist/server.d.cts +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1 -0
- package/package.json +107 -4
- package/src/bindings.ts +247 -0
- package/src/calendar-body.tsx +133 -0
- package/src/calendar.tsx +34 -0
- package/src/date-picker.tsx +350 -0
- package/src/date-range-picker.tsx +223 -0
- package/src/date-text.tsx +110 -0
- package/src/date-time-picker.tsx +287 -0
- package/src/headless.ts +21 -0
- package/src/hooks.ts +474 -0
- package/src/icons.tsx +35 -0
- package/src/index.ts +21 -0
- package/src/multi-date-picker.tsx +243 -0
- package/src/panel.ts +67 -0
- package/src/presets.tsx +44 -0
- package/src/server.ts +8 -0
- package/src/time-columns.tsx +60 -0
- package/src/use-store.ts +113 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
2
|
+
import type { CalendarBindings } from './bindings';
|
|
3
|
+
import { Icon } from './icons';
|
|
4
|
+
|
|
5
|
+
/** The calendar markup (@nepdatejs/styles contract) for any set of bindings. */
|
|
6
|
+
export function CalendarBody({
|
|
7
|
+
bindings,
|
|
8
|
+
className,
|
|
9
|
+
theme,
|
|
10
|
+
}: {
|
|
11
|
+
bindings: CalendarBindings;
|
|
12
|
+
className?: string | undefined;
|
|
13
|
+
theme?: string | undefined;
|
|
14
|
+
}): ReactElement {
|
|
15
|
+
const { state } = bindings;
|
|
16
|
+
const tiles =
|
|
17
|
+
state.view === 'month'
|
|
18
|
+
? state.months
|
|
19
|
+
: state.view === 'year'
|
|
20
|
+
? state.years
|
|
21
|
+
: state.fiscalYears;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={className ? `ndp-calendar ${className}` : 'ndp-calendar'}
|
|
26
|
+
data-ndp-theme={theme}
|
|
27
|
+
>
|
|
28
|
+
<div className="ndp-calendar__header">
|
|
29
|
+
<button className="ndp-icon-button" {...bindings.getPrevProps()}>
|
|
30
|
+
<Icon name="chevron-left" />
|
|
31
|
+
</button>
|
|
32
|
+
<div className="ndp-calendar__title" aria-live="polite">
|
|
33
|
+
{bindings.getTitleButtons().map((button) => (
|
|
34
|
+
<button
|
|
35
|
+
key={button.key}
|
|
36
|
+
className="ndp-calendar__title-button"
|
|
37
|
+
{...button.props}
|
|
38
|
+
>
|
|
39
|
+
{button.label}
|
|
40
|
+
</button>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
<button className="ndp-icon-button" {...bindings.getNextProps()}>
|
|
44
|
+
<Icon name="chevron-right" />
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
{state.view === 'day' ? (
|
|
48
|
+
<table className="ndp-grid" {...bindings.getGridProps()}>
|
|
49
|
+
<thead>
|
|
50
|
+
<tr role="row">
|
|
51
|
+
{state.weekdays.map((weekday) => (
|
|
52
|
+
<th
|
|
53
|
+
key={weekday.index}
|
|
54
|
+
className="ndp-weekday"
|
|
55
|
+
scope="col"
|
|
56
|
+
role="columnheader"
|
|
57
|
+
abbr={weekday.longLabel}
|
|
58
|
+
data-weekend={weekday.isWeekend || undefined}
|
|
59
|
+
>
|
|
60
|
+
{weekday.label}
|
|
61
|
+
</th>
|
|
62
|
+
))}
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
{state.weeks.map((week, row) => (
|
|
67
|
+
<tr key={row} role="row">
|
|
68
|
+
{week.map((cell, column) =>
|
|
69
|
+
cell ? (
|
|
70
|
+
<td
|
|
71
|
+
key={cell.key}
|
|
72
|
+
role="gridcell"
|
|
73
|
+
aria-selected={cell.isSelected || undefined}
|
|
74
|
+
>
|
|
75
|
+
<button className="ndp-day" {...bindings.getDayProps(cell)}>
|
|
76
|
+
<span className="ndp-sr-only">{cell.ariaLabel}</span>
|
|
77
|
+
<span className="ndp-day__label" aria-hidden="true">
|
|
78
|
+
{cell.label}
|
|
79
|
+
</span>
|
|
80
|
+
{cell.subLabel !== null && (
|
|
81
|
+
<span className="ndp-day__sub" aria-hidden="true">
|
|
82
|
+
{cell.subLabel}
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
{cell.events[0] && (
|
|
86
|
+
<span
|
|
87
|
+
className="ndp-day__dot"
|
|
88
|
+
aria-hidden="true"
|
|
89
|
+
style={
|
|
90
|
+
cell.events[0].color
|
|
91
|
+
? ({
|
|
92
|
+
'--ndp-dot-color': cell.events[0].color,
|
|
93
|
+
} as CSSProperties)
|
|
94
|
+
: undefined
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</button>
|
|
99
|
+
</td>
|
|
100
|
+
) : (
|
|
101
|
+
<td key={`empty-${column}`} role="gridcell" />
|
|
102
|
+
),
|
|
103
|
+
)}
|
|
104
|
+
</tr>
|
|
105
|
+
))}
|
|
106
|
+
</tbody>
|
|
107
|
+
</table>
|
|
108
|
+
) : (
|
|
109
|
+
<div className="ndp-options" role="group" aria-label={state.title}>
|
|
110
|
+
{tiles.map((tile, index) => (
|
|
111
|
+
<button
|
|
112
|
+
key={tile.value}
|
|
113
|
+
className="ndp-option"
|
|
114
|
+
{...bindings.getOptionProps(tile, index, tiles)}
|
|
115
|
+
>
|
|
116
|
+
<span className="ndp-sr-only">
|
|
117
|
+
{tile.subLabel ? `${tile.label} (${tile.subLabel})` : tile.label}
|
|
118
|
+
</span>
|
|
119
|
+
<span className="ndp-option__label" aria-hidden="true">
|
|
120
|
+
{tile.label}
|
|
121
|
+
</span>
|
|
122
|
+
{tile.subLabel !== null && (
|
|
123
|
+
<span className="ndp-option__sub" aria-hidden="true">
|
|
124
|
+
{tile.subLabel}
|
|
125
|
+
</span>
|
|
126
|
+
)}
|
|
127
|
+
</button>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
package/src/calendar.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { CalendarDate } from '@nepdatejs/core';
|
|
3
|
+
import type { CalendarOptions, CalendarSelection } from '@nepdatejs/calendar';
|
|
4
|
+
import { CalendarBody } from './calendar-body';
|
|
5
|
+
import { useCalendar } from './hooks';
|
|
6
|
+
|
|
7
|
+
export interface CalendarProps extends Omit<
|
|
8
|
+
CalendarOptions,
|
|
9
|
+
'onSelect' | 'visibleMonth' | 'onVisibleMonthChange'
|
|
10
|
+
> {
|
|
11
|
+
/** Show this month (any day in it); pair with onMonthChange to control it. */
|
|
12
|
+
month?: CalendarDate | undefined;
|
|
13
|
+
onMonthChange?: ((month: CalendarDate) => void) | undefined;
|
|
14
|
+
onSelect?: ((selection: CalendarSelection) => void) | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
theme?: 'light' | 'dark' | 'auto' | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A standalone BS/AD month view with keyboard support. */
|
|
20
|
+
export function Calendar({
|
|
21
|
+
month,
|
|
22
|
+
onMonthChange,
|
|
23
|
+
className,
|
|
24
|
+
theme,
|
|
25
|
+
...options
|
|
26
|
+
}: CalendarProps): ReactElement {
|
|
27
|
+
const bindings = useCalendar({
|
|
28
|
+
...options,
|
|
29
|
+
visibleMonth: month,
|
|
30
|
+
onVisibleMonthChange: onMonthChange,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return <CalendarBody bindings={bindings} className={className} theme={theme} />;
|
|
34
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { useEffect, useRef, type CSSProperties, type ReactElement } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
getLocale,
|
|
4
|
+
type CalendarDate,
|
|
5
|
+
type CalendarSystem,
|
|
6
|
+
type DateFormatPattern,
|
|
7
|
+
type LocaleCode,
|
|
8
|
+
type Weekday,
|
|
9
|
+
} from '@nepdatejs/core';
|
|
10
|
+
import type { EventMarker, HolidayInput, HolidayLookup } from '@nepdatejs/calendar';
|
|
11
|
+
import type { DateDetail, SinglePreset } from '@nepdatejs/datepicker';
|
|
12
|
+
import { restoreFocus } from '@nepdatejs/dom';
|
|
13
|
+
import { CalendarBody } from './calendar-body';
|
|
14
|
+
import { useDatePicker } from './hooks';
|
|
15
|
+
import { Icon } from './icons';
|
|
16
|
+
import { usePanel } from './panel';
|
|
17
|
+
import type { PanelLayout } from '@nepdatejs/dom';
|
|
18
|
+
import { PANEL_WIDTH, PANEL_WIDTH_WITH_PRESETS, PanelBody } from './presets';
|
|
19
|
+
|
|
20
|
+
export interface DatePickerProps {
|
|
21
|
+
/** The AD date as "YYYY-MM-DD", or "": the same value a native date input uses. */
|
|
22
|
+
value?: string | undefined;
|
|
23
|
+
/** The starting value when the field keeps its own state, as in a plain form with `name`. */
|
|
24
|
+
defaultValue?: string | undefined;
|
|
25
|
+
onChange?: ((value: string, date: DateDetail | null) => void) | undefined;
|
|
26
|
+
/** Show and type dates in this calendar only, and hide the BS/AD switch. */
|
|
27
|
+
calendar?: CalendarSystem | undefined;
|
|
28
|
+
/** The calendar shown first when the viewer has not chosen one. */
|
|
29
|
+
defaultCalendar?: CalendarSystem | undefined;
|
|
30
|
+
/** Called when the viewer switches BS/AD. */
|
|
31
|
+
onCalendarChange?: ((calendar: CalendarSystem) => void) | undefined;
|
|
32
|
+
/** Earliest and latest allowed days, as AD "YYYY-MM-DD". */
|
|
33
|
+
min?: string | undefined;
|
|
34
|
+
max?: string | undefined;
|
|
35
|
+
isDateDisabled?: ((date: CalendarDate) => boolean) | undefined;
|
|
36
|
+
/** Names, digits and messages; defaults to the page language (ne when <html lang="ne">). */
|
|
37
|
+
locale?: LocaleCode | undefined;
|
|
38
|
+
/** Submit the AD value with a plain form under this name. */
|
|
39
|
+
name?: string | undefined;
|
|
40
|
+
id?: string | undefined;
|
|
41
|
+
placeholder?: string | undefined;
|
|
42
|
+
required?: boolean | undefined;
|
|
43
|
+
disabled?: boolean | undefined;
|
|
44
|
+
clearable?: boolean | undefined;
|
|
45
|
+
/** Show the same day in the other calendar under the field. */
|
|
46
|
+
showEquivalent?: boolean | undefined;
|
|
47
|
+
holidays?: readonly HolidayInput[] | HolidayLookup | undefined;
|
|
48
|
+
events?: ((date: CalendarDate) => readonly EventMarker[]) | undefined;
|
|
49
|
+
weekStartsOn?: Weekday | undefined;
|
|
50
|
+
weekendDays?: readonly Weekday[] | undefined;
|
|
51
|
+
/** Show the other calendar's day number under each day. Default true. */
|
|
52
|
+
showOtherCalendar?: boolean | undefined;
|
|
53
|
+
displayFormat?: DateFormatPattern | undefined;
|
|
54
|
+
closeOnSelect?: boolean | undefined;
|
|
55
|
+
/** Shortcuts beside the calendar: 'today', 'yesterday', 'tomorrow' or { id, label, resolve }. */
|
|
56
|
+
presets?: readonly SinglePreset[] | undefined;
|
|
57
|
+
/** Remember the viewer's BS/AD choice in localStorage under this key. */
|
|
58
|
+
persistCalendar?: string | undefined;
|
|
59
|
+
/** Override "today" (tests, server rendering). */
|
|
60
|
+
today?: CalendarDate | undefined;
|
|
61
|
+
/** Add the dashes of YYYY-MM-DD while the user types digits. Default true. */
|
|
62
|
+
mask?: boolean | undefined;
|
|
63
|
+
/** Show the calendar in the page, always open, instead of in a popover. */
|
|
64
|
+
inline?: boolean | undefined;
|
|
65
|
+
/** 'sheet' opens a bottom sheet; 'popover' sits beside the field; 'auto' (default) uses the sheet below 480px. */
|
|
66
|
+
panelLayout?: PanelLayout | undefined;
|
|
67
|
+
theme?: 'light' | 'dark' | 'auto' | undefined;
|
|
68
|
+
className?: string | undefined;
|
|
69
|
+
'aria-label'?: string | undefined;
|
|
70
|
+
'aria-invalid'?: boolean | undefined;
|
|
71
|
+
'aria-describedby'?: string | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A BS/AD date field: type a date or pick it from a calendar. The value is
|
|
76
|
+
* always the AD day as "YYYY-MM-DD", so forms, validation and storage are
|
|
77
|
+
* unchanged and a BS date can never be sent as if it were AD.
|
|
78
|
+
*/
|
|
79
|
+
export function DatePicker(props: DatePickerProps): ReactElement {
|
|
80
|
+
const {
|
|
81
|
+
name,
|
|
82
|
+
placeholder,
|
|
83
|
+
required,
|
|
84
|
+
disabled = false,
|
|
85
|
+
clearable = true,
|
|
86
|
+
showEquivalent = true,
|
|
87
|
+
persistCalendar,
|
|
88
|
+
panelLayout,
|
|
89
|
+
theme,
|
|
90
|
+
className,
|
|
91
|
+
'aria-label': ariaLabel,
|
|
92
|
+
'aria-invalid': ariaInvalid,
|
|
93
|
+
'aria-describedby': ariaDescribedBy,
|
|
94
|
+
locale: localeProp,
|
|
95
|
+
onCalendarChange,
|
|
96
|
+
...options
|
|
97
|
+
} = props;
|
|
98
|
+
const locale = localeProp ?? pageLocale();
|
|
99
|
+
const picker = useDatePicker({
|
|
100
|
+
...options,
|
|
101
|
+
locale,
|
|
102
|
+
onCalendarChange: (calendar: CalendarSystem) => {
|
|
103
|
+
remember(persistCalendar, calendar);
|
|
104
|
+
onCalendarChange?.(calendar);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const { state, store, calendar } = picker;
|
|
108
|
+
const labels = getLocale(locale);
|
|
109
|
+
const field = useRef<HTMLDivElement | null>(null);
|
|
110
|
+
const box = useRef<HTMLDivElement | null>(null);
|
|
111
|
+
const panel = useRef<HTMLDivElement | null>(null);
|
|
112
|
+
const input = useRef<HTMLInputElement | null>(null);
|
|
113
|
+
const hidden = useRef<HTMLInputElement | null>(null);
|
|
114
|
+
|
|
115
|
+
const panelWidth = state.presets.length > 0 ? PANEL_WIDTH_WITH_PRESETS : PANEL_WIDTH;
|
|
116
|
+
usePanel(
|
|
117
|
+
state.open && !state.inline,
|
|
118
|
+
{ anchor: box, panel, field, returnFocus: input },
|
|
119
|
+
() => {
|
|
120
|
+
store.actions.close();
|
|
121
|
+
},
|
|
122
|
+
calendar.focusGrid,
|
|
123
|
+
panelWidth,
|
|
124
|
+
panelLayout,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// The viewer's last BS/AD choice, read after hydration so server and client render alike.
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const stored = read(persistCalendar);
|
|
130
|
+
if (stored) {
|
|
131
|
+
store.actions.switchCalendar(stored);
|
|
132
|
+
}
|
|
133
|
+
}, [persistCalendar, store]);
|
|
134
|
+
|
|
135
|
+
// A form reset puts the field back to its default value.
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const form = hidden.current?.form ?? input.current?.form;
|
|
138
|
+
if (!form) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const reset = (): void => {
|
|
142
|
+
store.actions.reset();
|
|
143
|
+
};
|
|
144
|
+
form.addEventListener('reset', reset);
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
form.removeEventListener('reset', reset);
|
|
148
|
+
};
|
|
149
|
+
}, [store]);
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
input.current?.setCustomValidity(state.error?.message ?? '');
|
|
153
|
+
}, [state.error]);
|
|
154
|
+
|
|
155
|
+
const hint = state.error?.message ?? (showEquivalent ? state.equivalentText : null);
|
|
156
|
+
const inputProps = picker.getInputProps();
|
|
157
|
+
const describedBy =
|
|
158
|
+
[ariaDescribedBy, hint ? picker.ids.hint : undefined].filter(Boolean).join(' ') ||
|
|
159
|
+
undefined;
|
|
160
|
+
const invalid = ariaInvalid === true || state.error !== null;
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div
|
|
164
|
+
ref={field}
|
|
165
|
+
className={className ? `ndp-field ${className}` : 'ndp-field'}
|
|
166
|
+
data-ndp-theme={theme}
|
|
167
|
+
>
|
|
168
|
+
<div
|
|
169
|
+
ref={box}
|
|
170
|
+
className="ndp-input"
|
|
171
|
+
data-invalid={invalid || undefined}
|
|
172
|
+
data-disabled={disabled || undefined}
|
|
173
|
+
>
|
|
174
|
+
<input
|
|
175
|
+
ref={input}
|
|
176
|
+
className="ndp-input__control"
|
|
177
|
+
{...inputProps}
|
|
178
|
+
aria-describedby={describedBy}
|
|
179
|
+
aria-invalid={invalid || undefined}
|
|
180
|
+
aria-label={ariaLabel}
|
|
181
|
+
placeholder={placeholder ?? state.placeholder}
|
|
182
|
+
required={required}
|
|
183
|
+
disabled={disabled}
|
|
184
|
+
/>
|
|
185
|
+
<span className="ndp-input__actions">
|
|
186
|
+
{clearable && state.text !== '' && !disabled && (
|
|
187
|
+
<button
|
|
188
|
+
className="ndp-icon-button"
|
|
189
|
+
{...picker.getClearProps()}
|
|
190
|
+
onClick={() => {
|
|
191
|
+
store.actions.clear();
|
|
192
|
+
input.current?.focus();
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
<Icon name="close" />
|
|
196
|
+
</button>
|
|
197
|
+
)}
|
|
198
|
+
{state.canSwitchCalendar && (
|
|
199
|
+
<button
|
|
200
|
+
className="ndp-input__switch"
|
|
201
|
+
disabled={disabled}
|
|
202
|
+
{...picker.getSwitchProps()}
|
|
203
|
+
>
|
|
204
|
+
{labels.calendarNames[state.calendar]}
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
{!state.inline && (
|
|
208
|
+
<button
|
|
209
|
+
className="ndp-icon-button"
|
|
210
|
+
disabled={disabled}
|
|
211
|
+
{...picker.getToggleProps()}
|
|
212
|
+
>
|
|
213
|
+
<Icon name="calendar" />
|
|
214
|
+
</button>
|
|
215
|
+
)}
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
{hint !== null && (
|
|
219
|
+
<p
|
|
220
|
+
id={picker.ids.hint}
|
|
221
|
+
className="ndp-hint"
|
|
222
|
+
data-error={state.error ? true : undefined}
|
|
223
|
+
aria-live="polite"
|
|
224
|
+
>
|
|
225
|
+
{hint}
|
|
226
|
+
</p>
|
|
227
|
+
)}
|
|
228
|
+
{name !== undefined && (
|
|
229
|
+
<input
|
|
230
|
+
ref={hidden}
|
|
231
|
+
type="hidden"
|
|
232
|
+
name={name}
|
|
233
|
+
value={state.value}
|
|
234
|
+
disabled={disabled}
|
|
235
|
+
/>
|
|
236
|
+
)}
|
|
237
|
+
{state.open && (
|
|
238
|
+
<div
|
|
239
|
+
ref={panel}
|
|
240
|
+
className="ndp-panel"
|
|
241
|
+
popover={state.inline ? undefined : 'manual'}
|
|
242
|
+
data-layout={state.inline ? 'inline' : undefined}
|
|
243
|
+
style={
|
|
244
|
+
state.inline
|
|
245
|
+
? ({ '--ndp-panel-width': `${String(panelWidth)}px` } as CSSProperties)
|
|
246
|
+
: undefined
|
|
247
|
+
}
|
|
248
|
+
data-ndp-theme={theme}
|
|
249
|
+
{...picker.getPanelProps()}
|
|
250
|
+
aria-label={
|
|
251
|
+
ariaLabel
|
|
252
|
+
? `${ariaLabel}: ${labels.labels.calendar}`
|
|
253
|
+
: labels.labels.chooseDate
|
|
254
|
+
}
|
|
255
|
+
>
|
|
256
|
+
<PanelBody
|
|
257
|
+
presets={state.presets}
|
|
258
|
+
label={labels.labels.presets}
|
|
259
|
+
onPreset={(id) => {
|
|
260
|
+
if (store.actions.applyPreset(id) && !store.getState().open) {
|
|
261
|
+
restoreFocus(input.current);
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
<CalendarBody
|
|
266
|
+
bindings={{
|
|
267
|
+
...calendar,
|
|
268
|
+
getDayProps: (cell) => ({
|
|
269
|
+
...calendar.getDayProps(cell),
|
|
270
|
+
onClick: () => {
|
|
271
|
+
if (
|
|
272
|
+
!cell.isDisabled &&
|
|
273
|
+
store.actions.select(cell.date) &&
|
|
274
|
+
!store.getState().open
|
|
275
|
+
) {
|
|
276
|
+
restoreFocus(input.current);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
}}
|
|
281
|
+
/>
|
|
282
|
+
</PanelBody>
|
|
283
|
+
<div className="ndp-panel__footer">
|
|
284
|
+
{state.canSwitchCalendar ? (
|
|
285
|
+
<div
|
|
286
|
+
className="ndp-segment"
|
|
287
|
+
role="group"
|
|
288
|
+
aria-label={labels.labels.calendar}
|
|
289
|
+
>
|
|
290
|
+
{(['BS', 'AD'] as const).map((option) => (
|
|
291
|
+
<button
|
|
292
|
+
key={option}
|
|
293
|
+
className="ndp-segment__item"
|
|
294
|
+
{...picker.getCalendarOptionProps(option)}
|
|
295
|
+
>
|
|
296
|
+
{labels.calendarNames[option]}
|
|
297
|
+
</button>
|
|
298
|
+
))}
|
|
299
|
+
</div>
|
|
300
|
+
) : (
|
|
301
|
+
<span />
|
|
302
|
+
)}
|
|
303
|
+
<button
|
|
304
|
+
className="ndp-button"
|
|
305
|
+
{...picker.getTodayProps()}
|
|
306
|
+
onClick={() => {
|
|
307
|
+
if (store.actions.selectToday()) {
|
|
308
|
+
restoreFocus(input.current);
|
|
309
|
+
}
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
{labels.labels.today}
|
|
313
|
+
</button>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Nepali names and digits when the page language is Nepali. */
|
|
322
|
+
function pageLocale(): LocaleCode {
|
|
323
|
+
return typeof document !== 'undefined' &&
|
|
324
|
+
document.documentElement.lang.toLowerCase().startsWith('ne')
|
|
325
|
+
? 'ne'
|
|
326
|
+
: 'en';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function read(key: string | undefined): CalendarSystem | null {
|
|
330
|
+
if (!key) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const stored = window.localStorage.getItem(key);
|
|
335
|
+
return stored === 'AD' || stored === 'BS' ? stored : null;
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function remember(key: string | undefined, calendar: CalendarSystem): void {
|
|
342
|
+
if (!key) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
window.localStorage.setItem(key, calendar);
|
|
347
|
+
} catch {
|
|
348
|
+
// Storage can be unavailable (private mode); the choice then lasts for this page only.
|
|
349
|
+
}
|
|
350
|
+
}
|