@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.
- package/LICENSE +21 -0
- package/README.md +74 -2
- package/dist/date-text-BX3Uag0O.cjs +84 -0
- package/dist/date-text-BX3Uag0O.cjs.map +1 -0
- package/dist/date-text-DLwhBmYj.js +61 -0
- package/dist/date-text-DLwhBmYj.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-AEI6dxTJ.cjs +619 -0
- package/dist/headless-AEI6dxTJ.cjs.map +1 -0
- package/dist/headless-CVj0DhD4.js +560 -0
- package/dist/headless-CVj0DhD4.js.map +1 -0
- package/dist/headless-ChE3zuYj.d.ts +195 -0
- package/dist/headless-CrIIPnOF.d.cts +195 -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 +915 -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 +895 -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 +110 -3
- package/src/bindings.ts +247 -0
- package/src/calendar-body.tsx +133 -0
- package/src/calendar.tsx +34 -0
- package/src/date-picker.tsx +343 -0
- package/src/date-range-picker.tsx +225 -0
- package/src/date-text.tsx +110 -0
- package/src/date-time-picker.tsx +289 -0
- package/src/headless.ts +21 -0
- package/src/hooks.ts +476 -0
- package/src/icons.tsx +35 -0
- package/src/index.ts +21 -0
- package/src/multi-date-picker.tsx +245 -0
- package/src/page-locale.ts +23 -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 +196 -0
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { useId, type ChangeEvent, type KeyboardEvent } from 'react';
|
|
2
|
+
import { getLocale, type CalendarSystem } from '@nepdatejs/core';
|
|
3
|
+
import {
|
|
4
|
+
createCalendar,
|
|
5
|
+
type CalendarOptions,
|
|
6
|
+
type CalendarState,
|
|
7
|
+
type CalendarStore,
|
|
8
|
+
} from '@nepdatejs/calendar';
|
|
9
|
+
import type { PresetState } from '@nepdatejs/datepicker';
|
|
10
|
+
import {
|
|
11
|
+
createDatePicker,
|
|
12
|
+
createDateRangePicker,
|
|
13
|
+
createDateTimePicker,
|
|
14
|
+
createMultiDatePicker,
|
|
15
|
+
type DateTimePickerOptions,
|
|
16
|
+
type DateTimePickerState,
|
|
17
|
+
type DateTimePickerStore,
|
|
18
|
+
type DatePickerOptions,
|
|
19
|
+
type DatePickerState,
|
|
20
|
+
type DatePickerStore,
|
|
21
|
+
type DateRangePickerOptions,
|
|
22
|
+
type DateRangePickerState,
|
|
23
|
+
type DateRangePickerStore,
|
|
24
|
+
type MultiDatePickerOptions,
|
|
25
|
+
type MultiDatePickerState,
|
|
26
|
+
type MultiDatePickerStore,
|
|
27
|
+
type RangeField,
|
|
28
|
+
} from '@nepdatejs/datepicker';
|
|
29
|
+
import { useCalendarBindings, type ButtonProps, type CalendarBindings } from './bindings';
|
|
30
|
+
import { useStore } from './use-store';
|
|
31
|
+
|
|
32
|
+
/** A standalone calendar: state, actions and prop getters. */
|
|
33
|
+
export function useCalendar(
|
|
34
|
+
options: CalendarOptions = {},
|
|
35
|
+
): CalendarBindings & { store: CalendarStore } {
|
|
36
|
+
const { store, state } = useStore<CalendarOptions, CalendarState, CalendarStore>(
|
|
37
|
+
createCalendar,
|
|
38
|
+
options,
|
|
39
|
+
);
|
|
40
|
+
const bindings = useCalendarBindings(state, store.actions, options.locale, {
|
|
41
|
+
getSnapshot: () => store.getState(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return { ...bindings, store };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface InputProps {
|
|
48
|
+
id: string;
|
|
49
|
+
/** Left out for an inline picker, whose calendar is always in the page. */
|
|
50
|
+
role?: 'combobox';
|
|
51
|
+
type: 'text';
|
|
52
|
+
inputMode: 'numeric';
|
|
53
|
+
autoComplete: 'off';
|
|
54
|
+
value: string;
|
|
55
|
+
placeholder: string;
|
|
56
|
+
'aria-haspopup'?: 'dialog';
|
|
57
|
+
'aria-expanded'?: boolean;
|
|
58
|
+
'aria-controls': string;
|
|
59
|
+
'aria-invalid'?: true;
|
|
60
|
+
'aria-describedby'?: string;
|
|
61
|
+
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
62
|
+
onBlur: () => void;
|
|
63
|
+
onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PanelProps {
|
|
67
|
+
id: string;
|
|
68
|
+
/** 'group' for an inline picker. */
|
|
69
|
+
role: 'dialog' | 'group';
|
|
70
|
+
'aria-label': string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DatePickerBindings {
|
|
74
|
+
readonly state: DatePickerState;
|
|
75
|
+
readonly actions: DatePickerStore['actions'];
|
|
76
|
+
readonly store: DatePickerStore;
|
|
77
|
+
/** Prop getters for the panel's calendar. */
|
|
78
|
+
readonly calendar: CalendarBindings;
|
|
79
|
+
readonly ids: { input: string; panel: string; hint: string };
|
|
80
|
+
getInputProps(extra?: { id?: string | undefined }): InputProps;
|
|
81
|
+
getToggleProps(): ButtonProps & { 'aria-expanded': boolean; 'aria-controls': string };
|
|
82
|
+
getClearProps(): ButtonProps;
|
|
83
|
+
/** The BS/AD switch inside the input. */
|
|
84
|
+
getSwitchProps(): ButtonProps;
|
|
85
|
+
/** One button of the BS/AD segment in the panel footer. */
|
|
86
|
+
getCalendarOptionProps(calendar: CalendarSystem): ButtonProps;
|
|
87
|
+
getTodayProps(): ButtonProps;
|
|
88
|
+
getPanelProps(): PanelProps;
|
|
89
|
+
/** A shortcut button (see state.presets). */
|
|
90
|
+
getPresetProps(preset: PresetState): ButtonProps;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A BS/AD date picker without markup: wire the getters to your own elements. */
|
|
94
|
+
export function useDatePicker(
|
|
95
|
+
options: DatePickerOptions & { id?: string | undefined } = {},
|
|
96
|
+
): DatePickerBindings {
|
|
97
|
+
const { id: givenId, ...storeOptions } = options;
|
|
98
|
+
const { store, state } = useStore<DatePickerOptions, DatePickerState, DatePickerStore>(
|
|
99
|
+
createDatePicker,
|
|
100
|
+
storeOptions,
|
|
101
|
+
);
|
|
102
|
+
const generated = useId();
|
|
103
|
+
const input = givenId ?? `ndp${generated.replace(/:/g, '')}`;
|
|
104
|
+
const ids = { input, panel: `${input}-panel`, hint: `${input}-hint` };
|
|
105
|
+
const labels = getLocale(options.locale);
|
|
106
|
+
const calendar = useCalendarBindings(
|
|
107
|
+
state.calendarState,
|
|
108
|
+
store.calendar.actions,
|
|
109
|
+
options.locale,
|
|
110
|
+
{
|
|
111
|
+
getSnapshot: () => store.calendar.getState(),
|
|
112
|
+
onDayActivate: (date) => {
|
|
113
|
+
store.actions.select(date);
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
const hasHint = state.error !== null || state.equivalentText !== null;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
state,
|
|
121
|
+
actions: store.actions,
|
|
122
|
+
store,
|
|
123
|
+
calendar,
|
|
124
|
+
ids,
|
|
125
|
+
getInputProps: () => ({
|
|
126
|
+
id: ids.input,
|
|
127
|
+
...popupProps(state),
|
|
128
|
+
type: 'text',
|
|
129
|
+
inputMode: 'numeric',
|
|
130
|
+
autoComplete: 'off',
|
|
131
|
+
value: state.text,
|
|
132
|
+
placeholder: state.placeholder,
|
|
133
|
+
'aria-controls': ids.panel,
|
|
134
|
+
...(state.error ? { 'aria-invalid': true as const } : {}),
|
|
135
|
+
...(hasHint ? { 'aria-describedby': ids.hint } : {}),
|
|
136
|
+
onChange: (event) => {
|
|
137
|
+
store.actions.type(event.target.value);
|
|
138
|
+
},
|
|
139
|
+
onBlur: () => {
|
|
140
|
+
store.actions.blur();
|
|
141
|
+
},
|
|
142
|
+
onKeyDown: (event) => {
|
|
143
|
+
if (event.key === 'ArrowDown' && (event.altKey || state.text === '')) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
store.actions.open();
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
getToggleProps: () => ({
|
|
150
|
+
type: 'button',
|
|
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
|
+
getClearProps: () => ({
|
|
159
|
+
type: 'button',
|
|
160
|
+
'aria-label': labels.labels.clear,
|
|
161
|
+
onClick: () => {
|
|
162
|
+
store.actions.clear();
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
getSwitchProps: () => {
|
|
166
|
+
const other: CalendarSystem = state.calendar === 'BS' ? 'AD' : 'BS';
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
type: 'button',
|
|
170
|
+
'aria-label': `${labels.labels.calendar}: ${labels.calendarNames[state.calendar]} → ${labels.calendarNames[other]}`,
|
|
171
|
+
onClick: () => {
|
|
172
|
+
store.actions.switchCalendar(other);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
getCalendarOptionProps: (option) => ({
|
|
177
|
+
type: 'button',
|
|
178
|
+
'aria-pressed': state.calendar === option,
|
|
179
|
+
onClick: () => {
|
|
180
|
+
store.actions.switchCalendar(option);
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
getTodayProps: () => ({
|
|
184
|
+
type: 'button',
|
|
185
|
+
onClick: () => {
|
|
186
|
+
store.actions.selectToday();
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
getPanelProps: () => ({
|
|
190
|
+
id: ids.panel,
|
|
191
|
+
role: state.inline ? 'group' : 'dialog',
|
|
192
|
+
'aria-label': labels.labels.chooseDate,
|
|
193
|
+
}),
|
|
194
|
+
getPresetProps: (preset) =>
|
|
195
|
+
presetProps(preset, (id) => store.actions.applyPreset(id)),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Combobox ARIA for a popup picker; an inline picker's input is a plain text field. */
|
|
200
|
+
export function popupProps(state: {
|
|
201
|
+
readonly open: boolean;
|
|
202
|
+
readonly inline: boolean;
|
|
203
|
+
}): Pick<InputProps, 'role' | 'aria-haspopup' | 'aria-expanded'> {
|
|
204
|
+
return state.inline
|
|
205
|
+
? {}
|
|
206
|
+
: { role: 'combobox', 'aria-haspopup': 'dialog', 'aria-expanded': state.open };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function presetProps(preset: PresetState, apply: (id: string) => boolean): ButtonProps {
|
|
210
|
+
return {
|
|
211
|
+
type: 'button',
|
|
212
|
+
'aria-pressed': preset.isActive,
|
|
213
|
+
disabled: preset.isDisabled,
|
|
214
|
+
onClick: () => {
|
|
215
|
+
apply(preset.id);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface DateRangePickerBindings {
|
|
221
|
+
readonly state: DateRangePickerState;
|
|
222
|
+
readonly actions: DateRangePickerStore['actions'];
|
|
223
|
+
readonly store: DateRangePickerStore;
|
|
224
|
+
readonly calendar: CalendarBindings;
|
|
225
|
+
readonly ids: { start: string; end: string; panel: string; hint: string };
|
|
226
|
+
getInputProps(field: RangeField): Omit<InputProps, 'onChange' | 'onBlur'> & {
|
|
227
|
+
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
228
|
+
onBlur: () => void;
|
|
229
|
+
};
|
|
230
|
+
getToggleProps(): ButtonProps & { 'aria-expanded': boolean; 'aria-controls': string };
|
|
231
|
+
getCalendarOptionProps(calendar: CalendarSystem): ButtonProps;
|
|
232
|
+
getPanelProps(): PanelProps;
|
|
233
|
+
getPresetProps(preset: PresetState): ButtonProps;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** A BS/AD date range picker without markup. */
|
|
237
|
+
export function useDateRangePicker(
|
|
238
|
+
options: DateRangePickerOptions = {},
|
|
239
|
+
): DateRangePickerBindings {
|
|
240
|
+
const { store, state } = useStore<
|
|
241
|
+
DateRangePickerOptions,
|
|
242
|
+
DateRangePickerState,
|
|
243
|
+
DateRangePickerStore
|
|
244
|
+
>(createDateRangePicker, options);
|
|
245
|
+
const base = `ndp${useId().replace(/:/g, '')}`;
|
|
246
|
+
const ids = {
|
|
247
|
+
start: `${base}-start`,
|
|
248
|
+
end: `${base}-end`,
|
|
249
|
+
panel: `${base}-panel`,
|
|
250
|
+
hint: `${base}-hint`,
|
|
251
|
+
};
|
|
252
|
+
const labels = getLocale(options.locale);
|
|
253
|
+
const calendar = useCalendarBindings(
|
|
254
|
+
state.calendarState,
|
|
255
|
+
store.calendar.actions,
|
|
256
|
+
options.locale,
|
|
257
|
+
{
|
|
258
|
+
getSnapshot: () => store.calendar.getState(),
|
|
259
|
+
onDayActivate: (date) => {
|
|
260
|
+
store.actions.select(date);
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
const texts: Record<RangeField, string> = {
|
|
265
|
+
start: state.startText,
|
|
266
|
+
end: state.endText,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
state,
|
|
271
|
+
actions: store.actions,
|
|
272
|
+
store,
|
|
273
|
+
calendar,
|
|
274
|
+
ids,
|
|
275
|
+
getInputProps: (field) => {
|
|
276
|
+
const invalid =
|
|
277
|
+
state.error !== null &&
|
|
278
|
+
(state.error.field === field || state.error.field === 'range');
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
id: ids[field],
|
|
282
|
+
...popupProps(state),
|
|
283
|
+
type: 'text',
|
|
284
|
+
inputMode: 'numeric',
|
|
285
|
+
autoComplete: 'off',
|
|
286
|
+
value: texts[field],
|
|
287
|
+
placeholder: state.placeholder,
|
|
288
|
+
'aria-controls': ids.panel,
|
|
289
|
+
...(invalid ? { 'aria-invalid': true as const } : {}),
|
|
290
|
+
...(state.error ? { 'aria-describedby': ids.hint } : {}),
|
|
291
|
+
onChange: (event) => {
|
|
292
|
+
store.actions.type(field, event.target.value);
|
|
293
|
+
},
|
|
294
|
+
onBlur: () => {
|
|
295
|
+
store.actions.blur(field);
|
|
296
|
+
},
|
|
297
|
+
onKeyDown: (event) => {
|
|
298
|
+
if (event.key === 'ArrowDown' && (event.altKey || texts[field] === '')) {
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
store.actions.open();
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
getToggleProps: () => ({
|
|
306
|
+
type: 'button',
|
|
307
|
+
'aria-label': labels.labels.openCalendar,
|
|
308
|
+
'aria-expanded': state.open,
|
|
309
|
+
'aria-controls': ids.panel,
|
|
310
|
+
onClick: () => {
|
|
311
|
+
store.actions.toggle();
|
|
312
|
+
},
|
|
313
|
+
}),
|
|
314
|
+
getCalendarOptionProps: (option) => ({
|
|
315
|
+
type: 'button',
|
|
316
|
+
'aria-pressed': state.calendar === option,
|
|
317
|
+
onClick: () => {
|
|
318
|
+
store.actions.switchCalendar(option);
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
getPanelProps: () => ({
|
|
322
|
+
id: ids.panel,
|
|
323
|
+
role: state.inline ? 'group' : 'dialog',
|
|
324
|
+
'aria-label': labels.labels.chooseDate,
|
|
325
|
+
}),
|
|
326
|
+
getPresetProps: (preset) =>
|
|
327
|
+
presetProps(preset, (id) => store.actions.applyPreset(id)),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface DateTimePickerBindings {
|
|
332
|
+
readonly state: DateTimePickerState;
|
|
333
|
+
readonly actions: DateTimePickerStore['actions'];
|
|
334
|
+
readonly store: DateTimePickerStore;
|
|
335
|
+
readonly calendar: CalendarBindings;
|
|
336
|
+
readonly ids: { date: string; time: string; panel: string; hint: string };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** A BS/AD date + time picker without markup. The value is a zoned ISO timestamp. */
|
|
340
|
+
export function useDateTimePicker(
|
|
341
|
+
options: DateTimePickerOptions & { id?: string | undefined } = {},
|
|
342
|
+
): DateTimePickerBindings {
|
|
343
|
+
const { id: givenId, ...storeOptions } = options;
|
|
344
|
+
const { store, state } = useStore<
|
|
345
|
+
DateTimePickerOptions,
|
|
346
|
+
DateTimePickerState,
|
|
347
|
+
DateTimePickerStore
|
|
348
|
+
>(createDateTimePicker, storeOptions);
|
|
349
|
+
const generated = useId();
|
|
350
|
+
const base = givenId ?? `ndp${generated.replace(/:/g, '')}`;
|
|
351
|
+
const calendar = useCalendarBindings(
|
|
352
|
+
state.calendarState,
|
|
353
|
+
store.calendar.actions,
|
|
354
|
+
options.locale,
|
|
355
|
+
{
|
|
356
|
+
getSnapshot: () => store.calendar.getState(),
|
|
357
|
+
onDayActivate: (date) => {
|
|
358
|
+
store.actions.selectDate(date);
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
state,
|
|
365
|
+
actions: store.actions,
|
|
366
|
+
store,
|
|
367
|
+
calendar,
|
|
368
|
+
ids: {
|
|
369
|
+
date: base,
|
|
370
|
+
time: `${base}-time`,
|
|
371
|
+
panel: `${base}-panel`,
|
|
372
|
+
hint: `${base}-hint`,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface MultiDatePickerBindings {
|
|
378
|
+
readonly state: MultiDatePickerState;
|
|
379
|
+
readonly actions: MultiDatePickerStore['actions'];
|
|
380
|
+
readonly store: MultiDatePickerStore;
|
|
381
|
+
readonly calendar: CalendarBindings;
|
|
382
|
+
readonly ids: { input: string; panel: string; hint: string };
|
|
383
|
+
getInputProps(): InputProps;
|
|
384
|
+
getToggleProps(): ButtonProps & { 'aria-expanded': boolean; 'aria-controls': string };
|
|
385
|
+
getClearProps(): ButtonProps;
|
|
386
|
+
getCalendarOptionProps(calendar: CalendarSystem): ButtonProps;
|
|
387
|
+
getPanelProps(): PanelProps;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** A picker for several separate BS/AD days, without markup (PICK-001). */
|
|
391
|
+
export function useMultiDatePicker(
|
|
392
|
+
options: MultiDatePickerOptions & { id?: string | undefined } = {},
|
|
393
|
+
): MultiDatePickerBindings {
|
|
394
|
+
const { id: givenId, ...storeOptions } = options;
|
|
395
|
+
const { store, state } = useStore<
|
|
396
|
+
MultiDatePickerOptions,
|
|
397
|
+
MultiDatePickerState,
|
|
398
|
+
MultiDatePickerStore
|
|
399
|
+
>(createMultiDatePicker, storeOptions);
|
|
400
|
+
const generated = useId();
|
|
401
|
+
const input = givenId ?? `ndp${generated.replace(/:/g, '')}`;
|
|
402
|
+
const ids = { input, panel: `${input}-panel`, hint: `${input}-hint` };
|
|
403
|
+
const labels = getLocale(options.locale);
|
|
404
|
+
const calendar = useCalendarBindings(
|
|
405
|
+
state.calendarState,
|
|
406
|
+
store.calendar.actions,
|
|
407
|
+
options.locale,
|
|
408
|
+
{
|
|
409
|
+
getSnapshot: () => store.calendar.getState(),
|
|
410
|
+
onDayActivate: (date) => {
|
|
411
|
+
store.actions.select(date);
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
state,
|
|
418
|
+
actions: store.actions,
|
|
419
|
+
store,
|
|
420
|
+
calendar,
|
|
421
|
+
ids,
|
|
422
|
+
getInputProps: () => ({
|
|
423
|
+
id: ids.input,
|
|
424
|
+
...popupProps(state),
|
|
425
|
+
type: 'text',
|
|
426
|
+
inputMode: 'numeric',
|
|
427
|
+
autoComplete: 'off',
|
|
428
|
+
value: state.text,
|
|
429
|
+
placeholder: state.placeholder,
|
|
430
|
+
'aria-controls': ids.panel,
|
|
431
|
+
...(state.error
|
|
432
|
+
? { 'aria-invalid': true as const, 'aria-describedby': ids.hint }
|
|
433
|
+
: {}),
|
|
434
|
+
onChange: (event) => {
|
|
435
|
+
store.actions.type(event.target.value);
|
|
436
|
+
},
|
|
437
|
+
onBlur: () => {
|
|
438
|
+
store.actions.blur();
|
|
439
|
+
},
|
|
440
|
+
onKeyDown: (event) => {
|
|
441
|
+
if (event.key === 'ArrowDown' && (event.altKey || state.text === '')) {
|
|
442
|
+
event.preventDefault();
|
|
443
|
+
store.actions.open();
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
}),
|
|
447
|
+
getToggleProps: () => ({
|
|
448
|
+
type: 'button',
|
|
449
|
+
'aria-label': labels.labels.openCalendar,
|
|
450
|
+
'aria-expanded': state.open,
|
|
451
|
+
'aria-controls': ids.panel,
|
|
452
|
+
onClick: () => {
|
|
453
|
+
store.actions.toggle();
|
|
454
|
+
},
|
|
455
|
+
}),
|
|
456
|
+
getClearProps: () => ({
|
|
457
|
+
type: 'button',
|
|
458
|
+
'aria-label': labels.labels.clear,
|
|
459
|
+
onClick: () => {
|
|
460
|
+
store.actions.clear();
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
getCalendarOptionProps: (option) => ({
|
|
464
|
+
type: 'button',
|
|
465
|
+
'aria-pressed': state.calendar === option,
|
|
466
|
+
onClick: () => {
|
|
467
|
+
store.actions.switchCalendar(option);
|
|
468
|
+
},
|
|
469
|
+
}),
|
|
470
|
+
getPanelProps: () => ({
|
|
471
|
+
id: ids.panel,
|
|
472
|
+
role: state.inline ? 'group' : 'dialog',
|
|
473
|
+
'aria-label': labels.labels.chooseDate,
|
|
474
|
+
}),
|
|
475
|
+
};
|
|
476
|
+
}
|
package/src/icons.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
const PATHS = {
|
|
4
|
+
'chevron-left': ['M10 3.5 5.5 8l4.5 4.5'],
|
|
5
|
+
'chevron-right': ['M6 3.5 10.5 8 6 12.5'],
|
|
6
|
+
calendar: [
|
|
7
|
+
'M2 6.5h12',
|
|
8
|
+
'M5.5 1.5v3',
|
|
9
|
+
'M10.5 1.5v3',
|
|
10
|
+
'M4 3h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z',
|
|
11
|
+
],
|
|
12
|
+
close: ['M4 4l8 8', 'M12 4l-8 8'],
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type IconName = keyof typeof PATHS;
|
|
16
|
+
|
|
17
|
+
/** The Figma icon set, 16px, stroked with currentColor. */
|
|
18
|
+
export function Icon({ name }: { name: IconName }): ReactElement {
|
|
19
|
+
return (
|
|
20
|
+
<svg
|
|
21
|
+
viewBox="0 0 16 16"
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke="currentColor"
|
|
24
|
+
strokeWidth={1.5}
|
|
25
|
+
strokeLinecap="round"
|
|
26
|
+
strokeLinejoin="round"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
focusable="false"
|
|
29
|
+
>
|
|
30
|
+
{PATHS[name].map((d) => (
|
|
31
|
+
<path key={d} d={d} />
|
|
32
|
+
))}
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nepdatejs/react: BS/AD date picker and calendar for React. Headless hooks
|
|
3
|
+
* (useCalendar, useDatePicker, useDateRangePicker, …) and styled components that
|
|
4
|
+
* use the @nepdatejs/styles classes (import '@nepdatejs/styles' once).
|
|
5
|
+
*/
|
|
6
|
+
export { Calendar, type CalendarProps } from './calendar';
|
|
7
|
+
export { DatePicker, type DatePickerProps } from './date-picker';
|
|
8
|
+
export { DateRangePicker, type DateRangePickerProps } from './date-range-picker';
|
|
9
|
+
export { DateTimePicker, type DateTimePickerProps } from './date-time-picker';
|
|
10
|
+
export { MultiDatePicker, type MultiDatePickerProps } from './multi-date-picker';
|
|
11
|
+
export { TimeColumns } from './time-columns';
|
|
12
|
+
export {
|
|
13
|
+
bothDates,
|
|
14
|
+
DateText,
|
|
15
|
+
shortDate,
|
|
16
|
+
ShortDate,
|
|
17
|
+
type DateTextProps,
|
|
18
|
+
} from './date-text';
|
|
19
|
+
export { CalendarBody } from './calendar-body';
|
|
20
|
+
export { Icon, type IconName } from './icons';
|
|
21
|
+
export * from './headless';
|