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