@suphark/ui 0.39.0 → 0.40.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/CHANGELOG.md +31 -1
- package/package.json +1 -1
- package/src/components/design-system/sections/showcase-forms.tsx +1 -1
- package/src/components/shared/date/date-picker.tsx +377 -0
- package/src/components/shared/form/form-field-date.tsx +1 -1
- package/src/components/shared/ui-text.tsx +43 -0
- package/src/components/ui/date-picker.tsx +8 -306
- package/src/index.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
|
|
4
4
|
|
|
5
|
-
## [0.
|
|
5
|
+
## [0.40.1] — 2026-10-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `DatePicker`: ปฏิทินใน popup มีที่ว่างซ้ายขวามาก (เจ้าของแจ้ง) — กล่องกว้างตายตัว 320–340px แต่ช่องวันที่ 28px ปฏิทินจึงกว้างแค่ ~200px · ตอนนี้ช่องวันที่ 40px (กดง่ายขึ้นด้วย) และกล่องกว้างพอดีปฏิทิน 306px (commit แรกหลุด format ของ biome — แก้ในรุ่นเดียวกัน)
|
|
10
|
+
|
|
11
|
+
## [0.40.0] — 2026-10-07
|
|
12
|
+
|
|
13
|
+
> ประกาศพร้อม publish แล้ว แต่ยังไม่ขึ้น npm — รวมอยู่ใน 0.40.1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`DatePicker` ใช้ในฟอร์มและหลายภาษาได้** (ขอจาก Pi Auth — `<input type="date">` แสดง "10/02/2027" อ่านได้ทั้ง 2 ต.ค. และ 10 ก.พ.):
|
|
19
|
+
- `locale` (BCP 47): แสดงผ่าน Intl `{ day, month: "short", year }` ทั้งในช่อง ปฏิทิน และวันที่สัมพัทธ์ — `"en-GB"` = "2 Oct 2027", `"en"` = "Oct 2, 2027", `"th"` = "2 ต.ค. 2570" · ไม่ส่ง = รูปแบบไทยเดิม
|
|
20
|
+
- `name` (+ `defaultValue` แบบไม่ควบคุม, `value`/`onChange` ไม่บังคับแล้ว): ส่ง `YYYY-MM-DD` ตามวันที่ local ไปกับ FormData ของ `<form action>` · `required` ทำงานกับการตรวจของเบราว์เซอร์และตาข่ายฟอร์ม (ชื่อช่องจาก `LabeledField`)
|
|
21
|
+
- `showRelative` (ค่าเริ่มต้น true) · `toDateInputValue(date)` export ให้ใช้เอง · type `DatePickerProps`
|
|
22
|
+
- `UiText.datePicker` (placeholder, ทางลัด, ปุ่ม) ไทย/อังกฤษในตัว
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `DatePicker` ย้ายไป `components/shared/date/date-picker.tsx` — ไม่ได้มาจาก shadcn CLI (registry ของ base-nova ไม่มี date-picker) แต่ยกมาจาก supplychain; `components/ui/date-picker` ยัง re-export ให้ import path เดิมใช้ได้
|
|
27
|
+
- ข้อความเดิมที่ฝังไว้ย้ายไป UiText: placeholder ค่าเริ่มต้นจาก "เลือกวันที่อ้างอิงราคา..." (ของ supplychain) เป็น "เลือกวันที่", ทางลัดเป็นภาษาไทย ("วันนี้", "+1 สัปดาห์" …) แทนอังกฤษปนไทย, หัวข้อ "Quick Preset" → "ทางลัด"
|
|
28
|
+
- สีดิบ (slate/emerald) → semantic token · ปุ่ม "ตกลง" ของช่องพิมพ์วันที่เป็น `type="button"` (เดิม submit — ส่งฟอร์มแม่ได้ถ้าไม่ได้กด Enter) · ปุ่มล้างมี `aria-label` และกดด้วยคีย์บอร์ดได้
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `value="YYYY-MM-DD"` อ่านเป็น UTC แล้วเลื่อนวันในโซนเวลาตะวันตก → อ่านเป็นวันที่ local
|
|
33
|
+
- วันที่สัมพัทธ์ ("(พรุ่งนี้)") แสดงหลัง mount — ขึ้นกับวันของเครื่องผู้ใช้ จึง hydration mismatch ได้ข้ามวัน/โซนเวลา (ค้างใน todo)
|
|
34
|
+
- showcase: `/components/date-picker` เพิ่มตัวอย่างในฟอร์ม server action (`name` + `locale="en-GB"` + `required`)
|
|
35
|
+
|
|
6
36
|
|
|
7
37
|
### Added
|
|
8
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Design system กลางของ Suphark apps: shadcn/ui (base-nova, Base UI, Tailwind v4) + shared components + token/ฟอนต์ Noto Sans Thai Looped — ส่งเป็น source ให้แอป Next transpile เอง",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { Combobox } from "@suphark/ui/components/shared/combobox";
|
|
5
|
+
import { DatePicker } from "@suphark/ui/components/shared/date/date-picker";
|
|
5
6
|
import { MultiSelectCombobox } from "@suphark/ui/components/shared/multi-select-combobox";
|
|
6
7
|
import { Badge } from "@suphark/ui/components/ui/badge";
|
|
7
8
|
import {
|
|
@@ -11,7 +12,6 @@ import {
|
|
|
11
12
|
CardHeader,
|
|
12
13
|
CardTitle,
|
|
13
14
|
} from "@suphark/ui/components/ui/card";
|
|
14
|
-
import { DatePicker } from "@suphark/ui/components/ui/date-picker";
|
|
15
15
|
import {
|
|
16
16
|
Field,
|
|
17
17
|
FieldDescription,
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
5
|
+
import { Calendar } from "@suphark/ui/components/ui/calendar";
|
|
6
|
+
import {
|
|
7
|
+
InputGroup,
|
|
8
|
+
InputGroupAddon,
|
|
9
|
+
InputGroupButton,
|
|
10
|
+
InputGroupInput,
|
|
11
|
+
} from "@suphark/ui/components/ui/input-group";
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@suphark/ui/components/ui/popover";
|
|
13
|
+
import { useMounted } from "@suphark/ui/hooks/use-mounted";
|
|
14
|
+
import { formatRelativeThaiDate, formatThaiFullDate } from "@suphark/ui/lib/formatters";
|
|
15
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
16
|
+
import { addDays, addMonths, addWeeks, subDays, subMonths, subWeeks } from "date-fns";
|
|
17
|
+
import { CalendarIcon, Check, Clock, X } from "lucide-react";
|
|
18
|
+
import { useMemo, useState } from "react";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Smart date parser supporting intuitive Thai & international date inputs:
|
|
22
|
+
* - "1/7" -> 1 July of current year
|
|
23
|
+
* - "1/7/25" -> 1 July 2025 CE
|
|
24
|
+
* - "1/7/68" -> 1 July 2025 CE (BE 2568)
|
|
25
|
+
* - "1/7/2568" -> 1 July 2025 CE (BE 2568)
|
|
26
|
+
*/
|
|
27
|
+
export function parseSmartDateInput(input: string): Date | null {
|
|
28
|
+
if (!input?.trim()) return null;
|
|
29
|
+
const clean = input.trim().replace(/\s+/g, "");
|
|
30
|
+
|
|
31
|
+
const parts = clean.split(/[/\-.]/);
|
|
32
|
+
if (parts.length < 2 || parts.length > 3) return null;
|
|
33
|
+
|
|
34
|
+
const day = parseInt(parts[0] ?? "", 10);
|
|
35
|
+
const month = parseInt(parts[1] ?? "", 10);
|
|
36
|
+
if (Number.isNaN(day) || Number.isNaN(month) || day < 1 || day > 31 || month < 1 || month > 12) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const currentYear = new Date().getFullYear();
|
|
41
|
+
let year = currentYear;
|
|
42
|
+
|
|
43
|
+
if (parts.length === 3 && parts[2]) {
|
|
44
|
+
const rawYear = parseInt(parts[2], 10);
|
|
45
|
+
if (Number.isNaN(rawYear)) return null;
|
|
46
|
+
|
|
47
|
+
if (rawYear > 2400) {
|
|
48
|
+
// Full Buddhist Era year (e.g. 2568 -> 2025 CE)
|
|
49
|
+
year = rawYear - 543;
|
|
50
|
+
} else if (rawYear >= 50 && rawYear < 100) {
|
|
51
|
+
// Short BE year (e.g. 68 -> 2568 -> 2025 CE)
|
|
52
|
+
year = 2500 + rawYear - 543;
|
|
53
|
+
} else if (rawYear < 50) {
|
|
54
|
+
// Short AD year (e.g. 25 -> 2025 CE)
|
|
55
|
+
year = 2000 + rawYear;
|
|
56
|
+
} else {
|
|
57
|
+
year = rawYear;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const d = new Date(year, month - 1, day);
|
|
62
|
+
if (d.getFullYear() === year && d.getMonth() === month - 1 && d.getDate() === day) {
|
|
63
|
+
return d;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type DateInput = Date | string | number | null | undefined;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* แปลงค่าที่รับมาเป็น Date — `"YYYY-MM-DD"` อ่านเป็นวันที่ local
|
|
73
|
+
* (`new Date("2027-10-02")` อ่านเป็น UTC เที่ยงคืน → ฝั่งตะวันตกของ UTC กลายเป็นวันก่อนหน้า)
|
|
74
|
+
*/
|
|
75
|
+
function toDate(value: DateInput): Date | undefined {
|
|
76
|
+
if (value === null || value === undefined || value === "") return undefined;
|
|
77
|
+
if (typeof value === "string") {
|
|
78
|
+
const ymd = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
79
|
+
if (ymd) return new Date(Number(ymd[1]), Number(ymd[2]) - 1, Number(ymd[3]));
|
|
80
|
+
}
|
|
81
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
82
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `YYYY-MM-DD` ตามวันที่ local (ไม่ใช่ toISOString ที่เลื่อนวันตาม timezone) — ค่าที่ส่งในฟอร์ม */
|
|
86
|
+
export function toDateInputValue(date: Date | null | undefined): string {
|
|
87
|
+
if (!date) return "";
|
|
88
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
89
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** "2 Oct 2027" / "2 ต.ค. 2570" ตาม locale — ไม่ส่ง locale = รูปแบบไทยเดิม (เดือนย่อ + พ.ศ.) */
|
|
93
|
+
function formatDate(date: Date, locale: string | undefined): string {
|
|
94
|
+
if (!locale) return formatThaiFullDate(date);
|
|
95
|
+
return new Intl.DateTimeFormat(locale, {
|
|
96
|
+
day: "numeric",
|
|
97
|
+
month: "short",
|
|
98
|
+
year: "numeric",
|
|
99
|
+
}).format(date);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** "พรุ่งนี้" / "in 3 days" — นับเป็นวันตามปฏิทิน */
|
|
103
|
+
function formatRelative(date: Date, locale: string | undefined): string {
|
|
104
|
+
if (!locale) return formatRelativeThaiDate(date);
|
|
105
|
+
const startOf = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
106
|
+
const days = Math.round((startOf(date) - startOf(new Date())) / 86_400_000);
|
|
107
|
+
return new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(days, "day");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface DatePickerProps {
|
|
111
|
+
id?: string;
|
|
112
|
+
/** ควบคุมจากภายนอก (`"YYYY-MM-DD"` อ่านเป็นวันที่ local) — ใช้แบบไม่ควบคุมให้ส่ง `defaultValue` แทน */
|
|
113
|
+
value?: DateInput;
|
|
114
|
+
/** ค่าเริ่มต้นแบบไม่ควบคุม (ใช้คู่ `name` ใน `<form action>`) — `null` = เริ่มว่าง */
|
|
115
|
+
defaultValue?: DateInput;
|
|
116
|
+
onChange?: (date: Date | null) => void;
|
|
117
|
+
/**
|
|
118
|
+
* ชื่อฟิลด์ในฟอร์ม — ส่งค่า `YYYY-MM-DD` (วันที่ local) ไปกับ FormData ของ `<form action>`/server action
|
|
119
|
+
* ว่าง = ส่ง `""`
|
|
120
|
+
*/
|
|
121
|
+
name?: string;
|
|
122
|
+
/** ต้องเลือกวันที่ก่อนส่งฟอร์ม — ไม่เลือกแล้วกดส่ง เบราว์เซอร์บล็อกและตาข่ายฟอร์มบอกชื่อช่องให้ */
|
|
123
|
+
required?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* ภาษาของวันที่ (BCP 47 เช่น "en", "th") — แสดงแบบ "2 Oct 2027" / "2 ต.ค. 2570" ทั้งในช่อง ปฏิทิน และวันที่สัมพัทธ์
|
|
126
|
+
* ไม่ส่ง = รูปแบบไทยเดิม · ข้อความอื่นมาจาก `UiText.datePicker`
|
|
127
|
+
*/
|
|
128
|
+
locale?: string;
|
|
129
|
+
/** ไม่ส่ง = `UiText.datePicker.placeholder` */
|
|
130
|
+
placeholder?: string;
|
|
131
|
+
disabled?: boolean;
|
|
132
|
+
className?: string;
|
|
133
|
+
allowClear?: boolean;
|
|
134
|
+
showPresets?: boolean;
|
|
135
|
+
showSmartInput?: boolean;
|
|
136
|
+
/** แสดงวันที่สัมพัทธ์ต่อท้าย เช่น "(พรุ่งนี้)" — ค่าเริ่มต้น true */
|
|
137
|
+
showRelative?: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function DatePicker({
|
|
141
|
+
id,
|
|
142
|
+
value,
|
|
143
|
+
defaultValue,
|
|
144
|
+
onChange,
|
|
145
|
+
name,
|
|
146
|
+
required = false,
|
|
147
|
+
locale,
|
|
148
|
+
placeholder,
|
|
149
|
+
disabled = false,
|
|
150
|
+
className,
|
|
151
|
+
allowClear = true,
|
|
152
|
+
showPresets = true,
|
|
153
|
+
showSmartInput = true,
|
|
154
|
+
showRelative = true,
|
|
155
|
+
}: DatePickerProps) {
|
|
156
|
+
const text = useUiText().datePicker;
|
|
157
|
+
const mounted = useMounted();
|
|
158
|
+
const [open, setOpen] = useState(false);
|
|
159
|
+
const [smartText, setSmartText] = useState("");
|
|
160
|
+
// ไม่ควบคุม = มี defaultValue หรือไม่มีทั้ง value และ onChange (เช่นใช้แค่ name ในฟอร์ม)
|
|
161
|
+
const controlled = value !== undefined || (defaultValue === undefined && onChange !== undefined);
|
|
162
|
+
const [own, setOwn] = useState<Date | null>(() => toDate(defaultValue) ?? null);
|
|
163
|
+
|
|
164
|
+
const selectedDate = controlled ? toDate(value) : (own ?? undefined);
|
|
165
|
+
const isValidDate = selectedDate !== undefined;
|
|
166
|
+
|
|
167
|
+
const parsedSmartDate = useMemo(() => parseSmartDateInput(smartText), [smartText]);
|
|
168
|
+
|
|
169
|
+
const change = (date: Date | null) => {
|
|
170
|
+
if (!controlled) setOwn(date);
|
|
171
|
+
onChange?.(date);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const handleSelectDate = (date: Date | undefined) => {
|
|
175
|
+
change(date ?? null);
|
|
176
|
+
setOpen(false);
|
|
177
|
+
setSmartText("");
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const applyPreset = (date: Date | null) => {
|
|
181
|
+
change(date);
|
|
182
|
+
setOpen(false);
|
|
183
|
+
setSmartText("");
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const handleSmartSubmit = () => {
|
|
187
|
+
if (parsedSmartDate) {
|
|
188
|
+
change(parsedSmartDate);
|
|
189
|
+
setOpen(false);
|
|
190
|
+
setSmartText("");
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const today = new Date();
|
|
195
|
+
const displayMonth = parsedSmartDate || selectedDate;
|
|
196
|
+
const presets: Array<[string, Date]> = [
|
|
197
|
+
[text?.today ?? "วันนี้", today],
|
|
198
|
+
[text?.yesterday ?? "เมื่อวาน", subDays(today, 1)],
|
|
199
|
+
[text?.tomorrow ?? "พรุ่งนี้", addDays(today, 1)],
|
|
200
|
+
[text?.nextWeek ?? "+1 สัปดาห์", addWeeks(today, 1)],
|
|
201
|
+
[text?.lastWeek ?? "-1 สัปดาห์", subWeeks(today, 1)],
|
|
202
|
+
[text?.nextMonth ?? "+1 เดือน", addMonths(today, 1)],
|
|
203
|
+
[text?.lastMonth ?? "-1 เดือน", subMonths(today, 1)],
|
|
204
|
+
];
|
|
205
|
+
// ปฏิทินใช้ locale เดียวกับช่อง (ชื่อเดือน/วัน/ปี) — ไม่ส่ง locale = ค่าเดิมของ Calendar
|
|
206
|
+
const calendarFormatters = locale
|
|
207
|
+
? {
|
|
208
|
+
formatMonthDropdown: (d: Date) => d.toLocaleString(locale, { month: "short" }),
|
|
209
|
+
formatYearDropdown: (d: Date) => d.toLocaleString(locale, { year: "numeric" }),
|
|
210
|
+
formatWeekdayName: (d: Date) => d.toLocaleString(locale, { weekday: "narrow" }),
|
|
211
|
+
formatCaption: (d: Date) => d.toLocaleString(locale, { month: "long", year: "numeric" }),
|
|
212
|
+
}
|
|
213
|
+
: undefined;
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<>
|
|
217
|
+
<Popover
|
|
218
|
+
open={open}
|
|
219
|
+
onOpenChange={(o) => {
|
|
220
|
+
setOpen(o);
|
|
221
|
+
if (!o) setSmartText("");
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
<PopoverTrigger
|
|
225
|
+
render={
|
|
226
|
+
<Button
|
|
227
|
+
id={id}
|
|
228
|
+
variant="outline"
|
|
229
|
+
disabled={disabled}
|
|
230
|
+
aria-required={required || undefined}
|
|
231
|
+
className={cn(
|
|
232
|
+
"h-8 w-full justify-between bg-background pr-2 pl-3 text-left font-normal text-xs",
|
|
233
|
+
!isValidDate && "text-muted-foreground",
|
|
234
|
+
className,
|
|
235
|
+
)}
|
|
236
|
+
/>
|
|
237
|
+
}
|
|
238
|
+
>
|
|
239
|
+
<div className="flex items-center gap-2 truncate">
|
|
240
|
+
<CalendarIcon className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
241
|
+
<span className="truncate">
|
|
242
|
+
{isValidDate ? (
|
|
243
|
+
<>
|
|
244
|
+
<span className="font-medium text-foreground">
|
|
245
|
+
{formatDate(selectedDate, locale)}
|
|
246
|
+
</span>
|
|
247
|
+
{/* วันที่สัมพัทธ์ขึ้นกับ "วันนี้" ของเครื่องผู้ใช้ — แสดงหลัง mount กัน hydration mismatch */}
|
|
248
|
+
{showRelative && mounted && (
|
|
249
|
+
<span className="ml-1 text-muted-foreground text-xs">
|
|
250
|
+
({formatRelative(selectedDate, locale)})
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
</>
|
|
254
|
+
) : (
|
|
255
|
+
<span>{placeholder ?? text?.placeholder ?? "เลือกวันที่"}</span>
|
|
256
|
+
)}
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
{allowClear && isValidDate && !disabled && (
|
|
260
|
+
<span
|
|
261
|
+
role="button"
|
|
262
|
+
tabIndex={0}
|
|
263
|
+
aria-label={text?.clear ?? "ล้างวันที่"}
|
|
264
|
+
className="ml-1 cursor-pointer rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
265
|
+
onClick={(e) => {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
change(null);
|
|
268
|
+
}}
|
|
269
|
+
onKeyDown={(e) => {
|
|
270
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
e.stopPropagation();
|
|
273
|
+
change(null);
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<X className="h-3.5 w-3.5" />
|
|
277
|
+
</span>
|
|
278
|
+
)}
|
|
279
|
+
</PopoverTrigger>
|
|
280
|
+
{/* กว้างพอดีปฏิทิน: ช่องวันที่ 40px × 7 + p-3 + ขอบ (เดิมกล่อง 320–340px แต่ช่อง 28px → ว่างซ้ายขวามาก) */}
|
|
281
|
+
<PopoverContent className="w-[306px] border bg-background p-3 shadow-xl" align="start">
|
|
282
|
+
{/* Smart Date Text Input */}
|
|
283
|
+
{showSmartInput && (
|
|
284
|
+
<div className="mb-3 flex flex-col gap-1.5 border-b pb-2.5">
|
|
285
|
+
<InputGroup className="h-8">
|
|
286
|
+
<InputGroupInput
|
|
287
|
+
value={smartText}
|
|
288
|
+
onChange={(e) => setSmartText(e.target.value)}
|
|
289
|
+
onKeyDown={(event) => {
|
|
290
|
+
if (event.key === "Enter") {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
event.stopPropagation();
|
|
293
|
+
handleSmartSubmit();
|
|
294
|
+
}
|
|
295
|
+
}}
|
|
296
|
+
placeholder={text?.smartPlaceholder ?? "เช่น 1/7, 1/7/25, 1-7-2568..."}
|
|
297
|
+
className="h-8 text-xs"
|
|
298
|
+
/>
|
|
299
|
+
<InputGroupAddon align="inline-end">
|
|
300
|
+
<InputGroupButton
|
|
301
|
+
type="button"
|
|
302
|
+
variant="default"
|
|
303
|
+
size="xs"
|
|
304
|
+
onClick={handleSmartSubmit}
|
|
305
|
+
disabled={!parsedSmartDate}
|
|
306
|
+
>
|
|
307
|
+
{text?.apply ?? "ตกลง"}
|
|
308
|
+
</InputGroupButton>
|
|
309
|
+
</InputGroupAddon>
|
|
310
|
+
</InputGroup>
|
|
311
|
+
{parsedSmartDate && (
|
|
312
|
+
<span className="flex items-center gap-1 font-medium text-success text-xs">
|
|
313
|
+
<Check className="size-3" />
|
|
314
|
+
{formatDate(parsedSmartDate, locale)}
|
|
315
|
+
</span>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
|
|
320
|
+
{/* Quick Presets Section (Flex Wrap & Clean Spacing) */}
|
|
321
|
+
{showPresets && (
|
|
322
|
+
<div className="mb-3 border-b pb-2.5">
|
|
323
|
+
<div className="mb-1.5 flex items-center gap-1 font-semibold text-muted-foreground text-xs">
|
|
324
|
+
<Clock className="h-3 w-3" />
|
|
325
|
+
{text?.presets ?? "ทางลัด"}
|
|
326
|
+
</div>
|
|
327
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
328
|
+
{presets.map(([label, date]) => (
|
|
329
|
+
<Button
|
|
330
|
+
key={label}
|
|
331
|
+
type="button"
|
|
332
|
+
variant="outline"
|
|
333
|
+
size="sm"
|
|
334
|
+
className="h-6 px-2 font-normal text-xs hover:bg-muted"
|
|
335
|
+
onClick={() => applyPreset(date)}
|
|
336
|
+
>
|
|
337
|
+
{label}
|
|
338
|
+
</Button>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
{/* Visual DayPicker Calendar */}
|
|
345
|
+
<div className="flex w-full justify-center">
|
|
346
|
+
<Calendar
|
|
347
|
+
mode="single"
|
|
348
|
+
selected={selectedDate}
|
|
349
|
+
onSelect={handleSelectDate}
|
|
350
|
+
disabled={disabled}
|
|
351
|
+
captionLayout="dropdown"
|
|
352
|
+
startMonth={new Date(1990, 0)}
|
|
353
|
+
endMonth={new Date(2040, 11)}
|
|
354
|
+
defaultMonth={displayMonth}
|
|
355
|
+
formatters={calendarFormatters}
|
|
356
|
+
className="p-0 [--cell-size:--spacing(10)]"
|
|
357
|
+
/>
|
|
358
|
+
</div>
|
|
359
|
+
</PopoverContent>
|
|
360
|
+
</Popover>
|
|
361
|
+
{name !== undefined && (
|
|
362
|
+
// ค่าที่ส่งไปกับฟอร์ม — ใช้ input ข้อความที่มองไม่เห็นแทน type="hidden" เพราะ hidden ไม่ถูกตรวจ `required`
|
|
363
|
+
// อยู่หลังปุ่มเสมอ: label ที่ครอบ (LabeledField) คลิกแล้วไปที่ปุ่ม ไม่ใช่ช่องนี้ · ตาข่ายฟอร์มอ่านชื่อจาก label เดียวกัน
|
|
364
|
+
<input
|
|
365
|
+
name={name}
|
|
366
|
+
value={toDateInputValue(selectedDate)}
|
|
367
|
+
onChange={() => {}}
|
|
368
|
+
required={required}
|
|
369
|
+
disabled={disabled}
|
|
370
|
+
tabIndex={-1}
|
|
371
|
+
aria-hidden
|
|
372
|
+
className="sr-only"
|
|
373
|
+
/>
|
|
374
|
+
)}
|
|
375
|
+
</>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
@@ -175,6 +175,21 @@ export interface UiText {
|
|
|
175
175
|
characterCount?: string;
|
|
176
176
|
/** NumberInput (optional) */
|
|
177
177
|
number?: Partial<{ increment: string; decrement: string }>;
|
|
178
|
+
/** DatePicker (optional) — ทางลัดวันที่ + ช่องพิมพ์วันที่ */
|
|
179
|
+
datePicker?: Partial<{
|
|
180
|
+
placeholder: string;
|
|
181
|
+
smartPlaceholder: string;
|
|
182
|
+
apply: string;
|
|
183
|
+
presets: string;
|
|
184
|
+
today: string;
|
|
185
|
+
yesterday: string;
|
|
186
|
+
tomorrow: string;
|
|
187
|
+
nextWeek: string;
|
|
188
|
+
lastWeek: string;
|
|
189
|
+
nextMonth: string;
|
|
190
|
+
lastMonth: string;
|
|
191
|
+
clear: string;
|
|
192
|
+
}>;
|
|
178
193
|
/** TimePicker / DateTimePicker (optional) */
|
|
179
194
|
time?: Partial<{
|
|
180
195
|
choose: string;
|
|
@@ -340,6 +355,20 @@ export const UI_TEXT_TH: UiText = {
|
|
|
340
355
|
presence: { online: "ออนไลน์", away: "ไม่อยู่", busy: "ไม่ว่าง", offline: "ออฟไลน์" },
|
|
341
356
|
characterCount: "พิมพ์แล้ว {count} จาก {max} ตัวอักษร",
|
|
342
357
|
number: { increment: "เพิ่ม", decrement: "ลด" },
|
|
358
|
+
datePicker: {
|
|
359
|
+
placeholder: "เลือกวันที่",
|
|
360
|
+
smartPlaceholder: "เช่น 1/7, 1/7/25, 1-7-2568...",
|
|
361
|
+
apply: "ตกลง",
|
|
362
|
+
presets: "ทางลัด",
|
|
363
|
+
today: "วันนี้",
|
|
364
|
+
yesterday: "เมื่อวาน",
|
|
365
|
+
tomorrow: "พรุ่งนี้",
|
|
366
|
+
nextWeek: "+1 สัปดาห์",
|
|
367
|
+
lastWeek: "-1 สัปดาห์",
|
|
368
|
+
nextMonth: "+1 เดือน",
|
|
369
|
+
lastMonth: "-1 เดือน",
|
|
370
|
+
clear: "ล้างวันที่",
|
|
371
|
+
},
|
|
343
372
|
time: {
|
|
344
373
|
choose: "เลือกเวลา",
|
|
345
374
|
hour: "ชั่วโมง",
|
|
@@ -511,6 +540,20 @@ export const UI_TEXT_EN: UiText = {
|
|
|
511
540
|
presence: { online: "Online", away: "Away", busy: "Busy", offline: "Offline" },
|
|
512
541
|
characterCount: "{count} of {max} characters",
|
|
513
542
|
number: { increment: "Increase", decrement: "Decrease" },
|
|
543
|
+
datePicker: {
|
|
544
|
+
placeholder: "Pick a date",
|
|
545
|
+
smartPlaceholder: "e.g. 1/7, 1/7/25, 1-7-2025…",
|
|
546
|
+
apply: "Apply",
|
|
547
|
+
presets: "Shortcuts",
|
|
548
|
+
today: "Today",
|
|
549
|
+
yesterday: "Yesterday",
|
|
550
|
+
tomorrow: "Tomorrow",
|
|
551
|
+
nextWeek: "+1 week",
|
|
552
|
+
lastWeek: "-1 week",
|
|
553
|
+
nextMonth: "+1 month",
|
|
554
|
+
lastMonth: "-1 month",
|
|
555
|
+
clear: "Clear date",
|
|
556
|
+
},
|
|
514
557
|
time: {
|
|
515
558
|
choose: "Choose time",
|
|
516
559
|
hour: "Hour",
|
|
@@ -1,308 +1,10 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Button } from "@suphark/ui/components/ui/button";
|
|
4
|
-
import { Calendar } from "@suphark/ui/components/ui/calendar";
|
|
5
|
-
import {
|
|
6
|
-
InputGroup,
|
|
7
|
-
InputGroupAddon,
|
|
8
|
-
InputGroupButton,
|
|
9
|
-
InputGroupInput,
|
|
10
|
-
} from "@suphark/ui/components/ui/input-group";
|
|
11
|
-
import { Popover, PopoverContent, PopoverTrigger } from "@suphark/ui/components/ui/popover";
|
|
12
|
-
import { formatRelativeThaiDate, formatThaiFullDate } from "@suphark/ui/lib/formatters";
|
|
13
|
-
import { cn } from "@suphark/ui/lib/utils";
|
|
14
|
-
import { addDays, addMonths, addWeeks, subDays, subMonths, subWeeks } from "date-fns";
|
|
15
|
-
import { CalendarIcon, Check, Clock, X } from "lucide-react";
|
|
16
|
-
import { useMemo, useState } from "react";
|
|
17
|
-
|
|
18
1
|
/**
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* - "1/7/25" -> 1 July 2025 CE
|
|
22
|
-
* - "1/7/68" -> 1 July 2025 CE (BE 2568)
|
|
23
|
-
* - "1/7/2568" -> 1 July 2025 CE (BE 2568)
|
|
2
|
+
* ย้ายไป `components/shared/date/date-picker.tsx` แล้ว (0.40.0) — ไฟล์นี้ไม่ได้มาจาก shadcn CLI
|
|
3
|
+
* (registry ของ base-nova ไม่มี date-picker) แต่ยกมาจาก supplychain · คงไว้ให้ import path เดิมใช้ได้
|
|
24
4
|
*/
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const day = parseInt(parts[0] ?? "", 10);
|
|
33
|
-
const month = parseInt(parts[1] ?? "", 10);
|
|
34
|
-
if (Number.isNaN(day) || Number.isNaN(month) || day < 1 || day > 31 || month < 1 || month > 12) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const currentYear = new Date().getFullYear();
|
|
39
|
-
let year = currentYear;
|
|
40
|
-
|
|
41
|
-
if (parts.length === 3 && parts[2]) {
|
|
42
|
-
const rawYear = parseInt(parts[2], 10);
|
|
43
|
-
if (Number.isNaN(rawYear)) return null;
|
|
44
|
-
|
|
45
|
-
if (rawYear > 2400) {
|
|
46
|
-
// Full Buddhist Era year (e.g. 2568 -> 2025 CE)
|
|
47
|
-
year = rawYear - 543;
|
|
48
|
-
} else if (rawYear >= 50 && rawYear < 100) {
|
|
49
|
-
// Short BE year (e.g. 68 -> 2568 -> 2025 CE)
|
|
50
|
-
year = 2500 + rawYear - 543;
|
|
51
|
-
} else if (rawYear < 50) {
|
|
52
|
-
// Short AD year (e.g. 25 -> 2025 CE)
|
|
53
|
-
year = 2000 + rawYear;
|
|
54
|
-
} else {
|
|
55
|
-
year = rawYear;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const d = new Date(year, month - 1, day);
|
|
60
|
-
if (d.getFullYear() === year && d.getMonth() === month - 1 && d.getDate() === day) {
|
|
61
|
-
return d;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface DatePickerProps {
|
|
68
|
-
id?: string;
|
|
69
|
-
value: Date | string | number | null | undefined;
|
|
70
|
-
onChange: (date: Date | null) => void;
|
|
71
|
-
placeholder?: string;
|
|
72
|
-
disabled?: boolean;
|
|
73
|
-
className?: string;
|
|
74
|
-
allowClear?: boolean;
|
|
75
|
-
showPresets?: boolean;
|
|
76
|
-
showSmartInput?: boolean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function DatePicker({
|
|
80
|
-
id,
|
|
81
|
-
value,
|
|
82
|
-
onChange,
|
|
83
|
-
placeholder = "เลือกวันที่อ้างอิงราคา...",
|
|
84
|
-
disabled = false,
|
|
85
|
-
className,
|
|
86
|
-
allowClear = true,
|
|
87
|
-
showPresets = true,
|
|
88
|
-
showSmartInput = true,
|
|
89
|
-
}: DatePickerProps) {
|
|
90
|
-
const [open, setOpen] = useState(false);
|
|
91
|
-
const [smartText, setSmartText] = useState("");
|
|
92
|
-
|
|
93
|
-
const selectedDate = value ? new Date(value) : undefined;
|
|
94
|
-
const isValidDate = selectedDate && !Number.isNaN(selectedDate.getTime());
|
|
95
|
-
|
|
96
|
-
const parsedSmartDate = useMemo(() => parseSmartDateInput(smartText), [smartText]);
|
|
97
|
-
|
|
98
|
-
const handleSelectDate = (date: Date | undefined) => {
|
|
99
|
-
onChange(date || null);
|
|
100
|
-
setOpen(false);
|
|
101
|
-
setSmartText("");
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const applyPreset = (date: Date | null) => {
|
|
105
|
-
onChange(date);
|
|
106
|
-
setOpen(false);
|
|
107
|
-
setSmartText("");
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const handleSmartSubmit = () => {
|
|
111
|
-
if (parsedSmartDate) {
|
|
112
|
-
onChange(parsedSmartDate);
|
|
113
|
-
setOpen(false);
|
|
114
|
-
setSmartText("");
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const today = new Date();
|
|
119
|
-
const displayMonth = parsedSmartDate || (isValidDate ? selectedDate : undefined);
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<Popover
|
|
123
|
-
open={open}
|
|
124
|
-
onOpenChange={(o) => {
|
|
125
|
-
setOpen(o);
|
|
126
|
-
if (!o) setSmartText("");
|
|
127
|
-
}}
|
|
128
|
-
>
|
|
129
|
-
<PopoverTrigger
|
|
130
|
-
render={
|
|
131
|
-
<Button
|
|
132
|
-
id={id}
|
|
133
|
-
variant="outline"
|
|
134
|
-
disabled={disabled}
|
|
135
|
-
className={cn(
|
|
136
|
-
"h-8 w-full justify-between bg-background pr-2 pl-3 text-left font-normal text-xs",
|
|
137
|
-
!isValidDate && "text-muted-foreground",
|
|
138
|
-
className,
|
|
139
|
-
)}
|
|
140
|
-
/>
|
|
141
|
-
}
|
|
142
|
-
>
|
|
143
|
-
<div className="flex items-center gap-2 truncate">
|
|
144
|
-
<CalendarIcon className="h-3.5 w-3.5 shrink-0 text-slate-500" />
|
|
145
|
-
<span className="truncate">
|
|
146
|
-
{isValidDate ? (
|
|
147
|
-
<>
|
|
148
|
-
<span className="font-medium text-foreground">
|
|
149
|
-
{formatThaiFullDate(selectedDate)}
|
|
150
|
-
</span>
|
|
151
|
-
<span className="ml-1 text-muted-foreground text-xs">
|
|
152
|
-
({formatRelativeThaiDate(selectedDate)})
|
|
153
|
-
</span>
|
|
154
|
-
</>
|
|
155
|
-
) : (
|
|
156
|
-
<span>{placeholder}</span>
|
|
157
|
-
)}
|
|
158
|
-
</span>
|
|
159
|
-
</div>
|
|
160
|
-
{allowClear && isValidDate && !disabled && (
|
|
161
|
-
<span
|
|
162
|
-
role="button"
|
|
163
|
-
tabIndex={0}
|
|
164
|
-
className="ml-1 cursor-pointer rounded p-0.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600"
|
|
165
|
-
onClick={(e) => {
|
|
166
|
-
e.stopPropagation();
|
|
167
|
-
onChange(null);
|
|
168
|
-
}}
|
|
169
|
-
>
|
|
170
|
-
<X className="h-3.5 w-3.5" />
|
|
171
|
-
</span>
|
|
172
|
-
)}
|
|
173
|
-
</PopoverTrigger>
|
|
174
|
-
<PopoverContent
|
|
175
|
-
className="w-[320px] border bg-background p-3 shadow-xl sm:w-[340px]"
|
|
176
|
-
align="start"
|
|
177
|
-
>
|
|
178
|
-
{/* Smart Date Text Input */}
|
|
179
|
-
{showSmartInput && (
|
|
180
|
-
<div className="mb-3 flex flex-col gap-1.5 border-b pb-2.5">
|
|
181
|
-
<InputGroup className="h-8">
|
|
182
|
-
<InputGroupInput
|
|
183
|
-
value={smartText}
|
|
184
|
-
onChange={(e) => setSmartText(e.target.value)}
|
|
185
|
-
onKeyDown={(event) => {
|
|
186
|
-
if (event.key === "Enter") {
|
|
187
|
-
event.preventDefault();
|
|
188
|
-
event.stopPropagation();
|
|
189
|
-
handleSmartSubmit();
|
|
190
|
-
}
|
|
191
|
-
}}
|
|
192
|
-
placeholder="เช่น 1/7, 1/7/25, 1-7-2568..."
|
|
193
|
-
className="h-8 text-xs"
|
|
194
|
-
/>
|
|
195
|
-
<InputGroupAddon align="inline-end">
|
|
196
|
-
<InputGroupButton
|
|
197
|
-
type="submit"
|
|
198
|
-
variant="default"
|
|
199
|
-
size="xs"
|
|
200
|
-
onClick={handleSmartSubmit}
|
|
201
|
-
disabled={!parsedSmartDate}
|
|
202
|
-
>
|
|
203
|
-
ตกลง
|
|
204
|
-
</InputGroupButton>
|
|
205
|
-
</InputGroupAddon>
|
|
206
|
-
</InputGroup>
|
|
207
|
-
{parsedSmartDate && (
|
|
208
|
-
<span className="flex items-center gap-1 font-medium text-emerald-600 text-xs">
|
|
209
|
-
<Check className="size-3" />
|
|
210
|
-
{formatThaiFullDate(parsedSmartDate)}
|
|
211
|
-
</span>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
)}
|
|
215
|
-
|
|
216
|
-
{/* Quick Presets Section (Flex Wrap & Clean Spacing) */}
|
|
217
|
-
{showPresets && (
|
|
218
|
-
<div className="mb-3 border-b pb-2.5">
|
|
219
|
-
<div className="mb-1.5 flex items-center gap-1 font-semibold text-muted-foreground text-xs">
|
|
220
|
-
<Clock className="h-3 w-3" />
|
|
221
|
-
Quick Preset
|
|
222
|
-
</div>
|
|
223
|
-
<div className="flex flex-wrap items-center gap-1.5">
|
|
224
|
-
<Button
|
|
225
|
-
type="button"
|
|
226
|
-
variant="outline"
|
|
227
|
-
size="sm"
|
|
228
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
229
|
-
onClick={() => applyPreset(today)}
|
|
230
|
-
>
|
|
231
|
-
Today
|
|
232
|
-
</Button>
|
|
233
|
-
<Button
|
|
234
|
-
type="button"
|
|
235
|
-
variant="outline"
|
|
236
|
-
size="sm"
|
|
237
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
238
|
-
onClick={() => applyPreset(subDays(today, 1))}
|
|
239
|
-
>
|
|
240
|
-
Yesterday
|
|
241
|
-
</Button>
|
|
242
|
-
<Button
|
|
243
|
-
type="button"
|
|
244
|
-
variant="outline"
|
|
245
|
-
size="sm"
|
|
246
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
247
|
-
onClick={() => applyPreset(addDays(today, 1))}
|
|
248
|
-
>
|
|
249
|
-
Tomorrow
|
|
250
|
-
</Button>
|
|
251
|
-
<Button
|
|
252
|
-
type="button"
|
|
253
|
-
variant="outline"
|
|
254
|
-
size="sm"
|
|
255
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
256
|
-
onClick={() => applyPreset(addWeeks(today, 1))}
|
|
257
|
-
>
|
|
258
|
-
+1 Week
|
|
259
|
-
</Button>
|
|
260
|
-
<Button
|
|
261
|
-
type="button"
|
|
262
|
-
variant="outline"
|
|
263
|
-
size="sm"
|
|
264
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
265
|
-
onClick={() => applyPreset(subWeeks(today, 1))}
|
|
266
|
-
>
|
|
267
|
-
-1 Week
|
|
268
|
-
</Button>
|
|
269
|
-
<Button
|
|
270
|
-
type="button"
|
|
271
|
-
variant="outline"
|
|
272
|
-
size="sm"
|
|
273
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
274
|
-
onClick={() => applyPreset(addMonths(today, 1))}
|
|
275
|
-
>
|
|
276
|
-
+1 Month
|
|
277
|
-
</Button>
|
|
278
|
-
<Button
|
|
279
|
-
type="button"
|
|
280
|
-
variant="outline"
|
|
281
|
-
size="sm"
|
|
282
|
-
className="h-6 px-2 font-normal text-xs hover:bg-slate-100"
|
|
283
|
-
onClick={() => applyPreset(subMonths(today, 1))}
|
|
284
|
-
>
|
|
285
|
-
-1 Month
|
|
286
|
-
</Button>
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
289
|
-
)}
|
|
290
|
-
|
|
291
|
-
{/* Visual DayPicker Calendar */}
|
|
292
|
-
<div className="flex w-full justify-center">
|
|
293
|
-
<Calendar
|
|
294
|
-
mode="single"
|
|
295
|
-
selected={isValidDate ? selectedDate : undefined}
|
|
296
|
-
onSelect={handleSelectDate}
|
|
297
|
-
disabled={disabled}
|
|
298
|
-
captionLayout="dropdown"
|
|
299
|
-
startMonth={new Date(1990, 0)}
|
|
300
|
-
endMonth={new Date(2040, 11)}
|
|
301
|
-
defaultMonth={displayMonth}
|
|
302
|
-
className="p-0"
|
|
303
|
-
/>
|
|
304
|
-
</div>
|
|
305
|
-
</PopoverContent>
|
|
306
|
-
</Popover>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
5
|
+
export {
|
|
6
|
+
DatePicker,
|
|
7
|
+
type DatePickerProps,
|
|
8
|
+
parseSmartDateInput,
|
|
9
|
+
toDateInputValue,
|
|
10
|
+
} from "@suphark/ui/components/shared/date/date-picker";
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,12 @@ export {
|
|
|
76
76
|
} from "./components/shared/confirm-dialog";
|
|
77
77
|
export { CountBadge } from "./components/shared/count-badge";
|
|
78
78
|
export { DataList, DataRow } from "./components/shared/data-list";
|
|
79
|
+
export {
|
|
80
|
+
DatePicker,
|
|
81
|
+
type DatePickerProps,
|
|
82
|
+
parseSmartDateInput,
|
|
83
|
+
toDateInputValue,
|
|
84
|
+
} from "./components/shared/date/date-picker";
|
|
79
85
|
export { DateRangePicker } from "./components/shared/date/date-range-picker";
|
|
80
86
|
export { DescriptionList } from "./components/shared/description-list";
|
|
81
87
|
export {
|
|
@@ -403,7 +409,6 @@ export {
|
|
|
403
409
|
ContextMenuSubTrigger,
|
|
404
410
|
ContextMenuTrigger,
|
|
405
411
|
} from "./components/ui/context-menu";
|
|
406
|
-
export { DatePicker, parseSmartDateInput } from "./components/ui/date-picker";
|
|
407
412
|
export {
|
|
408
413
|
Dialog,
|
|
409
414
|
DialogClose,
|