@k3-universe/react-kit 0.0.10 → 0.0.11
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/dist/index.js +1303 -75
- package/dist/kit/builder/form/components/FormBuilder.d.ts +5 -1
- package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
- package/dist/kit/builder/form/components/FormBuilderField.d.ts.map +1 -1
- package/dist/kit/builder/form/components/fields/DateTimePickerField.d.ts +4 -0
- package/dist/kit/builder/form/components/fields/DateTimePickerField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/DateTimeRangePickerField.d.ts +4 -0
- package/dist/kit/builder/form/components/fields/DateTimeRangePickerField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/TimePickerField.d.ts +4 -0
- package/dist/kit/builder/form/components/fields/TimePickerField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/TimeRangePickerField.d.ts +4 -0
- package/dist/kit/builder/form/components/fields/TimeRangePickerField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/index.d.ts +4 -0
- package/dist/kit/builder/form/components/fields/index.d.ts.map +1 -1
- package/dist/kit/components/datetimepicker/DateTimePicker.d.ts +32 -0
- package/dist/kit/components/datetimepicker/DateTimePicker.d.ts.map +1 -0
- package/dist/kit/components/datetimepicker/DateTimeRangePicker.d.ts +39 -0
- package/dist/kit/components/datetimepicker/DateTimeRangePicker.d.ts.map +1 -0
- package/dist/kit/components/datetimepicker/index.d.ts +5 -0
- package/dist/kit/components/datetimepicker/index.d.ts.map +1 -0
- package/dist/kit/components/timepicker/TimePicker.d.ts +26 -0
- package/dist/kit/components/timepicker/TimePicker.d.ts.map +1 -0
- package/dist/kit/components/timepicker/TimeRangePicker.d.ts +31 -0
- package/dist/kit/components/timepicker/TimeRangePicker.d.ts.map +1 -0
- package/dist/kit/components/timepicker/index.d.ts +5 -0
- package/dist/kit/components/timepicker/index.d.ts.map +1 -0
- package/dist/kit/themes/clean-slate.css +16 -0
- package/dist/kit/themes/default.css +16 -0
- package/dist/kit/themes/minimal-modern.css +16 -0
- package/dist/kit/themes/spotify.css +16 -0
- package/package.json +1 -1
- package/src/kit/builder/form/components/FormBuilder.tsx +17 -0
- package/src/kit/builder/form/components/FormBuilderField.tsx +48 -0
- package/src/kit/builder/form/components/fields/DateTimePickerField.tsx +33 -0
- package/src/kit/builder/form/components/fields/DateTimeRangePickerField.tsx +42 -0
- package/src/kit/builder/form/components/fields/TimePickerField.tsx +30 -0
- package/src/kit/builder/form/components/fields/TimeRangePickerField.tsx +37 -0
- package/src/kit/builder/form/components/fields/index.ts +4 -0
- package/src/kit/components/datetimepicker/DateTimePicker.tsx +314 -0
- package/src/kit/components/datetimepicker/DateTimeRangePicker.tsx +486 -0
- package/src/kit/components/datetimepicker/index.ts +3 -0
- package/src/kit/components/timepicker/TimePicker.tsx +311 -0
- package/src/kit/components/timepicker/TimeRangePicker.tsx +291 -0
- package/src/kit/components/timepicker/index.ts +3 -0
- package/src/stories/kit/builder/Form.DateTime.stories.tsx +66 -0
- package/src/stories/kit/builder/Form.Time.stories.tsx +64 -0
- package/src/stories/kit/components/TimePicker.stories.tsx +69 -0
- package/src/stories/kit/components/TimeRangePicker.stories.tsx +37 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { FieldRenderProps } from './types'
|
|
3
|
+
import { TimePicker } from '../../../../components/timepicker/TimePicker'
|
|
4
|
+
|
|
5
|
+
export function TimePickerField({ field, value, onChange, className }: FieldRenderProps) {
|
|
6
|
+
const v = React.useMemo(() => {
|
|
7
|
+
if (!value) return null
|
|
8
|
+
if (value instanceof Date) return value
|
|
9
|
+
const d = new Date(value as string)
|
|
10
|
+
return Number.isNaN(d.getTime()) ? null : d
|
|
11
|
+
}, [value])
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<TimePicker
|
|
15
|
+
className={className}
|
|
16
|
+
value={v}
|
|
17
|
+
onChange={(d: Date | null) => onChange(d)}
|
|
18
|
+
placeholder={field.placeholder}
|
|
19
|
+
precision={field.timePrecision ?? 'minute'}
|
|
20
|
+
hourCycle={field.hourCycle ?? 24}
|
|
21
|
+
minuteStep={field.minuteStep ?? 5}
|
|
22
|
+
secondStep={field.secondStep ?? 5}
|
|
23
|
+
showFooter={field.showFooter}
|
|
24
|
+
cancelLabel={field.cancelLabel}
|
|
25
|
+
applyLabel={field.applyLabel}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default TimePickerField
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { FieldRenderProps } from './types'
|
|
3
|
+
import { TimeRangePicker } from '../../../../components/timepicker/TimeRangePicker'
|
|
4
|
+
|
|
5
|
+
export function TimeRangePickerField({ field, value, onChange, className }: FieldRenderProps) {
|
|
6
|
+
const v = React.useMemo(() => {
|
|
7
|
+
if (!value || typeof value !== 'object') return null as { from?: Date | null; to?: Date | null } | null
|
|
8
|
+
const anyVal = value as { from?: unknown; to?: unknown }
|
|
9
|
+
const toDate = (x: unknown) => {
|
|
10
|
+
if (!x) return undefined
|
|
11
|
+
if (x instanceof Date) return x
|
|
12
|
+
const d = new Date(x as string)
|
|
13
|
+
return Number.isNaN(d.getTime()) ? undefined : d
|
|
14
|
+
}
|
|
15
|
+
const from = toDate(anyVal.from) ?? null
|
|
16
|
+
const to = toDate(anyVal.to) ?? null
|
|
17
|
+
return { from, to }
|
|
18
|
+
}, [value])
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<TimeRangePicker
|
|
22
|
+
className={className}
|
|
23
|
+
value={v}
|
|
24
|
+
onChange={(range) => onChange(range)}
|
|
25
|
+
placeholder={field.placeholder}
|
|
26
|
+
precision={field.timePrecision ?? 'minute'}
|
|
27
|
+
hourCycle={field.hourCycle ?? 24}
|
|
28
|
+
minuteStep={field.minuteStep ?? 5}
|
|
29
|
+
secondStep={field.secondStep ?? 5}
|
|
30
|
+
showFooter={field.showFooter}
|
|
31
|
+
cancelLabel={field.cancelLabel}
|
|
32
|
+
applyLabel={field.applyLabel}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default TimeRangePickerField
|
|
@@ -12,6 +12,10 @@ export * from './DatePickerField'
|
|
|
12
12
|
export * from './DateRangePickerField'
|
|
13
13
|
export * from './MonthPickerField'
|
|
14
14
|
export * from './MonthRangePickerField'
|
|
15
|
+
export * from './TimePickerField'
|
|
16
|
+
export * from './TimeRangePickerField'
|
|
17
|
+
export * from './DateTimePickerField'
|
|
18
|
+
export * from './DateTimeRangePickerField'
|
|
15
19
|
export * from './FileField'
|
|
16
20
|
export * from './ObjectField'
|
|
17
21
|
export * from './ArrayField'
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Calendar as CalendarIcon } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../../shadcn/lib/utils';
|
|
6
|
+
import { Button } from '../../../shadcn/ui/button';
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../../shadcn/ui/popover';
|
|
8
|
+
import { Calendar } from '../../../shadcn/ui/calendar';
|
|
9
|
+
import {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue,
|
|
15
|
+
} from '../../../shadcn/ui/select';
|
|
16
|
+
|
|
17
|
+
export type TimePrecision = 'hour' | 'minute' | 'second';
|
|
18
|
+
|
|
19
|
+
export interface DateTimePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
20
|
+
value?: Date | null;
|
|
21
|
+
onChange?: (date: Date | null) => void;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
minDate?: Date;
|
|
25
|
+
maxDate?: Date;
|
|
26
|
+
disabledDates?: Array<Date | { from: Date; to: Date }>;
|
|
27
|
+
// time config
|
|
28
|
+
timePrecision?: TimePrecision; // default 'minute'
|
|
29
|
+
hourCycle?: 12 | 24; // default 24
|
|
30
|
+
minuteStep?: number; // default 5
|
|
31
|
+
secondStep?: number; // default 5
|
|
32
|
+
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
|
|
33
|
+
open?: boolean;
|
|
34
|
+
onOpenChange?: (open: boolean) => void;
|
|
35
|
+
showFooter?: boolean; // default true
|
|
36
|
+
cancelLabel?: string; // default 'Cancel'
|
|
37
|
+
applyLabel?: string; // default 'Apply'
|
|
38
|
+
clearLabel?: string; // default 'Clear'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
42
|
+
const pad2 = (n: number) => String(n).padStart(2, '0');
|
|
43
|
+
const isBefore = (date: Date, min?: Date) => !!(min && date < startOfDay(min));
|
|
44
|
+
const isAfter = (date: Date, max?: Date) => !!(max && date > startOfDay(max));
|
|
45
|
+
function sameDay(a: Date, b: Date) {
|
|
46
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
47
|
+
}
|
|
48
|
+
function inDisabled(date: Date, items?: Array<Date | { from: Date; to: Date }>) {
|
|
49
|
+
if (!items || items.length === 0) return false;
|
|
50
|
+
const d = startOfDay(date);
|
|
51
|
+
for (const it of items) {
|
|
52
|
+
if (it instanceof Date) {
|
|
53
|
+
if (sameDay(d, it)) return true;
|
|
54
|
+
} else if (it && 'from' in it && 'to' in it) {
|
|
55
|
+
const from = startOfDay(it.from);
|
|
56
|
+
const to = startOfDay(it.to);
|
|
57
|
+
if (d >= from && d <= to) return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function TimeSelectors({
|
|
64
|
+
value,
|
|
65
|
+
onChange,
|
|
66
|
+
precision,
|
|
67
|
+
hourCycle,
|
|
68
|
+
minuteStep,
|
|
69
|
+
secondStep,
|
|
70
|
+
disabled,
|
|
71
|
+
}: {
|
|
72
|
+
value: Date | null;
|
|
73
|
+
onChange: (val: Date | null) => void;
|
|
74
|
+
precision: TimePrecision;
|
|
75
|
+
hourCycle: 12 | 24;
|
|
76
|
+
minuteStep: number;
|
|
77
|
+
secondStep: number;
|
|
78
|
+
disabled?: boolean;
|
|
79
|
+
}) {
|
|
80
|
+
const hours = React.useMemo(() => (hourCycle === 12 ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i)), [hourCycle]);
|
|
81
|
+
const minutes = React.useMemo(() => Array.from({ length: Math.ceil(60 / minuteStep) }, (_, i) => i * minuteStep), [minuteStep]);
|
|
82
|
+
const seconds = React.useMemo(() => Array.from({ length: Math.ceil(60 / secondStep) }, (_, i) => i * secondStep), [secondStep]);
|
|
83
|
+
const selectedHour = React.useMemo(() => {
|
|
84
|
+
if (!value) return hourCycle === 12 ? 12 : 0;
|
|
85
|
+
const h = value.getHours();
|
|
86
|
+
return hourCycle === 12 ? (h % 12 === 0 ? 12 : h % 12) : h;
|
|
87
|
+
}, [value, hourCycle]);
|
|
88
|
+
const selectedMinute = value?.getMinutes() ?? 0;
|
|
89
|
+
const selectedSecond = value?.getSeconds() ?? 0;
|
|
90
|
+
const selectedPeriod: 'AM' | 'PM' = value && value.getHours() >= 12 ? 'PM' : 'AM';
|
|
91
|
+
|
|
92
|
+
const setPart = (part: 'hour' | 'minute' | 'second' | 'period', v: number | 'AM' | 'PM') => {
|
|
93
|
+
const base = value
|
|
94
|
+
? new Date(value)
|
|
95
|
+
: (() => {
|
|
96
|
+
const n = new Date();
|
|
97
|
+
return new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0, 0);
|
|
98
|
+
})();
|
|
99
|
+
if (part === 'hour') {
|
|
100
|
+
let h = Number(v);
|
|
101
|
+
if (hourCycle === 12) {
|
|
102
|
+
const isPM = base.getHours() >= 12;
|
|
103
|
+
h = h % 12;
|
|
104
|
+
base.setHours(isPM ? (h === 12 ? 12 : h + 12) : (h === 12 ? 0 : h));
|
|
105
|
+
} else {
|
|
106
|
+
base.setHours(h);
|
|
107
|
+
}
|
|
108
|
+
} else if (part === 'minute') {
|
|
109
|
+
base.setMinutes(Number(v));
|
|
110
|
+
} else if (part === 'second') {
|
|
111
|
+
base.setSeconds(Number(v));
|
|
112
|
+
} else if (part === 'period' && (v === 'AM' || v === 'PM')) {
|
|
113
|
+
const curH = base.getHours();
|
|
114
|
+
const isAMNow = curH < 12;
|
|
115
|
+
if (v === 'AM' && !isAMNow) base.setHours(curH - 12);
|
|
116
|
+
if (v === 'PM' && isAMNow) base.setHours(curH + 12);
|
|
117
|
+
}
|
|
118
|
+
base.setMilliseconds(0);
|
|
119
|
+
onChange(base);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="flex items-end gap-2">
|
|
124
|
+
<div className="w-24">
|
|
125
|
+
<div className="mb-1 block text-xs text-muted-foreground">Hour</div>
|
|
126
|
+
<Select disabled={disabled} value={String(selectedHour)} onValueChange={(v) => setPart('hour', Number(v))}>
|
|
127
|
+
<SelectTrigger aria-label="Hour">
|
|
128
|
+
<SelectValue />
|
|
129
|
+
</SelectTrigger>
|
|
130
|
+
<SelectContent>
|
|
131
|
+
{hours.map((h) => (
|
|
132
|
+
<SelectItem key={h} value={String(h)}>{hourCycle === 12 ? h : pad2(h)}</SelectItem>
|
|
133
|
+
))}
|
|
134
|
+
</SelectContent>
|
|
135
|
+
</Select>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{(precision === 'minute' || precision === 'second') && (
|
|
139
|
+
<div className="w-24">
|
|
140
|
+
<div className="mb-1 block text-xs text-muted-foreground">Minute</div>
|
|
141
|
+
<Select disabled={disabled} value={String(selectedMinute - (selectedMinute % minuteStep))} onValueChange={(v) => setPart('minute', Number(v))}>
|
|
142
|
+
<SelectTrigger aria-label="Minute">
|
|
143
|
+
<SelectValue />
|
|
144
|
+
</SelectTrigger>
|
|
145
|
+
<SelectContent>
|
|
146
|
+
{minutes.map((m) => (
|
|
147
|
+
<SelectItem key={m} value={String(m)}>{pad2(m)}</SelectItem>
|
|
148
|
+
))}
|
|
149
|
+
</SelectContent>
|
|
150
|
+
</Select>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{precision === 'second' && (
|
|
155
|
+
<div className="w-24">
|
|
156
|
+
<div className="mb-1 block text-xs text-muted-foreground">Second</div>
|
|
157
|
+
<Select disabled={disabled} value={String(selectedSecond - (selectedSecond % secondStep))} onValueChange={(v) => setPart('second', Number(v))}>
|
|
158
|
+
<SelectTrigger aria-label="Second">
|
|
159
|
+
<SelectValue />
|
|
160
|
+
</SelectTrigger>
|
|
161
|
+
<SelectContent>
|
|
162
|
+
{seconds.map((s) => (
|
|
163
|
+
<SelectItem key={s} value={String(s)}>{pad2(s)}</SelectItem>
|
|
164
|
+
))}
|
|
165
|
+
</SelectContent>
|
|
166
|
+
</Select>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{hourCycle === 12 && (
|
|
171
|
+
<div className="w-24">
|
|
172
|
+
<div className="mb-1 block text-xs text-muted-foreground">Period</div>
|
|
173
|
+
<Select disabled={disabled} value={selectedPeriod} onValueChange={(v) => setPart('period', v as 'AM' | 'PM')}>
|
|
174
|
+
<SelectTrigger aria-label="Period">
|
|
175
|
+
<SelectValue />
|
|
176
|
+
</SelectTrigger>
|
|
177
|
+
<SelectContent>
|
|
178
|
+
<SelectItem value="AM">AM</SelectItem>
|
|
179
|
+
<SelectItem value="PM">PM</SelectItem>
|
|
180
|
+
</SelectContent>
|
|
181
|
+
</Select>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function DateTimePicker({
|
|
189
|
+
value,
|
|
190
|
+
onChange,
|
|
191
|
+
placeholder = 'Pick a date & time',
|
|
192
|
+
disabled,
|
|
193
|
+
minDate,
|
|
194
|
+
maxDate,
|
|
195
|
+
disabledDates,
|
|
196
|
+
timePrecision = 'minute',
|
|
197
|
+
hourCycle = 24,
|
|
198
|
+
minuteStep = 5,
|
|
199
|
+
secondStep = 5,
|
|
200
|
+
className,
|
|
201
|
+
buttonVariant = 'outline',
|
|
202
|
+
...props
|
|
203
|
+
}: DateTimePickerProps) {
|
|
204
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
205
|
+
const isOpen = typeof props.open === 'boolean' ? props.open : internalOpen;
|
|
206
|
+
const setOpen = (o: boolean) => (props.onOpenChange ? props.onOpenChange(o) : setInternalOpen(o));
|
|
207
|
+
const [draft, setDraft] = React.useState<Date | null>(value ?? null);
|
|
208
|
+
|
|
209
|
+
React.useEffect(() => {
|
|
210
|
+
if (isOpen) setDraft(value ?? null);
|
|
211
|
+
}, [isOpen, value]);
|
|
212
|
+
|
|
213
|
+
const isDisabled = (date: Date) => {
|
|
214
|
+
if (isBefore(date, minDate) || isAfter(date, maxDate)) return true;
|
|
215
|
+
if (inDisabled(date, disabledDates)) return true;
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const fmtLabel = (d: Date | null): string => {
|
|
220
|
+
if (!d) return placeholder;
|
|
221
|
+
const dateStr = d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: '2-digit' });
|
|
222
|
+
const h = d.getHours();
|
|
223
|
+
const m = d.getMinutes();
|
|
224
|
+
const s = d.getSeconds();
|
|
225
|
+
const timeStr = hourCycle === 12
|
|
226
|
+
? `${((h % 12) || 12)}:${pad2(m)}${timePrecision === 'second' ? `:${pad2(s)}` : ''} ${h >= 12 ? 'PM' : 'AM'}`
|
|
227
|
+
: `${pad2(h)}:${pad2(m)}${timePrecision === 'second' ? `:${pad2(s)}` : ''}`;
|
|
228
|
+
return `${dateStr} ${timeStr}`;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const label = fmtLabel(value ?? null);
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div className={cn('w-fit', className)} {...props}>
|
|
235
|
+
<Popover open={isOpen} onOpenChange={setOpen}>
|
|
236
|
+
<PopoverTrigger asChild>
|
|
237
|
+
<Button
|
|
238
|
+
type="button"
|
|
239
|
+
disabled={disabled}
|
|
240
|
+
variant={buttonVariant}
|
|
241
|
+
className={cn('w-[280px] justify-start text-left font-normal', !value && 'text-muted-foreground')}
|
|
242
|
+
>
|
|
243
|
+
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
244
|
+
{label}
|
|
245
|
+
</Button>
|
|
246
|
+
</PopoverTrigger>
|
|
247
|
+
<PopoverContent className="p-0" align="start">
|
|
248
|
+
<div className="p-3 space-y-3">
|
|
249
|
+
<Calendar
|
|
250
|
+
mode="single"
|
|
251
|
+
selected={draft ?? undefined}
|
|
252
|
+
onSelect={(d) => {
|
|
253
|
+
if (disabled) return;
|
|
254
|
+
if (!d) return;
|
|
255
|
+
if (isDisabled(d)) return;
|
|
256
|
+
// preserve time parts if exist
|
|
257
|
+
if (draft) {
|
|
258
|
+
const nd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), draft.getHours(), draft.getMinutes(), draft.getSeconds());
|
|
259
|
+
setDraft(nd);
|
|
260
|
+
} else {
|
|
261
|
+
setDraft(new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0));
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
defaultMonth={draft ?? new Date()}
|
|
265
|
+
disabled={isDisabled}
|
|
266
|
+
buttonVariant="ghost"
|
|
267
|
+
showOutsideDays
|
|
268
|
+
/>
|
|
269
|
+
<div>
|
|
270
|
+
<div className="mb-1 block text-xs text-muted-foreground">Time</div>
|
|
271
|
+
<TimeSelectors
|
|
272
|
+
value={draft}
|
|
273
|
+
onChange={(d) => setDraft(d)}
|
|
274
|
+
precision={timePrecision}
|
|
275
|
+
hourCycle={hourCycle}
|
|
276
|
+
minuteStep={minuteStep}
|
|
277
|
+
secondStep={secondStep}
|
|
278
|
+
disabled={disabled}
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{(props.showFooter ?? true) && (
|
|
284
|
+
<div className="flex items-center justify-between gap-2 p-2 border-t">
|
|
285
|
+
<Button type="button" variant="outline" size="sm" onClick={() => setOpen(false)} disabled={disabled}>
|
|
286
|
+
{props.cancelLabel ?? 'Cancel'}
|
|
287
|
+
</Button>
|
|
288
|
+
<div className="flex gap-2">
|
|
289
|
+
<Button type="button" variant="ghost" size="sm" onClick={() => onChange?.(null)} disabled={disabled}>
|
|
290
|
+
{props.clearLabel ?? 'Clear'}
|
|
291
|
+
</Button>
|
|
292
|
+
<Button
|
|
293
|
+
type="button"
|
|
294
|
+
variant="default"
|
|
295
|
+
size="sm"
|
|
296
|
+
onClick={() => {
|
|
297
|
+
onChange?.(draft ?? null);
|
|
298
|
+
setOpen(false);
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
{props.applyLabel ?? 'Apply'}
|
|
302
|
+
</Button>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</PopoverContent>
|
|
307
|
+
</Popover>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
DateTimePicker.displayName = 'DateTimePicker';
|
|
313
|
+
|
|
314
|
+
export default DateTimePicker;
|