@loword/loword-design-system 0.0.9 → 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.
Files changed (166) hide show
  1. package/dist/components/LDAutoComplete/AutoCompleteBase.js +6 -6
  2. package/dist/components/LDBadge/LDBadge.d.ts +5 -6
  3. package/dist/components/LDBadge/LDBadge.d.ts.map +1 -1
  4. package/dist/components/LDBadge/LDBadge.js +2 -2
  5. package/dist/components/LDBreadCrumb/LDBreadCrumb.d.ts +1 -2
  6. package/dist/components/LDBreadCrumb/LDBreadCrumb.d.ts.map +1 -1
  7. package/dist/components/LDButton/LDButton.d.ts +3 -3
  8. package/dist/components/LDButton/LDButton.d.ts.map +1 -1
  9. package/dist/components/LDButton/LDButton.js +5 -5
  10. package/dist/components/LDCalendar/LDCalendar.d.ts +7 -6
  11. package/dist/components/LDCalendar/LDCalendar.d.ts.map +1 -1
  12. package/dist/components/LDCalendar/LDCalendar.js +59 -16
  13. package/dist/components/LDCarousel/CarouselBase.d.ts.map +1 -1
  14. package/dist/components/LDCarousel/CarouselBase.js +8 -4
  15. package/dist/components/LDCheckbox/CheckboxBase.d.ts +1 -3
  16. package/dist/components/LDCheckbox/CheckboxBase.d.ts.map +1 -1
  17. package/dist/components/LDCheckbox/CheckboxBase.js +3 -3
  18. package/dist/components/LDCheckbox/LDCheckbox.d.ts.map +1 -1
  19. package/dist/components/LDCheckbox/LDCheckbox.js +4 -4
  20. package/dist/components/LDCheckbox/LDCheckboxGroup.js +1 -1
  21. package/dist/components/LDCheckbox/LDFormCheckbox.d.ts +14 -0
  22. package/dist/components/LDCheckbox/LDFormCheckbox.d.ts.map +1 -0
  23. package/dist/components/LDCheckbox/{FormCheckbox.js → LDFormCheckbox.js} +2 -2
  24. package/dist/components/LDCheckbox/LDFormCheckboxGroup.d.ts +13 -0
  25. package/dist/components/LDCheckbox/LDFormCheckboxGroup.d.ts.map +1 -0
  26. package/dist/components/LDCheckbox/{FormCheckboxGroup.js → LDFormCheckboxGroup.js} +2 -2
  27. package/dist/components/LDDatePicker/LDDatePicker.d.ts +10 -4
  28. package/dist/components/LDDatePicker/LDDatePicker.d.ts.map +1 -1
  29. package/dist/components/LDDatePicker/LDDatePicker.js +95 -25
  30. package/dist/components/LDDatePicker/LDFormDatePicker.d.ts +13 -0
  31. package/dist/components/LDDatePicker/LDFormDatePicker.d.ts.map +1 -0
  32. package/dist/components/LDDatePicker/LDFormDatePicker.js +15 -0
  33. package/dist/components/LDDateRangePicker/LDDateRangePicker.d.ts +13 -6
  34. package/dist/components/LDDateRangePicker/LDDateRangePicker.d.ts.map +1 -1
  35. package/dist/components/LDDateRangePicker/LDDateRangePicker.js +143 -93
  36. package/dist/components/LDDateRangePicker/LDFormDateRangePicker.d.ts +13 -0
  37. package/dist/components/LDDateRangePicker/LDFormDateRangePicker.d.ts.map +1 -0
  38. package/dist/components/LDDateRangePicker/LDFormDateRangePicker.js +20 -0
  39. package/dist/components/LDDrawer/LDDrawer.js +2 -2
  40. package/dist/components/LDFileUpload/FileUploadBase.d.ts +11 -3
  41. package/dist/components/LDFileUpload/FileUploadBase.d.ts.map +1 -1
  42. package/dist/components/LDFileUpload/FileUploadBase.js +17 -17
  43. package/dist/components/LDFileUpload/LDFileUpload.d.ts.map +1 -1
  44. package/dist/components/LDFileUpload/LDFileUpload.js +3 -1
  45. package/dist/components/LDFileUpload/LDFileUploadList.js +1 -1
  46. package/dist/components/LDFileUpload/LDImageUpload.d.ts +2 -0
  47. package/dist/components/LDFileUpload/LDImageUpload.d.ts.map +1 -1
  48. package/dist/components/LDFileUpload/LDImageUpload.js +5 -1
  49. package/dist/components/LDFileUpload/LDImageUploadList.js +1 -1
  50. package/dist/components/LDForm/LDFormErrorMessage.d.ts +3 -1
  51. package/dist/components/LDForm/LDFormErrorMessage.d.ts.map +1 -1
  52. package/dist/components/LDForm/LDFormErrorMessage.js +2 -2
  53. package/dist/components/LDInput/LDFormInput.d.ts +1 -1
  54. package/dist/components/LDInput/LDFormInput.d.ts.map +1 -1
  55. package/dist/components/LDInput/LDFormInput.js +37 -14
  56. package/dist/components/LDInput/LDInput.d.ts +4 -3
  57. package/dist/components/LDInput/LDInput.d.ts.map +1 -1
  58. package/dist/components/LDInput/LDInput.js +49 -48
  59. package/dist/components/LDModal/LDModal.d.ts +19 -40
  60. package/dist/components/LDModal/LDModal.d.ts.map +1 -1
  61. package/dist/components/LDModal/LDModal.js +56 -70
  62. package/dist/components/LDModal/LDModalView.d.ts +51 -0
  63. package/dist/components/LDModal/LDModalView.d.ts.map +1 -0
  64. package/dist/components/LDModal/LDModalView.js +72 -0
  65. package/dist/components/LDOTPInput/LDOTPInput.d.ts.map +1 -1
  66. package/dist/components/LDOTPInput/LDOTPInput.js +4 -3
  67. package/dist/components/LDPagination/LDPagination.d.ts +5 -1
  68. package/dist/components/LDPagination/LDPagination.d.ts.map +1 -1
  69. package/dist/components/LDPagination/LDPagination.js +10 -1
  70. package/dist/components/LDPagination/PaginationBase.d.ts +13 -4
  71. package/dist/components/LDPagination/PaginationBase.d.ts.map +1 -1
  72. package/dist/components/LDPagination/PaginationBase.js +8 -7
  73. package/dist/components/LDPopover/LDPopover.js +3 -3
  74. package/dist/components/LDRadio/LDFormRadioGroup.d.ts +13 -0
  75. package/dist/components/LDRadio/LDFormRadioGroup.d.ts.map +1 -0
  76. package/dist/components/LDRadio/{FormRadioGroup.js → LDFormRadioGroup.js} +2 -2
  77. package/dist/components/LDRadio/LDRadio.d.ts +1 -1
  78. package/dist/components/LDRadio/LDRadio.d.ts.map +1 -1
  79. package/dist/components/LDRadio/LDRadio.js +15 -6
  80. package/dist/components/LDRadio/LDRadioGroup.d.ts.map +1 -1
  81. package/dist/components/LDRadio/LDRadioGroup.js +1 -1
  82. package/dist/components/LDRadio/RadioBase.d.ts +2 -3
  83. package/dist/components/LDRadio/RadioBase.d.ts.map +1 -1
  84. package/dist/components/LDRadio/RadioBase.js +7 -4
  85. package/dist/components/LDSelect/LDFormMultiSelect.d.ts +13 -0
  86. package/dist/components/LDSelect/LDFormMultiSelect.d.ts.map +1 -0
  87. package/dist/components/LDSelect/{FormMultiSelect.js → LDFormMultiSelect.js} +2 -2
  88. package/dist/components/LDSelect/LDFormSelect.d.ts +13 -0
  89. package/dist/components/LDSelect/LDFormSelect.d.ts.map +1 -0
  90. package/dist/components/LDSelect/LDFormSelect.js +10 -0
  91. package/dist/components/LDSelect/LDMultiSelect.d.ts +6 -3
  92. package/dist/components/LDSelect/LDMultiSelect.d.ts.map +1 -1
  93. package/dist/components/LDSelect/LDMultiSelect.js +35 -31
  94. package/dist/components/LDSelect/LDSelect.d.ts +6 -3
  95. package/dist/components/LDSelect/LDSelect.d.ts.map +1 -1
  96. package/dist/components/LDSelect/LDSelect.js +37 -30
  97. package/dist/components/LDSelect/SelectBase.d.ts.map +1 -1
  98. package/dist/components/LDSelect/SelectBase.js +21 -7
  99. package/dist/components/LDSpinner/LDSpinner.d.ts +1 -1
  100. package/dist/components/LDSpinner/LDSpinner.d.ts.map +1 -1
  101. package/dist/components/LDSpinner/LDSpinner.js +6 -6
  102. package/dist/components/LDTable/LDTanstackTable.js +1 -1
  103. package/dist/components/LDTable/TableField.d.ts +1 -1
  104. package/dist/components/LDTable/TableField.d.ts.map +1 -1
  105. package/dist/components/LDTextarea/LDFormTextarea.d.ts +14 -0
  106. package/dist/components/LDTextarea/LDFormTextarea.d.ts.map +1 -0
  107. package/dist/components/LDTextarea/{FormTextarea.js → LDFormTextarea.js} +2 -2
  108. package/dist/components/LDTextarea/LDTextarea.d.ts +1 -1
  109. package/dist/components/LDTextarea/LDTextarea.d.ts.map +1 -1
  110. package/dist/components/LDTextarea/LDTextarea.js +30 -30
  111. package/dist/components/LDTimePicker/LDFormTimePicker.d.ts +12 -0
  112. package/dist/components/LDTimePicker/LDFormTimePicker.d.ts.map +1 -0
  113. package/dist/components/LDTimePicker/{FormTimePicker.js → LDFormTimePicker.js} +3 -3
  114. package/dist/components/LDTimePicker/LDTimePicker.js +2 -2
  115. package/dist/components/LDToast/LDToast.js +1 -1
  116. package/dist/components/LDToggleButton/LDFormToggleButton.d.ts +12 -0
  117. package/dist/components/LDToggleButton/LDFormToggleButton.d.ts.map +1 -0
  118. package/dist/components/LDToggleButton/{FormToggleButton.js → LDFormToggleButton.js} +2 -2
  119. package/dist/index.css +349 -207
  120. package/dist/index.d.ts +13 -12
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +13 -13
  123. package/dist/utils/cn.d.ts.map +1 -1
  124. package/dist/utils/cn.js +25 -1
  125. package/dist/utils/file.d.ts +2 -2
  126. package/dist/utils/file.d.ts.map +1 -1
  127. package/dist/utils/file.js +26 -23
  128. package/dist/utils/renderIcon.d.ts +1 -2
  129. package/dist/utils/renderIcon.d.ts.map +1 -1
  130. package/dist/utils/renderIcon.js +5 -4
  131. package/package.json +1 -1
  132. package/src/styles/components/button.css +2 -2
  133. package/src/styles/components/index.css +1 -0
  134. package/src/styles/components/input.css +2 -2
  135. package/src/styles/components/select.css +61 -0
  136. package/src/styles/motion.css +37 -0
  137. package/src/theme.css +3 -0
  138. package/dist/components/Icon/LucideIcon.d.ts +0 -12
  139. package/dist/components/Icon/LucideIcon.d.ts.map +0 -1
  140. package/dist/components/Icon/LucideIcon.js +0 -49
  141. package/dist/components/LDCheckbox/FormCheckbox.d.ts +0 -14
  142. package/dist/components/LDCheckbox/FormCheckbox.d.ts.map +0 -1
  143. package/dist/components/LDCheckbox/FormCheckboxGroup.d.ts +0 -13
  144. package/dist/components/LDCheckbox/FormCheckboxGroup.d.ts.map +0 -1
  145. package/dist/components/LDDatePicker/FormDatePicker.d.ts +0 -12
  146. package/dist/components/LDDatePicker/FormDatePicker.d.ts.map +0 -1
  147. package/dist/components/LDDatePicker/FormDatePicker.js +0 -12
  148. package/dist/components/LDDateRangePicker/FormDateRangePicker.d.ts +0 -12
  149. package/dist/components/LDDateRangePicker/FormDateRangePicker.d.ts.map +0 -1
  150. package/dist/components/LDDateRangePicker/FormDateRangePicker.js +0 -10
  151. package/dist/components/LDModal/openModal.d.ts +0 -24
  152. package/dist/components/LDModal/openModal.d.ts.map +0 -1
  153. package/dist/components/LDModal/openModal.js +0 -52
  154. package/dist/components/LDRadio/FormRadioGroup.d.ts +0 -13
  155. package/dist/components/LDRadio/FormRadioGroup.d.ts.map +0 -1
  156. package/dist/components/LDSelect/FormMultiSelect.d.ts +0 -13
  157. package/dist/components/LDSelect/FormMultiSelect.d.ts.map +0 -1
  158. package/dist/components/LDSelect/FormSelect.d.ts +0 -13
  159. package/dist/components/LDSelect/FormSelect.d.ts.map +0 -1
  160. package/dist/components/LDSelect/FormSelect.js +0 -10
  161. package/dist/components/LDTextarea/FormTextarea.d.ts +0 -14
  162. package/dist/components/LDTextarea/FormTextarea.d.ts.map +0 -1
  163. package/dist/components/LDTimePicker/FormTimePicker.d.ts +0 -12
  164. package/dist/components/LDTimePicker/FormTimePicker.d.ts.map +0 -1
  165. package/dist/components/LDToggleButton/FormToggleButton.d.ts +0 -12
  166. package/dist/components/LDToggleButton/FormToggleButton.d.ts.map +0 -1
