@nepdatejs/react 0.0.0-stage → 0.1.1

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/dist/date-text-BX3Uag0O.cjs +84 -0
  4. package/dist/date-text-BX3Uag0O.cjs.map +1 -0
  5. package/dist/date-text-DLwhBmYj.js +61 -0
  6. package/dist/date-text-DLwhBmYj.js.map +1 -0
  7. package/dist/date-text-Dipetpbp.d.cts +37 -0
  8. package/dist/date-text-Dipetpbp.d.ts +37 -0
  9. package/dist/headless-AEI6dxTJ.cjs +619 -0
  10. package/dist/headless-AEI6dxTJ.cjs.map +1 -0
  11. package/dist/headless-CVj0DhD4.js +560 -0
  12. package/dist/headless-CVj0DhD4.js.map +1 -0
  13. package/dist/headless-ChE3zuYj.d.ts +195 -0
  14. package/dist/headless-CrIIPnOF.d.cts +195 -0
  15. package/dist/headless.cjs +11 -0
  16. package/dist/headless.d.cts +2 -0
  17. package/dist/headless.d.ts +2 -0
  18. package/dist/headless.js +2 -0
  19. package/dist/index.cjs +915 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +245 -0
  22. package/dist/index.d.ts +245 -0
  23. package/dist/index.js +895 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/server.cjs +6 -0
  26. package/dist/server.d.cts +2 -0
  27. package/dist/server.d.ts +2 -0
  28. package/dist/server.js +1 -0
  29. package/package.json +110 -3
  30. package/src/bindings.ts +247 -0
  31. package/src/calendar-body.tsx +133 -0
  32. package/src/calendar.tsx +34 -0
  33. package/src/date-picker.tsx +343 -0
  34. package/src/date-range-picker.tsx +225 -0
  35. package/src/date-text.tsx +110 -0
  36. package/src/date-time-picker.tsx +289 -0
  37. package/src/headless.ts +21 -0
  38. package/src/hooks.ts +476 -0
  39. package/src/icons.tsx +35 -0
  40. package/src/index.ts +21 -0
  41. package/src/multi-date-picker.tsx +245 -0
  42. package/src/page-locale.ts +23 -0
  43. package/src/panel.ts +67 -0
  44. package/src/presets.tsx +44 -0
  45. package/src/server.ts +8 -0
  46. package/src/time-columns.tsx +60 -0
  47. package/src/use-store.ts +196 -0