@@ -1,42 +1,112 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useState, } from 'react';
4
4
  import dayjs from 'dayjs';
5
5
  import { ko } from 'react-day-picker/locale';
6
6
  import PopoverBase from '../LDPopover/PopoverBase.js';
7
- import LDCalendar from '../LDCalendar/LDCalendar.js';
8
- import LucideIcon from '../Icon/LucideIcon.js';
7
+ import LDCalendar, { TimePicker } from '../LDCalendar/LDCalendar.js';
9
8
  import cn from '../../utils/cn.js';
10
- const LDDatePicker = ({ value, defaultValue, onChange, placeholder = 'YYYY-MM-DD', disabled = false, block = false, color = 'neutral', isDisableAfterNow = false, minDate, trailingIcon = (_jsx(LucideIcon, { name: "calendar", size: 15, className: "text-mono400" })), showReset = true, className, style, triggerClassName, }) => {
9
+ import { Calendar, RotateCw } from 'lucide-react';
10
+ import LDButton from '../../components/LDButton/LDButton.js';
11
+ const timeFromDate = (selectedDate) => selectedDate ? dayjs(selectedDate).format('HH:mm:ss') : '00:00:00';
12
+ const combineDateTime = (date, time) => {
13
+ if (!date)
14
+ return undefined;
15
+ const [h = 0, m = 0, s = 0] = (time ?? '00:00:00').split(':').map(Number);
16
+ return dayjs(date).hour(h).minute(m).second(s).toDate();
17
+ };
18
+ // ── 스타일 매핑 ──
19
+ const variantColorClasses = {
20
+ filled: {
21
+ neutral: 'bg-neutral50 text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] focus:bg-tintBlue focus:outline-lb500',
22
+ point: 'bg-tintBlue text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] focus:outline-lb500',
23
+ danger: 'bg-tintRed text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] outline-danger600',
24
+ },
25
+ outline: {
26
+ neutral: 'bg-transparent text-neutral900 border-none outline-2 outline-neutral100 [outline-style:solid] [outline-offset:-2px] focus:outline-lb500',
27
+ point: 'bg-transparent text-neutral900 border-none outline-2 outline-lb500 [outline-style:solid] [outline-offset:-2px]',
28
+ danger: 'bg-transparent text-neutral900 border-none outline-2 outline-danger600 [outline-style:solid] [outline-offset:-2px]',
29
+ },
30
+ };
31
+ const LDDatePicker = ({ variant = 'filled', color = 'neutral', value, defaultValue, onChange, placeholder, dateFormat = 'YYYYMMDD', timeFormat = 'HH:MM:SS', disabled = false, block = false, isDisableAfterNow = false, minDate, trailingIcon = _jsx(Calendar, { size: 15, className: "text-mono400" }), showReset = true, isExistTime = true, className, style, triggerClassName, }) => {
11
32
  const [open, setOpen] = useState(false);
12
- const [internal, setInternal] = useState(defaultValue);
13
- const isControlled = value !== undefined;
14
- const resolved = isControlled ? value : internal;
15
- const handleSelect = (date) => {
16
- if (!isControlled)
17
- setInternal(date);
18
- onChange?.(date);
19
- setOpen(false);
33
+ const [selectedDate, setSelectedDate] = useState(value);
34
+ const [selectedTime, setSelectedTime] = useState(timeFromDate(value));
35
+ useEffect(() => {
36
+ if (open)
37
+ return;
38
+ setSelectedDate(value);
39
+ setSelectedTime(timeFromDate(value));
40
+ }, [open, value]);
41
+ const placeholderText = useMemo(() => {
42
+ if (placeholder)
43
+ return placeholder;
44
+ if (!isExistTime)
45
+ return dateFormat;
46
+ return `${dateFormat} ${timeFormat}`;
47
+ }, [placeholder, isExistTime, dateFormat, timeFormat]);
48
+ const reset = (d) => {
49
+ setSelectedDate(d);
50
+ setSelectedTime(timeFromDate(d));
20
51
  };
21
52
  const handleReset = (e) => {
22
53
  e.stopPropagation();
23
- if (!isControlled)
24
- setInternal(undefined);
25
- onChange?.(undefined);
54
+ reset(defaultValue);
55
+ if (!open)
56
+ onChange?.(defaultValue);
57
+ };
58
+ const handleConfirm = () => {
59
+ onChange?.(combineDateTime(selectedDate, selectedTime));
60
+ setOpen(false);
61
+ };
62
+ const handleCancel = () => {
63
+ reset(value);
64
+ setOpen(false);
65
+ };
66
+ const handleOpenChange = (next) => {
67
+ if (next) {
68
+ reset(value);
69
+ setOpen(true);
70
+ return;
71
+ }
72
+ if (selectedDate)
73
+ handleConfirm();
74
+ else
75
+ setOpen(false);
76
+ };
77
+ const handleSelect = useCallback((date) => {
78
+ setSelectedDate(date);
79
+ }, []);
80
+ const handleSelectedTime = (time) => {
81
+ setSelectedTime(time);
26
82
  };
27
- return (_jsx("div", { className: cn(block ? 'w-full' : 'w-auto', className), children: _jsxs(PopoverBase.Root, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverBase.Trigger, { disabled: disabled, style: style, className: cn('border border-neutral100 h-[38px] w-full display14 px-4 rounded-[5px]', 'flex flex-row items-center justify-between gap-4', 'outline-2 outline-transparent ld-transition', disabled && 'cursor-not-allowed opacity-50', triggerClassName), children: [resolved ? (_jsx("span", { children: dayjs(resolved).format('YYYY-MM-DD') })) : (_jsx("span", { className: "text-mono400", children: placeholder })), _jsxs("div", { className: "flex gap-[6px]", children: [trailingIcon, showReset && resolved && (_jsx("span", { role: "button", tabIndex: 0, className: "text-mono400 cursor-pointer", onClick: handleReset, onKeyDown: (e) => {
83
+ const isSameAsDefault = (combineDateTime(selectedDate, selectedTime)?.getTime() ?? null) ===
84
+ (defaultValue?.getTime() ?? null);
85
+ const showRotate = showReset && !isSameAsDefault;
86
+ const displayDate = open
87
+ ? combineDateTime(selectedDate, selectedTime)
88
+ : value;
89
+ const isDateDisabled = useMemo(() => {
90
+ if (isDisableAfterNow)
91
+ return (date) => date > new Date();
92
+ if (minDate) {
93
+ return (date) => {
94
+ const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
95
+ const minDateOnly = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
96
+ return dateOnly < minDateOnly;
97
+ };
98
+ }
99
+ return false;
100
+ }, [isDisableAfterNow, minDate]);
101
+ return (_jsx("div", { className: cn(block ? 'w-full' : 'w-auto', className), children: _jsxs(PopoverBase.Root, { open: open, onOpenChange: handleOpenChange, children: [_jsxs(PopoverBase.Trigger, { disabled: disabled, style: style, className: cn('border border-neutral100 h-[38px] w-full display14 px-4 rounded-[5px]', 'flex flex-row items-center justify-between gap-4', 'outline-2 outline-transparent ld-transition', disabled
102
+ ? 'cursor-not-allowed opacity-50'
103
+ : variantColorClasses[variant][color], triggerClassName), children: [displayDate ? (_jsx("span", { children: !isExistTime
104
+ ? `${dayjs(displayDate).format(dateFormat)}`
105
+ : `${dayjs(displayDate).format(`${dateFormat} ${timeFormat.replaceAll('M', 'm').replaceAll('S', 's')}`)}` })) : (_jsx("span", { className: "text-mono400", children: placeholderText })), _jsxs("div", { className: "flex items-center gap-xs", children: [_jsx("span", { role: "button", tabIndex: showRotate ? 0 : -1, "aria-hidden": !showRotate, className: cn('flex items-center text-mono400 cursor-pointer ld-transition', !showRotate ? 'opacity-0' : 'opacity-100'), onClick: handleReset, onKeyDown: (e) => {
28
106
  if (e.key === 'Enter' || e.key === ' ') {
29
107
  e.preventDefault();
30
108
  handleReset(e);
31
109
  }
32
- }, children: _jsx(LucideIcon, { name: "rotateCw", size: 14 }) }))] })] }), _jsx(PopoverBase.Content, { className: "rounded-md border border-neutral-200 bg-white shadow-md p-0 min-w-[280px] w-[var(--radix-popover-trigger-width)]", children: _jsx(LDCalendar, { className: "w-full", captionLayout: "dropdown", mode: "single", locale: ko, selected: resolved, onSelect: handleSelect, disabled: isDisableAfterNow
33
- ? (date) => date > new Date()
34
- : minDate
35
- ? (date) => {
36
- const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
37
- const minDateOnly = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
38
- return dateOnly < minDateOnly;
39
- }
40
- : false }) })] }) }));
110
+ }, children: _jsx(RotateCw, { size: 15 }) }), trailingIcon] })] }), _jsx(PopoverBase.Content, { className: "rounded-md border border-neutral-200 bg-white shadow-md p-0", children: _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-row max-h-90", children: [_jsx(LDCalendar, { captionLayout: "dropdown", mode: "single", locale: ko, selected: selectedDate, onSelect: handleSelect, disabled: isDateDisabled }), isExistTime && (_jsx(TimePicker, { value: selectedTime, onChange: handleSelectedTime, className: "shrink-0" }))] }), _jsxs("div", { className: "flex items-center justify-end gap-sm py-xs px-sm border-t border-neutral100", children: [_jsx(LDButton, { variant: "outline", color: "neutral", onClick: handleCancel, children: "\uCDE8\uC18C" }), _jsx(LDButton, { onClick: handleConfirm, children: "\uD655\uC778" })] })] }) })] }) }));
41
111
  };
42
112
  export default LDDatePicker;
@@ -0,0 +1,13 @@
1
+ import { type RegisterOptions } from 'react-hook-form';
2
+ import { type LDDatePickerProps } from './LDDatePicker.js';
3
+ interface LDFormDatePickerProps extends Omit<LDDatePickerProps, 'value' | 'defaultValue' | 'onChange'> {
4
+ name: string;
5
+ options?: RegisterOptions;
6
+ customErrorMessage?: string;
7
+ isHiddenErrorMessage?: boolean;
8
+ isToastErrorMessage?: boolean;
9
+ errorClassName?: string;
10
+ }
11
+ declare const LDFormDatePicker: ({ name, options, customErrorMessage, isHiddenErrorMessage, errorClassName, disabled, block, ...props }: LDFormDatePickerProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default LDFormDatePicker;
13
+ //# sourceMappingURL=LDFormDatePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDFormDatePicker.d.ts","sourceRoot":"","sources":["../../../src/components/LDDatePicker/LDFormDatePicker.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,iBAAiB,CAAC;AACvE,OAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKtE,UAAU,qBAAsB,SAAQ,IAAI,CAC1C,iBAAiB,EACjB,OAAO,GAAG,cAAc,GAAG,UAAU,CACtC;IACC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,QAAA,MAAM,gBAAgB,GAAI,wGASvB,qBAAqB,4CAiCvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFormContext } from 'react-hook-form';
4
+ import LDDatePicker from './LDDatePicker.js';
5
+ import FormField from '../LDForm/LDFormField.js';
6
+ import FormErrorMessage from '../LDForm/LDFormErrorMessage.js';
7
+ import cn from '../../utils/cn.js';
8
+ const LDFormDatePicker = ({ name, options, customErrorMessage = '', isHiddenErrorMessage = true, errorClassName, disabled = false, block = false, ...props }) => {
9
+ const { formState } = useFormContext();
10
+ const defaultValue = formState.defaultValues?.[name];
11
+ return (_jsx(FormField, { name: name, options: options, customErrorMessage: customErrorMessage, disabled: disabled, children: ({ field, fieldState }) => (_jsxs("div", { className: cn('relative', block ? 'w-full' : 'w-auto'), children: [_jsx(LDDatePicker, { ...props, block: block, value: field.value ?? undefined, defaultValue: defaultValue, onChange: (date) => {
12
+ field.onChange(date);
13
+ }, disabled: disabled, color: fieldState.hasError ? 'danger' : (props.color ?? 'neutral') }), _jsx(FormErrorMessage, { error: fieldState.error, disabled: disabled, className: cn(isHiddenErrorMessage && 'hidden', errorClassName) })] })) }));
14
+ };
15
+ export default LDFormDatePicker;
@@ -4,7 +4,10 @@ type DateRange = {
4
4
  to?: Date;
5
5
  };
6
6
  type ViewMode = 'daily' | 'monthly' | 'yearly';
7
+ type DateRangePickerVariant = 'filled' | 'outline';
8
+ type DateRangePickerColor = 'neutral' | 'point' | 'danger';
7
9
  export interface LDDateRangePickerProps {
10
+ variant?: DateRangePickerVariant;
8
11
  /** 선택된 날짜 범위 ({ from, to? }) */
9
12
  value?: DateRange;
10
13
  /** 날짜 범위 변경 시 호출되는 콜백 */
@@ -14,19 +17,23 @@ export interface LDDateRangePickerProps {
14
17
  /** true 시 너비 100% */
15
18
  block?: boolean;
16
19
  /** 색상 테마 (danger 시 에러 스타일) */
17
- color?: 'neutral' | 'danger';
20
+ color?: DateRangePickerColor;
21
+ /** 미선택 시 표시할 텍스트 */
22
+ placeholder?: string;
23
+ /** 날짜 표시 형식 */
24
+ dateFormat?: string;
25
+ /** 시간 표시 형식 */
26
+ timeFormat?: string;
18
27
  /** 현재 시점 이후 날짜 선택 비활성화 */
19
28
  isDisableAfterNow?: boolean;
20
29
  /** 날짜 범위 완성 시 팝오버 자동 닫힘 */
21
30
  isAutoClose?: boolean;
22
31
  /** 선택 모드 (daily: 일별, monthly: 월별, yearly: 연별) */
23
32
  viewMode?: ViewMode;
24
- /** 초기 선택 날짜 범위 (uncontrolled) */
25
- defaultValue?: DateRange;
26
33
  /** 초기값 (리셋 시 이 값으로 복원) */
27
34
  initialValue?: {
28
- from: string;
29
- to: string;
35
+ from: string | Date;
36
+ to?: string | Date;
30
37
  };
31
38
  /** 트리거 우측 아이콘 */
32
39
  trailingIcon?: ReactNode;
@@ -44,6 +51,6 @@ export interface LDDateRangePickerProps {
44
51
  /** 트리거 버튼 className */
45
52
  triggerClassName?: string;
46
53
  }
47
- declare const LDDateRangePicker: ({ value, onChange, disabled, block, color, isDisableAfterNow, isAutoClose, viewMode, defaultValue, initialValue, trailingIcon, onRangeComplete, onResetComplete, className, style, triggerClassName, }: LDDateRangePickerProps) => import("react/jsx-runtime").JSX.Element;
54
+ declare const LDDateRangePicker: ({ variant, value, onChange, disabled, block, color, placeholder, dateFormat, timeFormat, isDisableAfterNow, isAutoClose, viewMode, initialValue, trailingIcon, onRangeComplete, onResetComplete, className, style, triggerClassName, }: LDDateRangePickerProps) => import("react/jsx-runtime").JSX.Element;
48
55
  export default LDDateRangePicker;
49
56
  //# sourceMappingURL=LDDateRangePicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LDDateRangePicker.d.ts","sourceRoot":"","sources":["../../../src/components/LDDateRangePicker/LDDateRangePicker.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AASjD,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAC3C,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAe/C,MAAM,WAAW,sBAAsB;IACrC,gCAAgC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,cAAc;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iCAAiC;IACjC,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,0BAA0B;IAC1B,YAAY,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,iBAAiB;IACjB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,EAAE,EAAE,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,0BAA0B;IAC1B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,uBAAuB;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,QAAA,MAAM,iBAAiB,GAAI,wMAmBxB,sBAAsB,4CAiLxB,CAAC;AA6LF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"LDDateRangePicker.d.ts","sourceRoot":"","sources":["../../../src/components/LDDateRangePicker/LDDateRangePicker.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAC;AAWf,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAC3C,KAAK,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAC/C,KAAK,sBAAsB,GAAG,QAAQ,GAAG,SAAS,CAAC;AACnD,KAAK,oBAAoB,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAqB3D,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,gCAAgC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,cAAc;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0BAA0B;IAC1B,YAAY,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC3D,iBAAiB;IACjB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,4BAA4B;IAC5B,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,EAAE,EAAE,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5D,0BAA0B;IAC1B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,uBAAuB;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwBD,QAAA,MAAM,iBAAiB,GAAI,wOAoBxB,sBAAsB,4CAwSxB,CAAC;AA6LF,eAAe,iBAAiB,CAAC"}
@@ -1,121 +1,171 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
4
4
  import dayjs from 'dayjs';
5
5
  import { ko } from 'react-day-picker/locale';
6
6
  import PopoverBase from '../LDPopover/PopoverBase.js';
7
- import LDCalendar from '../LDCalendar/LDCalendar.js';
8
- import LucideIcon from '../Icon/LucideIcon.js';
7
+ import LDCalendar, { TimePicker } from '../LDCalendar/LDCalendar.js';
8
+ import LDButton from '../LDButton/LDButton.js';
9
9
  import cn from '../../utils/cn.js';
10
+ import { Calendar, ChevronLeft, ChevronRight, RotateCw } from 'lucide-react';
11
+ import LDDivider from '../../components/LDDivider/LDDivider.js';
10
12
  const dateFormats = {
11
- daily: 'YYYY-MM-DD',
12
- monthly: 'YYYY-MM',
13
+ daily: 'YYYYMMDD',
14
+ monthly: 'YYYYMM',
13
15
  yearly: 'YYYY',
14
16
  };
15
- const placeholders = {
16
- daily: 'YYYY-MM-DD ~ YYYY-MM-DD',
17
- monthly: 'YYYY-MM ~ YYYY-MM',
18
- yearly: 'YYYY ~ YYYY',
17
+ const timeFromDate = (date) => date ? dayjs(date).format('HH:mm:ss') : '00:00:00';
18
+ const composeDateTime = (date, time) => {
19
+ const [h, m, s] = time.split(':').map(Number);
20
+ return dayjs(date)
21
+ .hour(h ?? 0)
22
+ .minute(m ?? 0)
23
+ .second(s ?? 0)
24
+ .toDate();
19
25
  };
20
- const LDDateRangePicker = ({ value, onChange, disabled = false, block = false, color = 'neutral', isDisableAfterNow = false, isAutoClose = false, viewMode = 'daily', defaultValue, initialValue, trailingIcon = (_jsx(LucideIcon, { name: "calendar", size: 15, className: "text-mono400" })), onRangeComplete, onResetComplete, className, style, triggerClassName, }) => {
26
+ const variantColorClasses = {
27
+ filled: {
28
+ neutral: 'bg-neutral50 text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] focus:bg-tintBlue focus:outline-lb500',
29
+ point: 'bg-tintBlue text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] focus:outline-lb500',
30
+ danger: 'bg-tintRed text-neutral900 border-none outline-2 outline-transparent [outline-style:solid] [outline-offset:-2px] outline-danger600',
31
+ },
32
+ outline: {
33
+ neutral: 'bg-transparent text-neutral900 border-none outline-2 outline-neutral100 [outline-style:solid] [outline-offset:-2px] focus:outline-lb500',
34
+ point: 'bg-transparent text-neutral900 border-none outline-2 outline-lb500 [outline-style:solid] [outline-offset:-2px]',
35
+ danger: 'bg-transparent text-neutral900 border-none outline-2 outline-danger600 [outline-style:solid] [outline-offset:-2px]',
36
+ },
37
+ };
38
+ const LDDateRangePicker = ({ variant = 'filled', value, onChange, disabled = false, block = false, color = 'neutral', placeholder, dateFormat, timeFormat = 'HH:MM:SS', isDisableAfterNow = false, isAutoClose = false, viewMode = 'daily', initialValue, trailingIcon = _jsx(Calendar, { size: 15, className: "text-mono400" }), onRangeComplete, onResetComplete, className, style, triggerClassName, }) => {
39
+ const fmt = dateFormat ?? dateFormats[viewMode];
40
+ const normalizedTimeFormat = timeFormat
41
+ .replaceAll('M', 'm')
42
+ .replaceAll('S', 's');
21
43
  const [open, setOpen] = useState(false);
22
- const [internal, setInternal] = useState(defaultValue);
23
- const isControlled = value !== undefined;
24
- const resolved = isControlled ? value : internal;
25
- const fmt = dateFormats[viewMode];
44
+ const [leftMonth, setLeftMonth] = useState(new Date());
45
+ const [rightMonth, setRightMonth] = useState(dayjs().add(1, 'month').toDate());
46
+ const [selectedRange, setSelectedRange] = useState(value);
47
+ const [fromTime, setFromTime] = useState(timeFromDate(value?.from));
48
+ const [toTime, setToTime] = useState(timeFromDate(value?.to));
49
+ // 시간은 ref로도 들고 있어 select 핸들러가 시간 변경에도 참조 안정 유지(memo 스킵용)
50
+ const fromTimeRef = useRef(fromTime);
51
+ const toTimeRef = useRef(toTime);
52
+ fromTimeRef.current = fromTime;
53
+ toTimeRef.current = toTime;
54
+ // selectedRange(날짜)와 fromTime/toTime(시간)을 병합. 커밋·표시 시점에만 사용.
55
+ const composeRange = useCallback((r) => r?.from
56
+ ? {
57
+ from: composeDateTime(r.from, fromTimeRef.current),
58
+ to: r.to ? composeDateTime(r.to, toTimeRef.current) : undefined,
59
+ }
60
+ : undefined, []);
61
+ const isDateDisabled = useMemo(() => (isDisableAfterNow ? (date) => date > new Date() : false), [isDisableAfterNow]);
62
+ const leftEndMonth = useMemo(() => dayjs(rightMonth).subtract(1, 'month').toDate(), [rightMonth]);
63
+ const rightStartMonth = useMemo(() => dayjs(leftMonth).add(1, 'month').toDate(), [leftMonth]);
64
+ const baseline = initialValue
65
+ ? {
66
+ from: dayjs(initialValue.from).toDate(),
67
+ to: initialValue.to ? dayjs(initialValue.to).toDate() : undefined,
68
+ }
69
+ : undefined;
70
+ useEffect(() => {
71
+ if (open)
72
+ return;
73
+ setSelectedRange(value);
74
+ setFromTime(timeFromDate(value?.from));
75
+ setToTime(timeFromDate(value?.to));
76
+ }, [open, value]);
77
+ const displayRange = open ? composeRange(selectedRange) : value;
78
+ const displayFmt = viewMode === 'daily' ? `${fmt} ${normalizedTimeFormat}` : fmt;
79
+ const placeholderFmt = viewMode === 'daily' ? `${fmt} ${timeFormat}` : fmt;
80
+ const placeholderText = placeholder ?? `${placeholderFmt} ~ ${placeholderFmt}`;
26
81
  const renderDateRange = () => {
27
- if (!resolved?.from) {
28
- return _jsx("span", { className: "text-mono400", children: placeholders[viewMode] });
82
+ if (!displayRange?.from) {
83
+ return _jsx("span", { className: "text-mono400", children: placeholderText });
29
84
  }
30
- if (!resolved.to) {
31
- return (_jsxs("span", { className: "text-mono400", children: [dayjs(resolved.from).format(fmt), " ~", ' ', placeholders[viewMode].split(' ~ ')[1]] }));
85
+ if (!displayRange.to) {
86
+ return (_jsxs("span", { className: "text-mono400", children: [dayjs(displayRange.from).format(displayFmt), " ~ ", placeholderFmt] }));
32
87
  }
33
- return (_jsxs(_Fragment, { children: [dayjs(resolved.from).format(fmt), " ~ ", dayjs(resolved.to).format(fmt)] }));
88
+ return (_jsxs(_Fragment, { children: [dayjs(displayRange.from).format(displayFmt), " ~", ' ', dayjs(displayRange.to).format(displayFmt)] }));
89
+ };
90
+ const reset = (r) => {
91
+ setFromTime(timeFromDate(r?.from));
92
+ setToTime(timeFromDate(r?.to));
34
93
  };
94
+ // rotate: initialValue(baseline)로 복원. 기존 동작대로 즉시 커밋한다.
35
95
  const handleReset = (e) => {
36
96
  e.stopPropagation();
37
- const resetValue = initialValue
38
- ? {
39
- from: dayjs(initialValue.from).toDate(),
40
- to: dayjs(initialValue.to).toDate(),
41
- }
42
- : undefined;
43
- if (!isControlled)
44
- setInternal(resetValue);
45
- onChange?.(resetValue);
97
+ setSelectedRange(baseline);
98
+ reset(baseline);
99
+ onChange?.(baseline);
46
100
  onResetComplete?.();
47
101
  };
48
- const isResetAvailable = initialValue?.from
49
- ? dayjs(resolved?.from).format(fmt) !== initialValue.from ||
50
- dayjs(resolved?.to).format(fmt) !== initialValue.to
51
- : !!resolved?.from;
52
- const handleRangeSelect = (range) => {
53
- if (!range)
102
+ const formatRangeDate = (date) => date ? dayjs(date).format(fmt) : '';
103
+ const isResetAvailable = baseline?.from
104
+ ? formatRangeDate(selectedRange?.from) !== formatRangeDate(baseline.from) ||
105
+ formatRangeDate(selectedRange?.to) !== formatRangeDate(baseline.to) ||
106
+ // daily 모드는 시간 차이도 리셋 대상
107
+ (viewMode === 'daily' &&
108
+ (fromTime !== timeFromDate(baseline.from) ||
109
+ toTime !== timeFromDate(baseline.to)))
110
+ : !!selectedRange?.from;
111
+ const handleConfirm = () => {
112
+ onChange?.(composeRange(selectedRange));
113
+ setOpen(false);
114
+ };
115
+ const handleCancel = () => {
116
+ setSelectedRange(value);
117
+ reset(value);
118
+ setOpen(false);
119
+ };
120
+ const handleOpenChange = (next) => {
121
+ if (next) {
122
+ setSelectedRange(value); // 열 때 현재 값으로 편집 시작
123
+ reset(value);
124
+ setOpen(true);
54
125
  return;
55
- if (!isControlled)
56
- setInternal(range);
57
- onChange?.(range);
58
- if (range.from && range.to) {
59
- onRangeComplete?.(range);
60
- if (isAutoClose)
61
- setOpen(false);
62
126
  }
127
+ // overlay·ESC: 선택이 있으면 커밋, 없으면 그냥 닫기
128
+ if (selectedRange?.from)
129
+ handleConfirm();
130
+ else
131
+ setOpen(false);
63
132
  };
64
- const handleDailySelect = (selected) => {
65
- if (!selected)
66
- return;
67
- const current = resolved;
68
- // from만 있는 상태에서 두 번째 클릭
69
- if (current?.from && !current?.to && selected?.from && selected?.to) {
70
- handleRangeSelect(selected);
133
+ // 시간만 갱신 (selectedRange는 안 건드림 → 캘린더 selected 불변 → 애니메이션 재실행 X)
134
+ const handleFromTime = (time) => setFromTime(time);
135
+ const handleToTime = (time) => setToTime(time);
136
+ const handleRangeSelect = useCallback((range) => {
137
+ if (!range)
71
138
  return;
72
- }
73
- // 완전한 범위가 이미 있는 상태에서 클릭
74
- if (current?.from && current?.to) {
75
- if (selected?.from &&
76
- selected?.to &&
77
- selected.from.getTime() === selected.to.getTime()) {
78
- if (!isControlled)
79
- setInternal({ from: selected.from, to: undefined });
80
- onChange?.({ from: selected.from, to: undefined });
81
- return;
82
- }
83
- if (selected?.from && selected?.to) {
84
- const clickedDate = selected.from.getTime() !== current.from.getTime() &&
85
- selected.from.getTime() !== current.to.getTime()
86
- ? selected.from
87
- : selected.to.getTime() !== current.from.getTime() &&
88
- selected.to.getTime() !== current.to.getTime()
89
- ? selected.to
90
- : selected.from;
91
- if (!isControlled)
92
- setInternal({ from: clickedDate, to: undefined });
93
- onChange?.({ from: clickedDate, to: undefined });
94
- return;
139
+ setSelectedRange(range);
140
+ if (range.from && range.to) {
141
+ const composed = composeRange(range);
142
+ onRangeComplete?.(composed);
143
+ if (isAutoClose) {
144
+ onChange?.(composed);
145
+ setOpen(false);
95
146
  }
96
147
  }
97
- // 기본 동작
98
- if (selected?.from &&
99
- selected?.to &&
100
- selected.from.getTime() === selected.to.getTime()) {
101
- if (!isControlled)
102
- setInternal({ from: selected.from, to: undefined });
103
- onChange?.({ from: selected.from, to: undefined });
104
- }
105
- else {
106
- if (!isControlled)
107
- setInternal(selected);
108
- onChange?.(selected);
148
+ }, [composeRange, isAutoClose, onChange, onRangeComplete]);
149
+ const handleDailySelect = useCallback((selected, clickedDate) => {
150
+ if (selectedRange?.from &&
151
+ !selectedRange.to &&
152
+ selected?.from &&
153
+ selected?.to) {
154
+ handleRangeSelect({ from: selected.from, to: selected.to });
155
+ return;
109
156
  }
110
- };
111
- return (_jsx("div", { className: cn(block ? 'w-full' : 'w-auto', className), children: _jsxs(PopoverBase.Root, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverBase.Trigger, { disabled: disabled, style: style, className: cn('border border-neutral100 h-[38px] w-full display14 px-4 rounded-[5px]', 'flex flex-row items-center justify-between gap-4', 'whitespace-nowrap outline-2 outline-transparent ld-transition', disabled && 'cursor-not-allowed opacity-50', color === 'danger'
112
- ? 'bg-tintRed outline-danger600'
113
- : 'focus-visible:bg-tintBlue focus-visible:outline-lb500', triggerClassName), children: [renderDateRange(), _jsxs("div", { className: "shrink-0 flex gap-1.5", children: [trailingIcon, isResetAvailable && (_jsx("span", { role: "button", tabIndex: 0, className: "text-mono400 cursor-pointer", onClick: handleReset, onKeyDown: (e) => {
157
+ setSelectedRange({ from: clickedDate, to: undefined });
158
+ }, [selectedRange, handleRangeSelect]);
159
+ return (_jsx("div", { className: cn(block ? 'w-full' : 'w-auto', className), children: _jsxs(PopoverBase.Root, { open: open, onOpenChange: handleOpenChange, children: [_jsxs(PopoverBase.Trigger, { disabled: disabled, style: style, className: cn('border border-neutral100 h-[38px] w-full display14 px-4 rounded-[5px]', 'flex flex-row items-center justify-between gap-4', 'whitespace-nowrap outline-2 outline-transparent ld-transition', disabled
160
+ ? 'cursor-not-allowed opacity-50'
161
+ : variantColorClasses[variant][color], triggerClassName), children: [renderDateRange(), _jsxs("div", { className: "shrink-0 flex items-center gap-xs", children: [_jsx("span", { role: "button", tabIndex: 0, className: cn('inline-flex items-center text-mono400 cursor-pointer transition-opacity duration-300', isResetAvailable
162
+ ? 'opacity-100'
163
+ : 'opacity-0 pointer-events-none'), onClick: handleReset, onKeyDown: (e) => {
114
164
  if (e.key === 'Enter' || e.key === ' ') {
115
165
  e.preventDefault();
116
166
  handleReset(e);
117
167
  }
118
- }, children: _jsx(LucideIcon, { name: "rotateCw", size: 14 }) }))] })] }), _jsxs(PopoverBase.Content, { className: "rounded-md border border-neutral-200 bg-white shadow-md p-0", children: [viewMode === 'daily' && (_jsx(LDCalendar, { captionLayout: "dropdown", mode: "range", locale: ko, selected: resolved, onSelect: handleDailySelect, disabled: isDisableAfterNow ? (date) => date > new Date() : false, numberOfMonths: 2 })), viewMode === 'monthly' && (_jsx(MonthRangePicker, { value: resolved, onChange: handleRangeSelect, isDisableAfterNow: isDisableAfterNow })), viewMode === 'yearly' && (_jsx(YearRangePicker, { value: resolved, onChange: handleRangeSelect, isDisableAfterNow: isDisableAfterNow }))] })] }) }));
168
+ }, children: _jsx(RotateCw, { size: 15 }) }), trailingIcon] })] }), _jsx(PopoverBase.Content, { className: "rounded-md border border-neutral-200 bg-white shadow-md p-0", children: _jsxs("div", { className: "flex flex-col", children: [viewMode === 'daily' && (_jsxs("div", { className: "flex flex-row mobile:flex-col", children: [_jsxs("div", { className: "flex flex-row max-h-90", children: [_jsx(LDCalendar, { captionLayout: "dropdown", mode: "range", locale: ko, selected: selectedRange, onSelect: handleDailySelect, month: leftMonth, onMonthChange: setLeftMonth, endMonth: leftEndMonth, disabled: isDateDisabled }), _jsx(TimePicker, { value: fromTime, onChange: handleFromTime, className: "shrink-0" })] }), _jsx(LDDivider, { orientation: "vertical", className: "h-auto self-stretch inline-block mobile:hidden" }), _jsx(LDDivider, { orientation: "horizontal", className: "w-auto self-stretch hidden mobile:inline-block" }), _jsxs("div", { className: "flex flex-row max-h-90", children: [_jsx(LDCalendar, { captionLayout: "dropdown", mode: "range", locale: ko, selected: selectedRange, onSelect: handleDailySelect, month: rightMonth, onMonthChange: setRightMonth, startMonth: rightStartMonth, disabled: isDateDisabled }), _jsx(TimePicker, { value: toTime, onChange: handleToTime, className: "shrink-0" })] })] })), viewMode === 'monthly' && (_jsx(MonthRangePicker, { value: selectedRange, onChange: handleRangeSelect, isDisableAfterNow: isDisableAfterNow })), viewMode === 'yearly' && (_jsx(YearRangePicker, { value: selectedRange, onChange: handleRangeSelect, isDisableAfterNow: isDisableAfterNow })), _jsxs("div", { className: "flex items-center justify-end gap-sm py-xs px-sm border-t border-neutral100", children: [_jsx(LDButton, { variant: "outline", color: "neutral", onClick: handleCancel, children: "\uCDE8\uC18C" }), _jsx(LDButton, { onClick: handleConfirm, children: "\uD655\uC778" })] })] }) })] }) }));
119
169
  };
120
170
  // ── MonthRangePicker ──
121
171
  const MonthRangePicker = ({ value, onChange, isDisableAfterNow, }) => {
@@ -151,8 +201,8 @@ const MonthRangePicker = ({ value, onChange, isDisableAfterNow, }) => {
151
201
  return false;
152
202
  return new Date(currentYear, month, 1) > dayjs(now).endOf('month').toDate();
153
203
  };
154
- return (_jsxs("div", { className: "p-3 w-[280px]", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer", onClick: () => setCurrentYear((prev) => prev - 1), children: _jsx(LucideIcon, { name: "chevronLeft", size: 16 }) }), _jsxs("span", { className: "display14 bold", children: [currentYear, "\uB144"] }), _jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setCurrentYear((prev) => prev + 1), disabled: isDisableAfterNow && currentYear >= now.getFullYear(), children: _jsx(LucideIcon, { name: "chevronRight", size: 16 }) })] }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 12 }, (_, m) => (_jsxs("button", { type: "button", onClick: () => handleMonthClick(m), disabled: isDisabled(m), className: cn('h-10 rounded-md display12 ld-transition cursor-pointer', isSelected(m)
155
- ? 'bg-lb500 text-white bold'
204
+ return (_jsxs("div", { className: "p-3 w-[280px]", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer", onClick: () => setCurrentYear((prev) => prev - 1), children: _jsx(ChevronLeft, { size: 16 }) }), _jsxs("span", { className: "display14 bold", children: [currentYear, "\uB144"] }), _jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setCurrentYear((prev) => prev + 1), disabled: isDisableAfterNow && currentYear >= now.getFullYear(), children: _jsx(ChevronRight, { size: 16 }) })] }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 12 }, (_, m) => (_jsxs("button", { type: "button", onClick: () => handleMonthClick(m), disabled: isDisabled(m), className: cn('h-10 rounded-md display12 ld-transition cursor-pointer', isSelected(m)
205
+ ? 'bg-tintBlue text-lb500 bold'
156
206
  : 'hover:bg-neutral100 text-mono500', isDisabled(m) && 'opacity-30 cursor-not-allowed'), children: [m + 1, "\uC6D4"] }, m))) })] }));
157
207
  };
158
208
  // ── YearRangePicker ──
@@ -189,9 +239,9 @@ const YearRangePicker = ({ value, onChange, isDisableAfterNow, }) => {
189
239
  return false;
190
240
  return year > now.getFullYear();
191
241
  };
192
- return (_jsxs("div", { className: "p-3 w-[280px]", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer", onClick: () => setStartYearPage((prev) => prev - 12), children: _jsx(LucideIcon, { name: "chevronLeft", size: 16 }) }), _jsxs("span", { className: "display14 bold", children: [years[0], " - ", years[years.length - 1]] }), _jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setStartYearPage((prev) => prev + 12), disabled: isDisableAfterNow &&
193
- (years[years.length - 1] ?? 0) >= now.getFullYear(), children: _jsx(LucideIcon, { name: "chevronRight", size: 16 }) })] }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: years.map((y) => (_jsxs("button", { type: "button", onClick: () => handleYearClick(y), disabled: isDisabled(y), className: cn('h-10 rounded-md display12 ld-transition cursor-pointer', isSelected(y)
194
- ? 'bg-lb500 text-white bold'
242
+ return (_jsxs("div", { className: "p-3 w-[280px]", children: [_jsxs("div", { className: "flex items-center justify-between mb-4 px-2", children: [_jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer", onClick: () => setStartYearPage((prev) => prev - 12), children: _jsx(ChevronLeft, { size: 16 }) }), _jsxs("span", { className: "display14 bold", children: [years[0], " - ", years[years.length - 1]] }), _jsx("button", { type: "button", className: "inline-flex items-center justify-center size-8 rounded-md hover:bg-neutral-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", onClick: () => setStartYearPage((prev) => prev + 12), disabled: isDisableAfterNow &&
243
+ (years[years.length - 1] ?? 0) >= now.getFullYear(), children: _jsx(ChevronRight, { size: 16 }) })] }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: years.map((y) => (_jsxs("button", { type: "button", onClick: () => handleYearClick(y), disabled: isDisabled(y), className: cn('h-10 rounded-md display12 ld-transition cursor-pointer', isSelected(y)
244
+ ? 'bg-tintBlue text-lb500 bold'
195
245
  : 'hover:bg-neutral100 text-mono500', isDisabled(y) && 'opacity-30 cursor-not-allowed'), children: [y, "\uB144"] }, y))) })] }));
196
246
  };
197
247
  export default LDDateRangePicker;
@@ -0,0 +1,13 @@
1
+ import { type RegisterOptions } from 'react-hook-form';
2
+ import { type LDDateRangePickerProps } from './LDDateRangePicker.js';
3
+ interface LDFormDateRangePickerProps extends Omit<LDDateRangePickerProps, 'value' | 'onChange'> {
4
+ name: string;
5
+ options?: RegisterOptions;
6
+ customErrorMessage?: string;
7
+ isHiddenErrorMessage?: boolean;
8
+ isToastErrorMessage?: boolean;
9
+ errorClassName?: string;
10
+ }
11
+ declare const LDFormDateRangePicker: ({ name, options, customErrorMessage, isHiddenErrorMessage, errorClassName, disabled, block, ...props }: LDFormDateRangePickerProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default LDFormDateRangePicker;
13
+ //# sourceMappingURL=LDFormDateRangePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDFormDateRangePicker.d.ts","sourceRoot":"","sources":["../../../src/components/LDDateRangePicker/LDFormDateRangePicker.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,iBAAiB,CAAC;AACvE,OAA0B,EACxB,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAC;AAK7B,UAAU,0BAA2B,SAAQ,IAAI,CAC/C,sBAAsB,EACtB,OAAO,GAAG,UAAU,CACrB;IACC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,QAAA,MAAM,qBAAqB,GAAI,wGAS5B,0BAA0B,4CAyC5B,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFormContext } from 'react-hook-form';
4
+ import LDDateRangePicker from './LDDateRangePicker.js';
5
+ import FormField from '../LDForm/LDFormField.js';
6
+ import FormErrorMessage from '../LDForm/LDFormErrorMessage.js';
7
+ import cn from '../../utils/cn.js';
8
+ const LDFormDateRangePicker = ({ name, options, customErrorMessage = '', isHiddenErrorMessage = true, errorClassName, disabled = false, block = false, ...props }) => {
9
+ const { formState } = useFormContext();
10
+ const defaultValue = formState.defaultValues?.[name];
11
+ const initialValue = props.initialValue ??
12
+ (defaultValue?.from
13
+ ? {
14
+ from: defaultValue.from,
15
+ to: defaultValue.to,
16
+ }
17
+ : undefined);
18
+ return (_jsx(FormField, { name: name, options: options, customErrorMessage: customErrorMessage, disabled: disabled, children: ({ field, fieldState }) => (_jsxs("div", { className: cn('relative', block ? 'w-full' : 'w-auto'), children: [_jsx(LDDateRangePicker, { ...props, block: block, value: field.value ?? undefined, initialValue: initialValue, onChange: field.onChange, disabled: disabled, color: fieldState.hasError ? 'danger' : (props.color ?? 'neutral') }), _jsx(FormErrorMessage, { error: fieldState.error, disabled: disabled, className: cn(isHiddenErrorMessage && 'hidden', errorClassName) })] })) }));
19
+ };
20
+ export default LDFormDateRangePicker;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import DrawerBase from './DrawerBase.js';
3
- import LucideIcon from '../Icon/LucideIcon.js';
4
3
  import LDButton from '../LDButton/LDButton.js';
5
4
  import cn from '../../utils/cn.js';
5
+ import { X } from 'lucide-react';
6
6
  // ── 방향별 스타일 매핑 ──
7
7
  const directionClasses = {
8
8
  bottom: 'bottom-0 left-0 right-0 rounded-t-2xl max-h-[95dvh] flex flex-col',
@@ -25,6 +25,6 @@ const LDDrawer = ({ open, defaultOpen, onOpenChange, direction = 'bottom', snapP
25
25
  onOpenChange?.(false);
26
26
  }
27
27
  };
28
- return (_jsxs(DrawerBase.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, direction: direction, snapPoints: snapPoints, activeSnapPoint: activeSnapPoint, setActiveSnapPoint: setActiveSnapPoint, children: [trigger && _jsx(DrawerBase.Trigger, { asChild: true, children: trigger }), _jsx(DrawerBase.Overlay, {}), _jsxs(DrawerBase.Content, { className: cn('bg-white', directionClasses[direction], className), style: style, children: [shouldShowHandle && _jsx(DrawerBase.Handle, {}), _jsxs("div", { className: "relative flex flex-col flex-1 overflow-hidden p-5 gap-6", children: [_jsx(DrawerBase.Close, { asChild: true, children: _jsx("button", { className: "absolute top-4 right-5 text-neutral500 cursor-pointer outline-hidden", children: _jsx(LucideIcon, { name: "x", size: 16 }) }) }), (title || description) && (_jsxs("div", { className: "flex flex-col gap-2 pt-2", children: [title && (_jsx(DrawerBase.Title, { className: "display20 bold", children: title })), description && (_jsx(DrawerBase.Description, { className: "display14 text-neutral500", children: description }))] })), children && _jsx("div", { className: "flex-1 overflow-auto", children: children }), buttons && buttons.length > 0 && (_jsx("div", { className: "flex gap-2", children: buttons.map((button, index) => (_jsx(LDButton, { block: true, variant: button.variant, color: button.color, onClick: () => handleButtonClick(button), children: button.label }, index))) }))] })] })] }));
28
+ return (_jsxs(DrawerBase.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, direction: direction, snapPoints: snapPoints, activeSnapPoint: activeSnapPoint, setActiveSnapPoint: setActiveSnapPoint, children: [trigger && _jsx(DrawerBase.Trigger, { asChild: true, children: trigger }), _jsx(DrawerBase.Overlay, {}), _jsxs(DrawerBase.Content, { className: cn('bg-white', directionClasses[direction], className), style: style, children: [shouldShowHandle && _jsx(DrawerBase.Handle, {}), _jsxs("div", { className: "relative flex flex-col flex-1 overflow-hidden p-5 gap-6", children: [_jsx(DrawerBase.Close, { asChild: true, children: _jsx("button", { className: "absolute top-4 right-5 text-neutral500 cursor-pointer outline-hidden", children: _jsx(X, { size: 16 }) }) }), (title || description) && (_jsxs("div", { className: "flex flex-col gap-2 pt-2", children: [title && (_jsx(DrawerBase.Title, { className: "display20 bold", children: title })), description && (_jsx(DrawerBase.Description, { className: "display14 text-neutral500", children: description }))] })), children && _jsx("div", { className: "flex-1 overflow-auto", children: children }), buttons && buttons.length > 0 && (_jsx("div", { className: "flex gap-2", children: buttons.map((button, index) => (_jsx(LDButton, { block: true, variant: button.variant, color: button.color, onClick: () => handleButtonClick(button), children: button.label }, index))) }))] })] })] }));
29
29
  };
30
30
  export default LDDrawer;