@@ -0,0 +1,225 @@
1
+ import { useRef, type CSSProperties, type ReactElement } from 'react';
2
+ import {
3
+ getLocale,
4
+ type CalendarDate,
5
+ type CalendarSystem,
6
+ type LocaleCode,
7
+ type Weekday,
8
+ } from '@nepdatejs/core';
9
+ import type { HolidayInput, HolidayLookup } from '@nepdatejs/calendar';
10
+ import type { RangeDetail, RangePreset, RangeValue } from '@nepdatejs/datepicker';
11
+ import { restoreFocus } from '@nepdatejs/dom';
12
+ import { CalendarBody } from './calendar-body';
13
+ import { useDateRangePicker } from './hooks';
14
+ import { Icon } from './icons';
15
+ import { usePageLocale } from './page-locale';
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 DateRangePickerProps {
21
+ value?: RangeValue | undefined;
22
+ defaultValue?: RangeValue | undefined;
23
+ onChange?: ((value: RangeValue, detail: RangeDetail) => void) | undefined;
24
+ calendar?: CalendarSystem | undefined;
25
+ defaultCalendar?: CalendarSystem | undefined;
26
+ min?: string | undefined;
27
+ max?: string | undefined;
28
+ minDays?: number | undefined;
29
+ maxDays?: number | undefined;
30
+ /** Shortcuts beside the calendar: 'last7Days', 'thisMonth', 'thisFiscalYear', … or { id, label, resolve }. */
31
+ presets?: readonly RangePreset[] | undefined;
32
+ isDateDisabled?: ((date: CalendarDate) => boolean) | undefined;
33
+ holidays?: readonly HolidayInput[] | HolidayLookup | undefined;
34
+ locale?: LocaleCode | undefined;
35
+ weekStartsOn?: Weekday | undefined;
36
+ weekendDays?: readonly Weekday[] | undefined;
37
+ showOtherCalendar?: boolean | undefined;
38
+ today?: CalendarDate | undefined;
39
+ /** Field names for plain form posts. */
40
+ startName?: string | undefined;
41
+ endName?: string | undefined;
42
+ startLabel?: string | undefined;
43
+ endLabel?: string | undefined;
44
+ /** Show a "Fiscal year" button that switches the panel to fiscal-year tiles. */
45
+ fiscalYearView?: boolean | undefined;
46
+ disabled?: boolean | undefined;
47
+ /** Add the dashes of YYYY-MM-DD while the user types digits. Default true. */
48
+ mask?: boolean | undefined;
49
+ /** Show the calendar in the page, always open, instead of in a popover. */
50
+ inline?: boolean | undefined;
51
+ /** 'sheet' opens a bottom sheet; 'popover' sits beside the field; 'auto' (default) uses the sheet below 480px. */
52
+ panelLayout?: PanelLayout | undefined;
53
+ theme?: 'light' | 'dark' | 'auto' | undefined;
54
+ className?: string | undefined;
55
+ }
56
+
57
+ /** Two BS/AD date fields sharing one calendar panel in range mode. */
58
+ export function DateRangePicker({
59
+ startName,
60
+ endName,
61
+ startLabel,
62
+ endLabel,
63
+ fiscalYearView = false,
64
+ disabled = false,
65
+ panelLayout,
66
+ theme,
67
+ className,
68
+ ...options
69
+ }: DateRangePickerProps): ReactElement {
70
+ const locale = usePageLocale(options.locale);
71
+ const picker = useDateRangePicker({ ...options, locale });
72
+ const { state, store, calendar } = picker;
73
+ const labels = getLocale(locale);
74
+ const field = useRef<HTMLDivElement | null>(null);
75
+ const row = useRef<HTMLDivElement | null>(null);
76
+ const panel = useRef<HTMLDivElement | null>(null);
77
+ const start = useRef<HTMLInputElement | null>(null);
78
+
79
+ const panelWidth = state.presets.length > 0 ? PANEL_WIDTH_WITH_PRESETS : PANEL_WIDTH;
80
+ usePanel(
81
+ state.open && !state.inline,
82
+ { anchor: row, panel, field, returnFocus: start },
83
+ () => {
84
+ store.actions.close();
85
+ },
86
+ calendar.focusGrid,
87
+ panelWidth,
88
+ panelLayout,
89
+ );
90
+
91
+ const invalid = (which: 'start' | 'end'): boolean =>
92
+ state.error !== null &&
93
+ (state.error.field === which || state.error.field === 'range');
94
+
95
+ return (
96
+ <div
97
+ ref={field}
98
+ className={className ? `ndp-field ${className}` : 'ndp-field'}
99
+ data-ndp-theme={theme}
100
+ >
101
+ <div ref={row} className="ndp-range">
102
+ <div
103
+ className="ndp-input"
104
+ data-invalid={invalid('start') || undefined}
105
+ data-disabled={disabled || undefined}
106
+ >
107
+ <input
108
+ ref={start}
109
+ className="ndp-input__control"
110
+ {...picker.getInputProps('start')}
111
+ aria-label={startLabel}
112
+ disabled={disabled}
113
+ />
114
+ </div>
115
+ <span className="ndp-range__separator" aria-hidden="true">
116
+ →
117
+ </span>
118
+ <div
119
+ className="ndp-input"
120
+ data-invalid={invalid('end') || undefined}
121
+ data-disabled={disabled || undefined}
122
+ >
123
+ <input
124
+ className="ndp-input__control"
125
+ {...picker.getInputProps('end')}
126
+ aria-label={endLabel}
127
+ disabled={disabled}
128
+ />
129
+ {!state.inline && (
130
+ <span className="ndp-input__actions">
131
+ <button
132
+ className="ndp-icon-button"
133
+ disabled={disabled}
134
+ {...picker.getToggleProps()}
135
+ >
136
+ <Icon name="calendar" />
137
+ </button>
138
+ </span>
139
+ )}
140
+ </div>
141
+ </div>
142
+ {state.error && (
143
+ <p id={picker.ids.hint} className="ndp-hint" data-error aria-live="polite">
144
+ {state.error.message}
145
+ </p>
146
+ )}
147
+ {startName !== undefined && (
148
+ <input
149
+ type="hidden"
150
+ name={startName}
151
+ value={state.value.start}
152
+ disabled={disabled}
153
+ />
154
+ )}
155
+ {endName !== undefined && (
156
+ <input type="hidden" name={endName} value={state.value.end} disabled={disabled} />
157
+ )}
158
+ {state.open && (
159
+ <div
160
+ ref={panel}
161
+ className="ndp-panel"
162
+ popover={state.inline ? undefined : 'manual'}
163
+ data-layout={state.inline ? 'inline' : undefined}
164
+ style={
165
+ state.inline
166
+ ? ({ '--ndp-panel-width': `${String(panelWidth)}px` } as CSSProperties)
167
+ : undefined
168
+ }
169
+ data-ndp-theme={theme}
170
+ {...picker.getPanelProps()}
171
+ >
172
+ <PanelBody
173
+ presets={state.presets}
174
+ label={labels.labels.presets}
175
+ onPreset={(id) => {
176
+ if (store.actions.applyPreset(id) && !store.getState().open) {
177
+ restoreFocus(start.current);
178
+ }
179
+ }}
180
+ >
181
+ <CalendarBody bindings={calendar} />
182
+ </PanelBody>
183
+ {(state.canSwitchCalendar || fiscalYearView) && (
184
+ <div className="ndp-panel__footer">
185
+ {state.canSwitchCalendar ? (
186
+ <div
187
+ className="ndp-segment"
188
+ role="group"
189
+ aria-label={labels.labels.calendar}
190
+ >
191
+ {(['BS', 'AD'] as const).map((option) => (
192
+ <button
193
+ key={option}
194
+ className="ndp-segment__item"
195
+ {...picker.getCalendarOptionProps(option)}
196
+ >
197
+ {labels.calendarNames[option]}
198
+ </button>
199
+ ))}
200
+ </div>
201
+ ) : (
202
+ <span />
203
+ )}
204
+ {fiscalYearView && (
205
+ <button
206
+ type="button"
207
+ className="ndp-button"
208
+ aria-pressed={state.calendarState.view === 'fiscal'}
209
+ onClick={() => {
210
+ store.calendar.actions.setView(
211
+ state.calendarState.view === 'fiscal' ? 'day' : 'fiscal',
212
+ );
213
+ calendar.focusGrid();
214
+ }}
215
+ >
216
+ {labels.labels.fiscalYear}
217
+ </button>
218
+ )}
219
+ </div>
220
+ )}
221
+ </div>
222
+ )}
223
+ </div>
224
+ );
225
+ }
@@ -0,0 +1,110 @@
1
+ import type { ReactElement } from 'react';
2
+ import {
3
+ formatDate,
4
+ fromIsoDate,
5
+ fromIsoTimestamp,
6
+ toCalendar,
7
+ type CalendarSystem,
8
+ type DateFormatPattern,
9
+ type LocaleCode,
10
+ } from '@nepdatejs/core';
11
+
12
+ export interface DateTextProps {
13
+ /** An AD date or timestamp from the server ("2026-09-26" or an ISO timestamp). */
14
+ value: string | null | undefined;
15
+ /** Which calendar to show first. Default 'BS'. */
16
+ calendar?: CalendarSystem | undefined;
17
+ /** Also show the day in the other calendar, muted. Default true. */
18
+ withEquivalent?: boolean | undefined;
19
+ format?: DateFormatPattern | undefined;
20
+ locale?: LocaleCode | undefined;
21
+ fallback?: string | undefined;
22
+ className?: string | undefined;
23
+ }
24
+
25
+ /**
26
+ * A server date shown in BS (or AD), with the other calendar alongside:
27
+ * "10 Ashwin 2083 · 26 Sep 2026". No hooks, so it works in server components.
28
+ */
29
+ export function DateText({
30
+ value,
31
+ calendar = 'BS',
32
+ withEquivalent = true,
33
+ format = 'long',
34
+ locale,
35
+ fallback = '—',
36
+ className,
37
+ }: DateTextProps): ReactElement {
38
+ const date = fromIsoDate(value);
39
+ if (!date) {
40
+ return <span className={className}>{fallback}</span>;
41
+ }
42
+ const other: CalendarSystem = calendar === 'BS' ? 'AD' : 'BS';
43
+
44
+ return (
45
+ <time dateTime={formatDate(date, 'Y-m-d')} className={className}>
46
+ {formatDate(toCalendar(date, calendar), format, locale)}
47
+ {withEquivalent && (
48
+ <span className="ndp-muted" style={{ color: 'var(--ndp-text-muted, #64748b)' }}>
49
+ {' · '}
50
+ {formatDate(toCalendar(date, other), other === 'AD' ? 'j M Y' : format, locale)}
51
+ </span>
52
+ )}
53
+ </time>
54
+ );
55
+ }
56
+
57
+ /**
58
+ * A server date as short text: "12 Ashwin 2083", keeping the time of day when
59
+ * the value has one. A timestamp with a zone is read in the configured zone
60
+ * (Nepal by default). Anything that is not a date is returned unchanged.
61
+ */
62
+ export function shortDate(value: string | null | undefined): string | null {
63
+ if (!value) {
64
+ return null;
65
+ }
66
+ const instant = fromIsoTimestamp(value);
67
+ const date = instant?.date ?? fromIsoDate(value);
68
+ if (!date) {
69
+ return value;
70
+ }
71
+ const time = instant?.time ?? /[T ](\d{2}:\d{2})/.exec(value)?.[1];
72
+
73
+ return `${formatDate(toCalendar(date, 'BS'), 'j F Y')}${time ? `, ${time}` : ''}`;
74
+ }
75
+
76
+ /** A server date as documents print it, in both calendars: "31 Shrawan 2083 (16 Aug 2026)". */
77
+ export function bothDates(value: string | null | undefined): string {
78
+ const date = fromIsoDate(value);
79
+
80
+ return date
81
+ ? `${formatDate(toCalendar(date, 'BS'), 'j F Y')} (${formatDate(date, 'j M Y')})`
82
+ : (value ?? '');
83
+ }
84
+
85
+ /** A server date for a table cell or list row, with the AD day on hover. */
86
+ export function ShortDate({
87
+ value,
88
+ fallback = '—',
89
+ className,
90
+ }: {
91
+ value: string | null | undefined;
92
+ fallback?: string | undefined;
93
+ className?: string | undefined;
94
+ }): ReactElement {
95
+ const text = shortDate(value);
96
+ const date = fromIsoDate(value);
97
+ if (!text) {
98
+ return <span className={className}>{fallback}</span>;
99
+ }
100
+
101
+ return (
102
+ <time
103
+ dateTime={value ?? undefined}
104
+ title={date ? formatDate(date, 'j M Y') : undefined}
105
+ className={className}
106
+ >
107
+ {text}
108
+ </time>
109
+ );
110
+ }
@@ -0,0 +1,289 @@
1
+ import { useRef, type CSSProperties, type ReactElement } from 'react';
2
+ import {
3
+ getLocale,
4
+ type CalendarDate,
5
+ type CalendarSystem,
6
+ type LocaleCode,
7
+ type Weekday,
8
+ } from '@nepdatejs/core';
9
+ import type { HolidayInput, HolidayLookup } from '@nepdatejs/calendar';
10
+ import type { DateTimeDetail, SinglePreset } from '@nepdatejs/datepicker';
11
+ import { CalendarBody } from './calendar-body';
12
+ import { popupProps, useDateTimePicker } from './hooks';
13
+ import { Icon } from './icons';
14
+ import { usePageLocale } from './page-locale';
15
+ import { usePanel } from './panel';
16
+ import type { PanelLayout } from '@nepdatejs/dom';
17
+ import {
18
+ PANEL_WIDTH,
19
+ PANEL_WIDTH_WITH_PRESETS,
20
+ PanelBody,
21
+ TIME_COLUMNS_WIDTH,
22
+ } from './presets';
23
+ import { TimeColumns } from './time-columns';
24
+
25
+ export interface DateTimePickerProps {
26
+ /** ISO timestamp with offset ("2026-10-18T14:30:00+05:45") or "". */
27
+ value?: string | undefined;
28
+ defaultValue?: string | undefined;
29
+ onChange?: ((value: string, detail: DateTimeDetail | null) => void) | undefined;
30
+ /** The zone the picker shows and the value's offset. Default Asia/Kathmandu (core config). */
31
+ timeZone?: string | undefined;
32
+ hour12?: boolean | undefined;
33
+ /** Minutes in the column (1–30). Default 5. */
34
+ minuteStep?: number | undefined;
35
+ /** Time used when a day is picked first. Default "00:00". */
36
+ defaultTime?: string | undefined;
37
+ calendar?: CalendarSystem | undefined;
38
+ defaultCalendar?: CalendarSystem | undefined;
39
+ /** Earliest and latest days, AD "YYYY-MM-DD". */
40
+ min?: string | undefined;
41
+ max?: string | undefined;
42
+ isDateDisabled?: ((date: CalendarDate) => boolean) | undefined;
43
+ holidays?: readonly HolidayInput[] | HolidayLookup | undefined;
44
+ presets?: readonly SinglePreset[] | undefined;
45
+ locale?: LocaleCode | undefined;
46
+ weekStartsOn?: Weekday | undefined;
47
+ today?: CalendarDate | undefined;
48
+ /** Submit the ISO value with a plain form. */
49
+ name?: string | undefined;
50
+ id?: string | undefined;
51
+ required?: boolean | undefined;
52
+ disabled?: boolean | undefined;
53
+ /** Add the dashes of YYYY-MM-DD while the user types digits. Default true. */
54
+ mask?: boolean | undefined;
55
+ /** Show the calendar in the page, always open, instead of in a popover. */
56
+ inline?: boolean | undefined;
57
+ /** 'sheet' opens a bottom sheet; 'popover' sits beside the field; 'auto' (default) uses the sheet below 480px. */
58
+ panelLayout?: PanelLayout | undefined;
59
+ theme?: 'light' | 'dark' | 'auto' | undefined;
60
+ className?: string | undefined;
61
+ /** Accessible names for the two inputs. */
62
+ 'aria-label'?: string | undefined;
63
+ timeLabel?: string | undefined;
64
+ }
65
+
66
+ /** A BS/AD date and time field. The value is one instant, with the zone's offset. */
67
+ export function DateTimePicker({
68
+ name,
69
+ required,
70
+ disabled = false,
71
+ panelLayout,
72
+ theme,
73
+ className,
74
+ 'aria-label': ariaLabel,
75
+ timeLabel,
76
+ ...options
77
+ }: DateTimePickerProps): ReactElement {
78
+ const locale = usePageLocale(options.locale);
79
+ const picker = useDateTimePicker({ ...options, locale });
80
+ const { state, store, calendar, ids } = picker;
81
+ const labels = getLocale(locale);
82
+ const field = useRef<HTMLDivElement | null>(null);
83
+ const row = useRef<HTMLDivElement | null>(null);
84
+ const panel = useRef<HTMLDivElement | null>(null);
85
+ const dateInput = useRef<HTMLInputElement | null>(null);
86
+
87
+ const panelWidth =
88
+ (state.presets.length > 0 ? PANEL_WIDTH_WITH_PRESETS : PANEL_WIDTH) +
89
+ TIME_COLUMNS_WIDTH;
90
+ usePanel(
91
+ state.open && !state.inline,
92
+ { anchor: row, panel, field, returnFocus: dateInput },
93
+ () => {
94
+ store.actions.close();
95
+ },
96
+ calendar.focusGrid,
97
+ panelWidth,
98
+ panelLayout,
99
+ );
100
+
101
+ const hint = state.error?.message ?? state.equivalentText;
102
+ const invalid = (which: 'date' | 'time'): boolean => state.error?.field === which;
103
+
104
+ return (
105
+ <div
106
+ ref={field}
107
+ className={className ? `ndp-field ${className}` : 'ndp-field'}
108
+ data-ndp-theme={theme}
109
+ >
110
+ <div ref={row} className="ndp-datetime">
111
+ <div
112
+ className="ndp-input"
113
+ data-invalid={invalid('date') || undefined}
114
+ data-disabled={disabled || undefined}
115
+ >
116
+ <input
117
+ ref={dateInput}
118
+ id={ids.date}
119
+ className="ndp-input__control"
120
+ {...popupProps(state)}
121
+ type="text"
122
+ inputMode="numeric"
123
+ autoComplete="off"
124
+ value={state.dateText}
125
+ placeholder={state.placeholder}
126
+ aria-label={ariaLabel}
127
+ aria-controls={ids.panel}
128
+ aria-invalid={invalid('date') || undefined}
129
+ aria-describedby={hint ? ids.hint : undefined}
130
+ required={required}
131
+ disabled={disabled}
132
+ onChange={(event) => {
133
+ store.actions.typeDate(event.target.value);
134
+ }}
135
+ onBlur={() => {
136
+ store.actions.blurDate();
137
+ }}
138
+ onKeyDown={(event) => {
139
+ if (event.key === 'ArrowDown' && (event.altKey || state.dateText === '')) {
140
+ event.preventDefault();
141
+ store.actions.open();
142
+ }
143
+ }}
144
+ />
145
+ {!state.inline && (
146
+ <span className="ndp-input__actions">
147
+ <button
148
+ type="button"
149
+ className="ndp-icon-button"
150
+ disabled={disabled}
151
+ aria-label={labels.labels.openCalendar}
152
+ aria-expanded={state.open}
153
+ aria-controls={ids.panel}
154
+ onClick={() => {
155
+ store.actions.toggle();
156
+ }}
157
+ >
158
+ <Icon name="calendar" />
159
+ </button>
160
+ </span>
161
+ )}
162
+ </div>
163
+ <div
164
+ className="ndp-input ndp-input--time"
165
+ data-invalid={invalid('time') || undefined}
166
+ data-disabled={disabled || undefined}
167
+ >
168
+ <input
169
+ id={ids.time}
170
+ className="ndp-input__control"
171
+ type="text"
172
+ inputMode="text"
173
+ autoComplete="off"
174
+ value={state.timeText}
175
+ placeholder={state.timePlaceholder}
176
+ aria-label={timeLabel ?? labels.labels.time}
177
+ aria-invalid={invalid('time') || undefined}
178
+ aria-describedby={hint ? ids.hint : undefined}
179
+ disabled={disabled}
180
+ onChange={(event) => {
181
+ store.actions.typeTime(event.target.value);
182
+ }}
183
+ onBlur={() => {
184
+ store.actions.blurTime();
185
+ }}
186
+ />
187
+ </div>
188
+ </div>
189
+ {hint !== null && (
190
+ <p
191
+ id={ids.hint}
192
+ className="ndp-hint"
193
+ data-error={state.error ? true : undefined}
194
+ aria-live="polite"
195
+ >
196
+ {hint}
197
+ </p>
198
+ )}
199
+ {name !== undefined && (
200
+ <input type="hidden" name={name} value={state.value} disabled={disabled} />
201
+ )}
202
+ {state.open && (
203
+ <div
204
+ ref={panel}
205
+ id={ids.panel}
206
+ className="ndp-panel"
207
+ popover={state.inline ? undefined : 'manual'}
208
+ data-layout={state.inline ? 'inline' : undefined}
209
+ style={
210
+ state.inline
211
+ ? ({ '--ndp-panel-width': `${String(panelWidth)}px` } as CSSProperties)
212
+ : undefined
213
+ }
214
+ role={state.inline ? 'group' : 'dialog'}
215
+ aria-label={labels.labels.chooseDate}
216
+ data-ndp-theme={theme}
217
+ >
218
+ <PanelBody
219
+ presets={state.presets}
220
+ label={labels.labels.presets}
221
+ onPreset={(presetId) => {
222
+ store.actions.applyPreset(presetId);
223
+ }}
224
+ >
225
+ <CalendarBody bindings={calendar} />
226
+ <TimeColumns
227
+ hours={state.hours}
228
+ minutes={state.minutes}
229
+ locale={options.locale}
230
+ onHour={(hour) => {
231
+ store.actions.selectHour(hour);
232
+ }}
233
+ onMinute={(minute) => {
234
+ store.actions.selectMinute(minute);
235
+ }}
236
+ />
237
+ </PanelBody>
238
+ <div className="ndp-panel__footer">
239
+ {state.canSwitchCalendar ? (
240
+ <div
241
+ className="ndp-segment"
242
+ role="group"
243
+ aria-label={labels.labels.calendar}
244
+ >
245
+ {(['BS', 'AD'] as const).map((option) => (
246
+ <button
247
+ key={option}
248
+ type="button"
249
+ className="ndp-segment__item"
250
+ aria-pressed={state.calendar === option}
251
+ onClick={() => {
252
+ store.actions.switchCalendar(option);
253
+ }}
254
+ >
255
+ {labels.calendarNames[option]}
256
+ </button>
257
+ ))}
258
+ </div>
259
+ ) : (
260
+ <span />
261
+ )}
262
+ <span>
263
+ <button
264
+ type="button"
265
+ className="ndp-button"
266
+ onClick={() => {
267
+ store.actions.selectNow();
268
+ }}
269
+ >
270
+ {labels.labels.now}
271
+ </button>
272
+ <button
273
+ type="button"
274
+ className="ndp-button"
275
+ data-variant="primary"
276
+ onClick={() => {
277
+ store.actions.close();
278
+ dateInput.current?.focus();
279
+ }}
280
+ >
281
+ {labels.labels.done}
282
+ </button>
283
+ </span>
284
+ </div>
285
+ </div>
286
+ )}
287
+ </div>
288
+ );
289
+ }
@@ -0,0 +1,21 @@
1
+ /** @nepdatejs/react/headless: hooks only; no markup, no CSS. */
2
+ export {
3
+ useCalendar,
4
+ useDatePicker,
5
+ useDateRangePicker,
6
+ useDateTimePicker,
7
+ useMultiDatePicker,
8
+ type DatePickerBindings,
9
+ type DateTimePickerBindings,
10
+ type DateRangePickerBindings,
11
+ type MultiDatePickerBindings,
12
+ type InputProps,
13
+ type PanelProps,
14
+ } from './hooks';
15
+ export {
16
+ useCalendarBindings,
17
+ type ButtonProps,
18
+ type CalendarBindings,
19
+ type GridProps,
20
+ } from './bindings';
21
+ export { deepEqual, useStore } from './use-store';